units-visual.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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. */
  6. import { Unit, Structure } from 'mol-model/structure';
  7. import { RepresentationProps, Visual } from '..';
  8. import { DefaultStructureMeshProps, MeshUpdateState } from '.';
  9. import { RuntimeContext } from 'mol-task';
  10. import { PickingId } from '../../util/picking';
  11. import { LocationIterator } from '../../util/location-iterator';
  12. import { Mesh } from '../../mesh/mesh';
  13. import { MarkerAction, applyMarkerAction, createMarkers } from '../../util/marker-data';
  14. import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
  15. import { MeshRenderObject } from 'mol-gl/render-object';
  16. import { createUnitsMeshRenderObject, createColors } from './visual/util/common';
  17. import { deepEqual, ValueCell, UUID } from 'mol-util';
  18. import { updateMeshValues, updateRenderableState } from '../util';
  19. import { Interval } from 'mol-data/int';
  20. import { createTransforms } from '../../util/transform-data';
  21. export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
  22. export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
  23. export const DefaultUnitsMeshProps = {
  24. ...DefaultStructureMeshProps,
  25. unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
  26. }
  27. export type UnitsMeshProps = typeof DefaultUnitsMeshProps
  28. export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> {
  29. defaultProps: P
  30. createMesh(ctx: RuntimeContext, unit: Unit, props: P, mesh?: Mesh): Promise<Mesh>
  31. createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
  32. getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
  33. mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean
  34. setUpdateState(state: MeshUpdateState, newProps: P, currentProps: P): void
  35. }
  36. export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> {
  37. const { defaultProps, createMesh, createLocationIterator, getLoci, mark, setUpdateState } = builder
  38. const updateState = MeshUpdateState.create()
  39. let renderObject: MeshRenderObject | undefined
  40. let currentProps: P
  41. let mesh: Mesh
  42. let currentGroup: Unit.SymmetryGroup
  43. let currentStructure: Structure
  44. let locationIt: LocationIterator
  45. let currentConformationId: UUID
  46. async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
  47. currentProps = Object.assign({}, defaultProps, props)
  48. currentProps.colorTheme.structure = currentStructure
  49. currentGroup = group
  50. const unit = group.units[0]
  51. currentConformationId = Unit.conformationId(unit)
  52. mesh = currentProps.unitKinds.includes(unit.kind)
  53. ? await createMesh(ctx, unit, currentProps, mesh)
  54. : Mesh.createEmpty(mesh)
  55. // TODO create empty location iterator when not in unitKinds
  56. locationIt = createLocationIterator(group)
  57. renderObject = await createUnitsMeshRenderObject(ctx, group, mesh, locationIt, currentProps)
  58. }
  59. async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
  60. if (!renderObject) return
  61. const newProps = Object.assign({}, currentProps, props)
  62. const unit = currentGroup.units[0]
  63. locationIt.reset()
  64. MeshUpdateState.reset(updateState)
  65. setUpdateState(updateState, newProps, currentProps)
  66. const newConformationId = Unit.conformationId(unit)
  67. if (newConformationId !== currentConformationId) {
  68. currentConformationId = newConformationId
  69. updateState.createMesh = true
  70. }
  71. if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
  72. if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) updateState.createMesh = true
  73. if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) updateState.updateColor = true
  74. if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createMesh = true
  75. //
  76. if (updateState.updateTransform) {
  77. locationIt = createLocationIterator(currentGroup)
  78. const { instanceCount, groupCount } = locationIt
  79. createTransforms(currentGroup, renderObject.values)
  80. createMarkers(instanceCount * groupCount, renderObject.values)
  81. updateState.updateColor = true
  82. }
  83. if (updateState.createMesh) {
  84. mesh = newProps.unitKinds.includes(unit.kind)
  85. ? await createMesh(ctx, unit, newProps, mesh)
  86. : Mesh.createEmpty(mesh)
  87. ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
  88. updateState.updateColor = true
  89. }
  90. if (updateState.updateColor) {
  91. await createColors(ctx, locationIt, newProps.colorTheme, renderObject.values)
  92. }
  93. updateMeshValues(renderObject.values, newProps)
  94. updateRenderableState(renderObject.state, newProps)
  95. currentProps = newProps
  96. }
  97. return {
  98. get renderObject () { return renderObject },
  99. async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
  100. if (structureGroup) currentStructure = structureGroup.structure
  101. const group = structureGroup ? structureGroup.group : undefined
  102. if (!group && !currentGroup) {
  103. throw new Error('missing group')
  104. } else if (group && (!currentGroup || !renderObject)) {
  105. await create(ctx, group, props)
  106. } else if (group && group.hashCode !== currentGroup.hashCode) {
  107. await create(ctx, group, props)
  108. } else {
  109. if (group && !areGroupsIdentical(group, currentGroup)) {
  110. currentGroup = group
  111. currentProps.colorTheme.structure = currentStructure
  112. }
  113. await update(ctx, props)
  114. }
  115. },
  116. getLoci(pickingId: PickingId) {
  117. return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
  118. },
  119. mark(loci: Loci, action: MarkerAction) {
  120. if (!renderObject) return
  121. const { tMarker } = renderObject.values
  122. const { groupCount, instanceCount } = locationIt
  123. function apply(interval: Interval) {
  124. const start = Interval.start(interval)
  125. const end = Interval.end(interval)
  126. return applyMarkerAction(tMarker.ref.value.array, start, end, action)
  127. }
  128. let changed = false
  129. if (isEveryLoci(loci)) {
  130. apply(Interval.ofBounds(0, groupCount * instanceCount))
  131. changed = true
  132. } else {
  133. changed = mark(loci, currentGroup, apply)
  134. }
  135. if (changed) {
  136. ValueCell.update(tMarker, tMarker.ref.value)
  137. }
  138. },
  139. destroy() {
  140. // TODO
  141. renderObject = undefined
  142. }
  143. }
  144. }
  145. function areGroupsIdentical(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) {
  146. return (
  147. groupA.units.length === groupB.units.length &&
  148. Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0])
  149. )
  150. }