units-representation.ts 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author David Sehnal <david.sehnal@gmail.com>
  6. */
  7. import { Structure, Unit } from 'mol-model/structure';
  8. import { Task } from 'mol-task'
  9. import { GraphicsRenderObject } from 'mol-gl/render-object';
  10. import { RepresentationContext, RepresentationParamsGetter, Representation } from '../representation';
  11. import { Visual } from '../visual';
  12. import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
  13. import { StructureGroup } from './units-visual';
  14. import { StructureRepresentation, StructureParams } from './representation';
  15. import { PickingId } from 'mol-geo/geometry/picking';
  16. import { MarkerAction } from 'mol-geo/geometry/marker-data';
  17. import { Theme, createEmptyTheme } from 'mol-theme/theme';
  18. import { ParamDefinition as PD } from 'mol-util/param-definition';
  19. import { UnitKind, UnitKindOptions } from './visual/util/common';
  20. import { Subject } from 'rxjs';
  21. export const UnitsParams = {
  22. ...StructureParams,
  23. unitKinds: PD.MultiSelect<UnitKind>(['atomic', 'spheres'], UnitKindOptions),
  24. }
  25. export type UnitsParams = typeof UnitsParams
  26. export interface UnitsVisual<P extends UnitsParams> extends Visual<StructureGroup, P> { }
  27. export function UnitsRepresentation<P extends UnitsParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> {
  28. let version = 0
  29. const updated = new Subject<number>()
  30. const _state = Representation.createState()
  31. let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>()
  32. let _structure: Structure
  33. let _groups: ReadonlyArray<Unit.SymmetryGroup>
  34. let _params: P
  35. let _props: PD.Values<P>
  36. let _theme = createEmptyTheme()
  37. function createOrUpdate(props: Partial<PD.Values<P>> = {}, structure?: Structure) {
  38. if (structure && structure !== _structure) {
  39. _params = getParams(ctx, structure)
  40. if (!_props) _props = PD.getDefaultValues(_params)
  41. }
  42. _props = Object.assign({}, _props, props)
  43. return Task.create('Creating or updating UnitsRepresentation', async runtime => {
  44. if (!_structure && !structure) {
  45. throw new Error('missing structure')
  46. } else if (structure && !_structure) {
  47. // console.log(label, 'initial structure')
  48. // First call with a structure, create visuals for each group.
  49. _groups = structure.unitSymmetryGroups;
  50. for (let i = 0; i < _groups.length; i++) {
  51. const group = _groups[i];
  52. const visual = visualCtor()
  53. const promise = visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, { group, structure })
  54. if (promise) await promise
  55. visuals.set(group.hashCode, { visual, group })
  56. if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length })
  57. }
  58. } else if (structure && !Structure.areEquivalent(structure, _structure)) {
  59. // console.log(label, 'structure not equivalent')
  60. // Tries to re-use existing visuals for the groups of the new structure.
  61. // Creates additional visuals if needed, destroys left-over visuals.
  62. _groups = structure.unitSymmetryGroups;
  63. // const newGroups: Unit.SymmetryGroup[] = []
  64. const oldVisuals = visuals
  65. visuals = new Map()
  66. for (let i = 0; i < _groups.length; i++) {
  67. const group = _groups[i];
  68. const visualGroup = oldVisuals.get(group.hashCode)
  69. if (visualGroup) {
  70. // console.log(label, 'found visualGroup to reuse')
  71. // console.log('old', visualGroup.group)
  72. // console.log('new', group)
  73. const { visual } = visualGroup
  74. const promise = visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, { group, structure })
  75. if (promise) await promise
  76. visuals.set(group.hashCode, { visual, group })
  77. oldVisuals.delete(group.hashCode)
  78. } else {
  79. // console.log(label, 'not found visualGroup to reuse, creating new')
  80. // newGroups.push(group)
  81. const visual = visualCtor()
  82. const promise = visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, { group, structure })
  83. if (promise) await promise
  84. visuals.set(group.hashCode, { visual, group })
  85. }
  86. if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length })
  87. }
  88. oldVisuals.forEach(({ visual }) => {
  89. // console.log(label, 'removed unused visual')
  90. visual.destroy()
  91. })
  92. // TODO review logic
  93. // For new groups, re-use left-over visuals
  94. // const unusedVisuals: UnitsVisual<P>[] = []
  95. // oldVisuals.forEach(({ visual }) => unusedVisuals.push(visual))
  96. // newGroups.forEach(async group => {
  97. // const visual = unusedVisuals.pop() || visualCtor()
  98. // await visual.createOrUpdate({ ...ctx, runtime }, _props, group)
  99. // visuals.set(group.hashCode, { visual, group })
  100. // })
  101. // unusedVisuals.forEach(visual => visual.destroy())
  102. } else if (structure && structure !== _structure && Structure.areEquivalent(structure, _structure)) {
  103. // console.log(label, 'structures equivalent but not identical')
  104. // Expects that for structures with the same hashCode,
  105. // the unitSymmetryGroups are the same as well.
  106. // Re-uses existing visuals for the groups of the new structure.
  107. _groups = structure.unitSymmetryGroups;
  108. // console.log('new', structure.unitSymmetryGroups)
  109. // console.log('old', _structure.unitSymmetryGroups)
  110. for (let i = 0; i < _groups.length; i++) {
  111. const group = _groups[i];
  112. const visualGroup = visuals.get(group.hashCode)
  113. if (visualGroup) {
  114. const promise = visualGroup.visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, { group, structure })
  115. if (promise) await promise
  116. visualGroup.group = group
  117. } else {
  118. throw new Error(`expected to find visual for hashCode ${group.hashCode}`)
  119. }
  120. if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length })
  121. }
  122. } else {
  123. // console.log(label, 'no new structure')
  124. // No new structure given, just update all visuals with new props.
  125. const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = [] // TODO avoid allocation
  126. visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ]))
  127. for (let i = 0, il = visualsList.length; i < il; ++i) {
  128. const [ visual ] = visualsList[i]
  129. const promise = visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props)
  130. if (promise) await promise
  131. if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: il })
  132. }
  133. }
  134. if (structure) _structure = structure
  135. updated.next(version++)
  136. });
  137. }
  138. function getLoci(pickingId: PickingId) {
  139. let loci: Loci = EmptyLoci
  140. visuals.forEach(({ visual }) => {
  141. const _loci = visual.getLoci(pickingId)
  142. if (!isEmptyLoci(_loci)) loci = _loci
  143. })
  144. return loci
  145. }
  146. function mark(loci: Loci, action: MarkerAction) {
  147. let changed = false
  148. visuals.forEach(({ visual }) => {
  149. changed = visual.mark(loci, action) || changed
  150. })
  151. return changed
  152. }
  153. function setState(state: Partial<Representation.State>) {
  154. if (state.visible !== undefined) visuals.forEach(({ visual }) => visual.setVisibility(state.visible!))
  155. if (state.pickable !== undefined) visuals.forEach(({ visual }) => visual.setPickable(state.pickable!))
  156. if (state.transform !== undefined) visuals.forEach(({ visual }) => visual.setTransform(state.transform!))
  157. Representation.updateState(_state, state)
  158. }
  159. function setTheme(theme: Theme) {
  160. _theme = theme
  161. }
  162. function destroy() {
  163. visuals.forEach(({ visual }) => visual.destroy())
  164. visuals.clear()
  165. }
  166. return {
  167. label,
  168. get groupCount() {
  169. let groupCount = 0
  170. visuals.forEach(({ visual }) => {
  171. if (visual.renderObject) groupCount += visual.groupCount
  172. })
  173. return groupCount
  174. },
  175. get renderObjects() {
  176. const renderObjects: GraphicsRenderObject[] = []
  177. visuals.forEach(({ visual }) => {
  178. if (visual.renderObject) renderObjects.push(visual.renderObject)
  179. })
  180. return renderObjects
  181. },
  182. get props() { return _props },
  183. get params() { return _params },
  184. get state() { return _state },
  185. get theme() { return _theme },
  186. updated,
  187. createOrUpdate,
  188. setState,
  189. setTheme,
  190. getLoci,
  191. mark,
  192. destroy
  193. }
  194. }