units-representation.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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 { RenderObject } from 'mol-gl/render-object';
  10. import { Visual, RepresentationContext, RepresentationParamsGetter } from '../representation';
  11. import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
  12. import { StructureGroup } from './units-visual';
  13. import { StructureRepresentation, StructureParams } from './representation';
  14. import { PickingId } from 'mol-geo/geometry/picking';
  15. import { MarkerAction } from 'mol-geo/geometry/marker-data';
  16. import { Theme, ThemeProps, createTheme } from 'mol-theme/theme';
  17. import { ParamDefinition as PD } from 'mol-util/param-definition';
  18. import { UnitKind, UnitKindOptions } from './visual/util/common';
  19. import { BehaviorSubject } from 'rxjs';
  20. export const UnitsParams = {
  21. ...StructureParams,
  22. unitKinds: PD.MultiSelect<UnitKind>(['atomic', 'spheres'], UnitKindOptions),
  23. }
  24. export type UnitsParams = typeof UnitsParams
  25. export interface UnitsVisual<P extends UnitsParams> extends Visual<StructureGroup, P> { }
  26. export function UnitsRepresentation<P extends UnitsParams>(label: string, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: () => UnitsVisual<P>): StructureRepresentation<P> {
  27. const updated = new BehaviorSubject(0)
  28. let visuals = new Map<number, { group: Unit.SymmetryGroup, visual: UnitsVisual<P> }>()
  29. let _structure: Structure
  30. let _groups: ReadonlyArray<Unit.SymmetryGroup>
  31. let _params: P
  32. let _props: PD.Values<P>
  33. let _theme: Theme
  34. function createOrUpdate(ctx: RepresentationContext, props: Partial<PD.Values<P>> = {}, themeProps: ThemeProps = {}, structure?: Structure) {
  35. if (structure && structure !== _structure) {
  36. _params = getParams(ctx, structure)
  37. if (!_props) _props = PD.getDefaultValues(_params)
  38. }
  39. _props = Object.assign({}, _props, props)
  40. _theme = createTheme(ctx, { structure: structure || _structure }, props, themeProps, _theme)
  41. return Task.create('Creating or updating UnitsRepresentation', async runtime => {
  42. if (!_structure && !structure) {
  43. throw new Error('missing structure')
  44. } else if (structure && !_structure) {
  45. // console.log('initial structure')
  46. // First call with a structure, create visuals for each group.
  47. _groups = structure.unitSymmetryGroups;
  48. for (let i = 0; i < _groups.length; i++) {
  49. const group = _groups[i];
  50. const visual = visualCtor()
  51. await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
  52. visuals.set(group.hashCode, { visual, group })
  53. }
  54. } else if (structure && _structure.hashCode !== structure.hashCode) {
  55. // console.log('_structure.hashCode !== structure.hashCode')
  56. // Tries to re-use existing visuals for the groups of the new structure.
  57. // Creates additional visuals if needed, destroys left-over visuals.
  58. _groups = structure.unitSymmetryGroups;
  59. // const newGroups: Unit.SymmetryGroup[] = []
  60. const oldVisuals = visuals
  61. visuals = new Map()
  62. for (let i = 0; i < _groups.length; i++) {
  63. const group = _groups[i];
  64. const visualGroup = oldVisuals.get(group.hashCode)
  65. if (visualGroup) {
  66. const { visual } = visualGroup
  67. await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
  68. visuals.set(group.hashCode, { visual, group })
  69. oldVisuals.delete(group.hashCode)
  70. } else {
  71. // newGroups.push(group)
  72. const visual = visualCtor()
  73. await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
  74. visuals.set(group.hashCode, { visual, group })
  75. }
  76. }
  77. oldVisuals.forEach(({ visual }) => visual.destroy())
  78. // TODO review logic
  79. // For new groups, re-use left-over visuals
  80. // const unusedVisuals: UnitsVisual<P>[] = []
  81. // oldVisuals.forEach(({ visual }) => unusedVisuals.push(visual))
  82. // newGroups.forEach(async group => {
  83. // const visual = unusedVisuals.pop() || visualCtor()
  84. // await visual.createOrUpdate({ ...ctx, runtime }, _props, group)
  85. // visuals.set(group.hashCode, { visual, group })
  86. // })
  87. // unusedVisuals.forEach(visual => visual.destroy())
  88. } else if (structure && structure !== _structure && _structure.hashCode === structure.hashCode) {
  89. // console.log('_structure.hashCode === structure.hashCode')
  90. // Expects that for structures with the same hashCode,
  91. // the unitSymmetryGroups are the same as well.
  92. // Re-uses existing visuals for the groups of the new structure.
  93. _groups = structure.unitSymmetryGroups;
  94. for (let i = 0; i < _groups.length; i++) {
  95. const group = _groups[i];
  96. const visualGroup = visuals.get(group.hashCode)
  97. if (visualGroup) {
  98. await visualGroup.visual.createOrUpdate({ ...ctx, runtime }, _theme, _props, { group, structure })
  99. visualGroup.group = group
  100. } else {
  101. throw new Error(`expected to find visual for hashCode ${group.hashCode}`)
  102. }
  103. }
  104. } else {
  105. // console.log('no new structure')
  106. // No new structure given, just update all visuals with new props.
  107. const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = [] // TODO avoid allocation
  108. visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ]))
  109. for (let i = 0, il = visualsList.length; i < il; ++i) {
  110. const [ visual ] = visualsList[i]
  111. await visual.createOrUpdate({ ...ctx, runtime }, _theme, _props)
  112. }
  113. }
  114. if (structure) _structure = structure
  115. updated.next(updated.getValue() + 1)
  116. });
  117. }
  118. function getLoci(pickingId: PickingId) {
  119. let loci: Loci = EmptyLoci
  120. visuals.forEach(({ visual }) => {
  121. const _loci = visual.getLoci(pickingId)
  122. if (!isEmptyLoci(_loci)) loci = _loci
  123. })
  124. return loci
  125. }
  126. function mark(loci: Loci, action: MarkerAction) {
  127. let changed = false
  128. visuals.forEach(({ visual }) => {
  129. changed = visual.mark(loci, action) || changed
  130. })
  131. return changed
  132. }
  133. function setVisibility(value: boolean) {
  134. visuals.forEach(({ visual }) => {
  135. visual.setVisibility(value)
  136. })
  137. }
  138. function destroy() {
  139. visuals.forEach(({ visual }) => visual.destroy())
  140. visuals.clear()
  141. }
  142. return {
  143. label,
  144. get renderObjects() {
  145. const renderObjects: RenderObject[] = []
  146. visuals.forEach(({ visual }) => {
  147. if (visual.renderObject) renderObjects.push(visual.renderObject)
  148. })
  149. return renderObjects
  150. },
  151. get props() { return _props },
  152. get params() { return _params },
  153. get updated() { return updated },
  154. createOrUpdate,
  155. getLoci,
  156. mark,
  157. setVisibility,
  158. destroy
  159. }
  160. }