units-visual.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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 { VisualUpdateState, StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams, StructureParams } from '.';
  9. import { RuntimeContext } from 'mol-task';
  10. import { PickingId } from '../../geometry/picking';
  11. import { LocationIterator } from '../../util/location-iterator';
  12. import { Mesh } from '../../geometry/mesh/mesh';
  13. import { MarkerAction, applyMarkerAction, createMarkers } from '../../geometry/marker-data';
  14. import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
  15. import { MeshRenderObject, PointsRenderObject, LinesRenderObject, DirectVolumeRenderObject } from 'mol-gl/render-object';
  16. import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject, createUnitsDirectVolumeRenderObject, UnitKind, UnitKindOptions, includesUnitKind, colorChanged, sizeChanged } from './visual/util/common';
  17. import { deepEqual, ValueCell, UUID } from 'mol-util';
  18. import { Interval } from 'mol-data/int';
  19. import { Points } from '../../geometry/points/points';
  20. import { updateRenderableState, Geometry } from '../../geometry/geometry';
  21. import { createColors } from '../../geometry/color-data';
  22. import { createSizes } from '../../geometry/size-data';
  23. import { Lines } from '../../geometry/lines/lines';
  24. import { MultiSelectParam, paramDefaultValues } from 'mol-util/parameter';
  25. import { DirectVolume } from '../../geometry/direct-volume/direct-volume';
  26. import { RenderableValues } from 'mol-gl/renderable/schema';
  27. export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
  28. export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
  29. function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) {
  30. return (
  31. groupA.units.length === groupB.units.length &&
  32. Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0])
  33. )
  34. }
  35. const UnitsParams = {
  36. ...StructureParams,
  37. unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', ['atomic', 'spheres'], UnitKindOptions),
  38. }
  39. const DefaultUnitsProps = paramDefaultValues(UnitsParams)
  40. type UnitsProps = typeof DefaultUnitsProps
  41. type UnitsRenderObject = MeshRenderObject | LinesRenderObject | PointsRenderObject | DirectVolumeRenderObject
  42. interface UnitsVisualBuilder<P extends UnitsProps, G extends Geometry> {
  43. defaultProps: P
  44. createGeometry(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, geometry?: G): Promise<G>
  45. createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
  46. getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
  47. mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean
  48. setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
  49. }
  50. interface UnitsVisualGeometryBuilder<P extends UnitsProps, G extends Geometry> extends UnitsVisualBuilder<P, G> {
  51. createEmptyGeometry(geometry?: G): G
  52. createRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, geometry: Geometry, locationIt: LocationIterator, currentProps: P): Promise<UnitsRenderObject>
  53. updateValues(values: RenderableValues, newProps: P): void
  54. }
  55. export function UnitsVisual<P extends UnitsProps>(builder: UnitsVisualGeometryBuilder<P, Geometry>): UnitsVisual<P> {
  56. const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
  57. const { createEmptyGeometry, createRenderObject, updateValues } = builder
  58. const updateState = VisualUpdateState.create()
  59. let renderObject: UnitsRenderObject | undefined
  60. let currentProps: P
  61. let geometry: Geometry
  62. let currentGroup: Unit.SymmetryGroup
  63. let currentStructure: Structure
  64. let locationIt: LocationIterator
  65. let currentConformationId: UUID
  66. async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
  67. currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure })
  68. currentGroup = group
  69. const unit = group.units[0]
  70. currentConformationId = Unit.conformationId(unit)
  71. geometry = includesUnitKind(currentProps.unitKinds, unit)
  72. ? await createGeometry(ctx, unit, currentStructure, currentProps, geometry)
  73. : createEmptyGeometry(geometry)
  74. // TODO create empty location iterator when not in unitKinds
  75. locationIt = createLocationIterator(group)
  76. renderObject = await createRenderObject(ctx, group, geometry, locationIt, currentProps)
  77. }
  78. async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
  79. if (!renderObject) return
  80. const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
  81. const unit = currentGroup.units[0]
  82. locationIt.reset()
  83. VisualUpdateState.reset(updateState)
  84. setUpdateState(updateState, newProps, currentProps)
  85. const newConformationId = Unit.conformationId(unit)
  86. if (newConformationId !== currentConformationId) {
  87. currentConformationId = newConformationId
  88. updateState.createGeometry = true
  89. }
  90. if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
  91. if (colorChanged(currentProps, newProps)) updateState.updateColor = true
  92. if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
  93. //
  94. if (updateState.updateTransform) {
  95. locationIt = createLocationIterator(currentGroup)
  96. const { instanceCount, groupCount } = locationIt
  97. createUnitsTransform(currentGroup, renderObject.values)
  98. createMarkers(instanceCount * groupCount, renderObject.values)
  99. updateState.updateColor = true
  100. }
  101. if (updateState.createGeometry) {
  102. geometry = includesUnitKind(newProps.unitKinds, unit)
  103. ? await createGeometry(ctx, unit, currentStructure, newProps, geometry)
  104. : createEmptyGeometry(geometry)
  105. ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(geometry))
  106. updateState.updateColor = true
  107. }
  108. if (updateState.updateSize) {
  109. // not all geometries have size data, so check here
  110. if ('uSize' in renderObject.values) {
  111. await createSizes(ctx, locationIt, newProps, renderObject.values)
  112. }
  113. }
  114. if (updateState.updateColor) {
  115. await createColors(ctx, locationIt, newProps, renderObject.values)
  116. }
  117. updateValues(renderObject.values, newProps)
  118. updateRenderableState(renderObject.state, newProps)
  119. currentProps = newProps
  120. }
  121. return {
  122. get renderObject () { return renderObject },
  123. async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
  124. if (structureGroup) currentStructure = structureGroup.structure
  125. const group = structureGroup ? structureGroup.group : undefined
  126. if (!group && !currentGroup) {
  127. throw new Error('missing group')
  128. } else if (group && (!currentGroup || !renderObject)) {
  129. // console.log('unit-visual first create')
  130. await create(ctx, group, props)
  131. } else if (group && group.hashCode !== currentGroup.hashCode) {
  132. // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
  133. await create(ctx, group, props)
  134. } else {
  135. // console.log('unit-visual update')
  136. if (group && !sameGroupConformation(group, currentGroup)) {
  137. // console.log('unit-visual new conformation')
  138. currentGroup = group
  139. }
  140. await update(ctx, props)
  141. }
  142. },
  143. getLoci(pickingId: PickingId) {
  144. return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
  145. },
  146. mark(loci: Loci, action: MarkerAction) {
  147. if (!renderObject) return false
  148. const { tMarker } = renderObject.values
  149. const { groupCount, instanceCount } = locationIt
  150. function apply(interval: Interval) {
  151. const start = Interval.start(interval)
  152. const end = Interval.end(interval)
  153. return applyMarkerAction(tMarker.ref.value.array, start, end, action)
  154. }
  155. let changed = false
  156. if (isEveryLoci(loci)) {
  157. changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
  158. } else {
  159. changed = mark(loci, currentGroup, apply)
  160. }
  161. if (changed) {
  162. ValueCell.update(tMarker, tMarker.ref.value)
  163. }
  164. return changed
  165. },
  166. destroy() {
  167. // TODO
  168. renderObject = undefined
  169. }
  170. }
  171. }
  172. // mesh
  173. export const UnitsMeshParams = {
  174. ...StructureMeshParams,
  175. ...UnitsParams,
  176. }
  177. export const DefaultUnitsMeshProps = paramDefaultValues(UnitsMeshParams)
  178. export type UnitsMeshProps = typeof DefaultUnitsMeshProps
  179. export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> extends UnitsVisualBuilder<P, Mesh> { }
  180. export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> {
  181. return UnitsVisual({
  182. ...builder,
  183. setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
  184. builder.setUpdateState(state, newProps, currentProps)
  185. if (sizeChanged(currentProps, newProps)) state.createGeometry = true
  186. },
  187. createEmptyGeometry: Mesh.createEmpty,
  188. createRenderObject: createUnitsMeshRenderObject,
  189. updateValues: Mesh.updateValues
  190. })
  191. }
  192. // points
  193. export const UnitsPointsParams = {
  194. ...StructurePointsParams,
  195. ...UnitsParams,
  196. }
  197. export const DefaultUnitsPointsProps = paramDefaultValues(UnitsPointsParams)
  198. export type UnitsPointsProps = typeof DefaultUnitsPointsProps
  199. export interface UnitsPointVisualBuilder<P extends UnitsPointsProps> extends UnitsVisualBuilder<P, Points> { }
  200. export function UnitsPointsVisual<P extends UnitsPointsProps>(builder: UnitsPointVisualBuilder<P>): UnitsVisual<P> {
  201. return UnitsVisual({
  202. ...builder,
  203. createEmptyGeometry: Points.createEmpty,
  204. createRenderObject: createUnitsPointsRenderObject,
  205. setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
  206. builder.setUpdateState(state, newProps, currentProps)
  207. if (sizeChanged(currentProps, newProps)) state.updateSize = true
  208. },
  209. updateValues: Points.updateValues
  210. })
  211. }
  212. // lines
  213. export const UnitsLinesParams = {
  214. ...StructureLinesParams,
  215. ...UnitsParams,
  216. }
  217. export const DefaultUnitsLinesProps = paramDefaultValues(UnitsLinesParams)
  218. export type UnitsLinesProps = typeof DefaultUnitsLinesProps
  219. export interface UnitsLinesVisualBuilder<P extends UnitsLinesProps> extends UnitsVisualBuilder<P, Lines> { }
  220. export function UnitsLinesVisual<P extends UnitsLinesProps>(builder: UnitsLinesVisualBuilder<P>): UnitsVisual<P> {
  221. return UnitsVisual({
  222. ...builder,
  223. createEmptyGeometry: Lines.createEmpty,
  224. createRenderObject: createUnitsLinesRenderObject,
  225. setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
  226. builder.setUpdateState(state, newProps, currentProps)
  227. if (sizeChanged(currentProps, newProps)) state.updateSize = true
  228. },
  229. updateValues: Lines.updateValues
  230. })
  231. }
  232. // direct-volume
  233. export const UnitsDirectVolumeParams = {
  234. ...StructureDirectVolumeParams,
  235. ...UnitsParams,
  236. }
  237. export const DefaultUnitsDirectVolumeProps = paramDefaultValues(UnitsDirectVolumeParams)
  238. export type UnitsDirectVolumeProps = typeof DefaultUnitsDirectVolumeProps
  239. export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeProps> extends UnitsVisualBuilder<P, DirectVolume> { }
  240. export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builder: UnitsDirectVolumeVisualBuilder<P>): UnitsVisual<P> {
  241. return UnitsVisual({
  242. ...builder,
  243. createEmptyGeometry: DirectVolume.createEmpty,
  244. createRenderObject: createUnitsDirectVolumeRenderObject,
  245. setUpdateState: (state: VisualUpdateState, newProps: P, currentProps: P) => {
  246. builder.setUpdateState(state, newProps, currentProps)
  247. if (sizeChanged(currentProps, newProps)) state.createGeometry = true
  248. },
  249. updateValues: DirectVolume.updateValues
  250. })
  251. }