units-visual.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  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. // TODO refactor to make DRY
  7. import { Unit, Structure } from 'mol-model/structure';
  8. import { RepresentationProps, Visual } from '../';
  9. import { DefaultStructureMeshProps, VisualUpdateState, DefaultStructurePointsProps, DefaultStructureLinesProps } from '.';
  10. import { RuntimeContext } from 'mol-task';
  11. import { PickingId } from '../../geometry/picking';
  12. import { LocationIterator } from '../../util/location-iterator';
  13. import { Mesh } from '../../geometry/mesh/mesh';
  14. import { MarkerAction, applyMarkerAction, createMarkers } from '../../geometry/marker-data';
  15. import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
  16. import { MeshRenderObject, PointsRenderObject, LinesRenderObject } from 'mol-gl/render-object';
  17. import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject } from './visual/util/common';
  18. import { deepEqual, ValueCell, UUID } from 'mol-util';
  19. import { Interval } from 'mol-data/int';
  20. import { Points } from '../../geometry/points/points';
  21. import { updateRenderableState } from '../../geometry/geometry';
  22. import { createColors } from '../../geometry/color-data';
  23. import { createSizes } from '../../geometry/size-data';
  24. import { Lines } from '../../geometry/lines/lines';
  25. export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
  26. export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
  27. function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) {
  28. return (
  29. groupA.units.length === groupB.units.length &&
  30. Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0])
  31. )
  32. }
  33. // mesh
  34. export const DefaultUnitsMeshProps = {
  35. ...DefaultStructureMeshProps,
  36. unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
  37. }
  38. export type UnitsMeshProps = typeof DefaultUnitsMeshProps
  39. export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> {
  40. defaultProps: P
  41. createMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, mesh?: Mesh): Promise<Mesh>
  42. createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
  43. getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
  44. mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean
  45. setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
  46. }
  47. export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> {
  48. const { defaultProps, createMesh, createLocationIterator, getLoci, mark, setUpdateState } = builder
  49. const updateState = VisualUpdateState.create()
  50. let renderObject: MeshRenderObject | undefined
  51. let currentProps: P
  52. let mesh: Mesh
  53. let currentGroup: Unit.SymmetryGroup
  54. let currentStructure: Structure
  55. let locationIt: LocationIterator
  56. let currentConformationId: UUID
  57. async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
  58. currentProps = Object.assign({}, defaultProps, props)
  59. currentProps.colorTheme.structure = currentStructure
  60. currentGroup = group
  61. const unit = group.units[0]
  62. currentConformationId = Unit.conformationId(unit)
  63. mesh = currentProps.unitKinds.includes(unit.kind)
  64. ? await createMesh(ctx, unit, currentStructure, currentProps, mesh)
  65. : Mesh.createEmpty(mesh)
  66. // TODO create empty location iterator when not in unitKinds
  67. locationIt = createLocationIterator(group)
  68. renderObject = await createUnitsMeshRenderObject(ctx, group, mesh, locationIt, currentProps)
  69. }
  70. async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
  71. if (!renderObject) return
  72. const newProps = Object.assign({}, currentProps, props)
  73. newProps.colorTheme.structure = currentStructure
  74. const unit = currentGroup.units[0]
  75. locationIt.reset()
  76. VisualUpdateState.reset(updateState)
  77. setUpdateState(updateState, newProps, currentProps)
  78. const newConformationId = Unit.conformationId(unit)
  79. if (newConformationId !== currentConformationId) {
  80. currentConformationId = newConformationId
  81. updateState.createGeometry = true
  82. }
  83. if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
  84. if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) updateState.createGeometry = true
  85. if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) updateState.updateColor = true
  86. if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
  87. //
  88. if (updateState.updateTransform) {
  89. locationIt = createLocationIterator(currentGroup)
  90. const { instanceCount, groupCount } = locationIt
  91. createUnitsTransform(currentGroup, renderObject.values)
  92. createMarkers(instanceCount * groupCount, renderObject.values)
  93. updateState.updateColor = true
  94. }
  95. if (updateState.createGeometry) {
  96. mesh = newProps.unitKinds.includes(unit.kind)
  97. ? await createMesh(ctx, unit, currentStructure, newProps, mesh)
  98. : Mesh.createEmpty(mesh)
  99. ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
  100. updateState.updateColor = true
  101. }
  102. if (updateState.updateColor) {
  103. await createColors(ctx, locationIt, newProps.colorTheme, renderObject.values)
  104. }
  105. // TODO why do I need to cast here?
  106. Mesh.updateValues(renderObject.values, newProps as UnitsMeshProps)
  107. updateRenderableState(renderObject.state, newProps as UnitsMeshProps)
  108. currentProps = newProps
  109. }
  110. return {
  111. get renderObject () { return renderObject },
  112. async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
  113. if (structureGroup) currentStructure = structureGroup.structure
  114. const group = structureGroup ? structureGroup.group : undefined
  115. if (!group && !currentGroup) {
  116. throw new Error('missing group')
  117. } else if (group && (!currentGroup || !renderObject)) {
  118. // console.log('unit-visual first create')
  119. await create(ctx, group, props)
  120. } else if (group && group.hashCode !== currentGroup.hashCode) {
  121. // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
  122. await create(ctx, group, props)
  123. } else {
  124. // console.log('unit-visual update')
  125. if (group && !sameGroupConformation(group, currentGroup)) {
  126. // console.log('unit-visual new conformation')
  127. currentGroup = group
  128. }
  129. await update(ctx, props)
  130. }
  131. },
  132. getLoci(pickingId: PickingId) {
  133. return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
  134. },
  135. mark(loci: Loci, action: MarkerAction) {
  136. if (!renderObject) return false
  137. const { tMarker } = renderObject.values
  138. const { groupCount, instanceCount } = locationIt
  139. function apply(interval: Interval) {
  140. const start = Interval.start(interval)
  141. const end = Interval.end(interval)
  142. return applyMarkerAction(tMarker.ref.value.array, start, end, action)
  143. }
  144. let changed = false
  145. if (isEveryLoci(loci)) {
  146. changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
  147. } else {
  148. changed = mark(loci, currentGroup, apply)
  149. }
  150. if (changed) {
  151. ValueCell.update(tMarker, tMarker.ref.value)
  152. }
  153. return changed
  154. },
  155. destroy() {
  156. // TODO
  157. renderObject = undefined
  158. }
  159. }
  160. }
  161. // points
  162. export const DefaultUnitsPointsProps = {
  163. ...DefaultStructurePointsProps,
  164. unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
  165. }
  166. export type UnitsPointsProps = typeof DefaultUnitsPointsProps
  167. export interface UnitsPointVisualBuilder<P extends UnitsPointsProps> {
  168. defaultProps: P
  169. createPoints(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, points?: Points): Promise<Points>
  170. createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
  171. getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
  172. mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean
  173. setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
  174. }
  175. export function UnitsPointsVisual<P extends UnitsPointsProps>(builder: UnitsPointVisualBuilder<P>): UnitsVisual<P> {
  176. const { defaultProps, createPoints, createLocationIterator, getLoci, mark, setUpdateState } = builder
  177. const updateState = VisualUpdateState.create()
  178. let renderObject: PointsRenderObject | undefined
  179. let currentProps: P
  180. let points: Points
  181. let currentGroup: Unit.SymmetryGroup
  182. let currentStructure: Structure
  183. let locationIt: LocationIterator
  184. let currentConformationId: UUID
  185. async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
  186. currentProps = Object.assign({}, defaultProps, props)
  187. currentProps.colorTheme.structure = currentStructure
  188. currentGroup = group
  189. const unit = group.units[0]
  190. currentConformationId = Unit.conformationId(unit)
  191. points = currentProps.unitKinds.includes(unit.kind)
  192. ? await createPoints(ctx, unit, currentStructure, currentProps, points)
  193. : Points.createEmpty(points)
  194. // TODO create empty location iterator when not in unitKinds
  195. locationIt = createLocationIterator(group)
  196. renderObject = await createUnitsPointsRenderObject(ctx, group, points, locationIt, currentProps)
  197. }
  198. async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
  199. if (!renderObject) return
  200. const newProps = Object.assign({}, currentProps, props)
  201. newProps.colorTheme.structure = currentStructure
  202. const unit = currentGroup.units[0]
  203. locationIt.reset()
  204. VisualUpdateState.reset(updateState)
  205. setUpdateState(updateState, newProps, currentProps)
  206. const newConformationId = Unit.conformationId(unit)
  207. if (newConformationId !== currentConformationId) {
  208. currentConformationId = newConformationId
  209. updateState.createGeometry = true
  210. }
  211. if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
  212. if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) updateState.updateSize = true
  213. if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) updateState.updateColor = true
  214. if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
  215. //
  216. if (updateState.updateTransform) {
  217. locationIt = createLocationIterator(currentGroup)
  218. const { instanceCount, groupCount } = locationIt
  219. createUnitsTransform(currentGroup, renderObject.values)
  220. createMarkers(instanceCount * groupCount, renderObject.values)
  221. updateState.updateColor = true
  222. }
  223. if (updateState.createGeometry) {
  224. points = newProps.unitKinds.includes(unit.kind)
  225. ? await createPoints(ctx, unit, currentStructure, newProps, points)
  226. : Points.createEmpty(points)
  227. ValueCell.update(renderObject.values.drawCount, points.pointCount)
  228. updateState.updateColor = true
  229. }
  230. if (updateState.updateSize) {
  231. await createSizes(ctx, locationIt, newProps.sizeTheme, renderObject.values)
  232. }
  233. if (updateState.updateColor) {
  234. await createColors(ctx, locationIt, newProps.colorTheme, renderObject.values)
  235. }
  236. // TODO why do I need to cast here?
  237. Points.updateValues(renderObject.values, newProps as UnitsPointsProps)
  238. updateRenderableState(renderObject.state, newProps as UnitsPointsProps)
  239. currentProps = newProps
  240. }
  241. return {
  242. get renderObject () { return renderObject },
  243. async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
  244. if (structureGroup) currentStructure = structureGroup.structure
  245. const group = structureGroup ? structureGroup.group : undefined
  246. if (!group && !currentGroup) {
  247. throw new Error('missing group')
  248. } else if (group && (!currentGroup || !renderObject)) {
  249. // console.log('unit-visual first create')
  250. await create(ctx, group, props)
  251. } else if (group && group.hashCode !== currentGroup.hashCode) {
  252. // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
  253. await create(ctx, group, props)
  254. } else {
  255. // console.log('unit-visual update')
  256. if (group && !sameGroupConformation(group, currentGroup)) {
  257. // console.log('unit-visual new conformation')
  258. currentGroup = group
  259. }
  260. await update(ctx, props)
  261. }
  262. },
  263. getLoci(pickingId: PickingId) {
  264. return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
  265. },
  266. mark(loci: Loci, action: MarkerAction) {
  267. if (!renderObject) return false
  268. const { tMarker } = renderObject.values
  269. const { groupCount, instanceCount } = locationIt
  270. function apply(interval: Interval) {
  271. const start = Interval.start(interval)
  272. const end = Interval.end(interval)
  273. return applyMarkerAction(tMarker.ref.value.array, start, end, action)
  274. }
  275. let changed = false
  276. if (isEveryLoci(loci)) {
  277. changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
  278. } else {
  279. changed = mark(loci, currentGroup, apply)
  280. }
  281. if (changed) {
  282. ValueCell.update(tMarker, tMarker.ref.value)
  283. }
  284. return changed
  285. },
  286. destroy() {
  287. // TODO
  288. renderObject = undefined
  289. }
  290. }
  291. }
  292. // lines
  293. export const DefaultUnitsLinesProps = {
  294. ...DefaultStructureLinesProps,
  295. unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
  296. }
  297. export type UnitsLinesProps = typeof DefaultUnitsLinesProps
  298. export interface UnitsLinesVisualBuilder<P extends UnitsLinesProps> {
  299. defaultProps: P
  300. createLines(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, lines?: Lines): Promise<Lines>
  301. createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
  302. getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
  303. mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean
  304. setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
  305. }
  306. export function UnitsLinesVisual<P extends UnitsLinesProps>(builder: UnitsLinesVisualBuilder<P>): UnitsVisual<P> {
  307. const { defaultProps, createLines, createLocationIterator, getLoci, mark, setUpdateState } = builder
  308. const updateState = VisualUpdateState.create()
  309. let renderObject: LinesRenderObject | undefined
  310. let currentProps: P
  311. let lines: Lines
  312. let currentGroup: Unit.SymmetryGroup
  313. let currentStructure: Structure
  314. let locationIt: LocationIterator
  315. let currentConformationId: UUID
  316. async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
  317. currentProps = Object.assign({}, defaultProps, props)
  318. currentProps.colorTheme.structure = currentStructure
  319. currentGroup = group
  320. const unit = group.units[0]
  321. currentConformationId = Unit.conformationId(unit)
  322. lines = currentProps.unitKinds.includes(unit.kind)
  323. ? await createLines(ctx, unit, currentStructure, currentProps, lines)
  324. : Lines.createEmpty(lines)
  325. // TODO create empty location iterator when not in unitKinds
  326. locationIt = createLocationIterator(group)
  327. renderObject = await createUnitsLinesRenderObject(ctx, group, lines, locationIt, currentProps)
  328. }
  329. async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
  330. if (!renderObject) return
  331. const newProps = Object.assign({}, currentProps, props)
  332. newProps.colorTheme.structure = currentStructure
  333. const unit = currentGroup.units[0]
  334. locationIt.reset()
  335. VisualUpdateState.reset(updateState)
  336. setUpdateState(updateState, newProps, currentProps)
  337. const newConformationId = Unit.conformationId(unit)
  338. if (newConformationId !== currentConformationId) {
  339. currentConformationId = newConformationId
  340. updateState.createGeometry = true
  341. }
  342. if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
  343. if (!deepEqual(newProps.sizeTheme, currentProps.sizeTheme)) updateState.updateSize = true
  344. if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) updateState.updateColor = true
  345. if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
  346. //
  347. if (updateState.updateTransform) {
  348. locationIt = createLocationIterator(currentGroup)
  349. const { instanceCount, groupCount } = locationIt
  350. createUnitsTransform(currentGroup, renderObject.values)
  351. createMarkers(instanceCount * groupCount, renderObject.values)
  352. updateState.updateColor = true
  353. }
  354. if (updateState.createGeometry) {
  355. lines = newProps.unitKinds.includes(unit.kind)
  356. ? await createLines(ctx, unit, currentStructure, newProps, lines)
  357. : Lines.createEmpty(lines)
  358. ValueCell.update(renderObject.values.drawCount, lines.lineCount * 2 * 3)
  359. updateState.updateColor = true
  360. }
  361. if (updateState.updateSize) {
  362. await createSizes(ctx, locationIt, newProps.sizeTheme, renderObject.values)
  363. }
  364. if (updateState.updateColor) {
  365. await createColors(ctx, locationIt, newProps.colorTheme, renderObject.values)
  366. }
  367. // TODO why do I need to cast here?
  368. Lines.updateValues(renderObject.values, newProps as UnitsLinesProps)
  369. updateRenderableState(renderObject.state, newProps as UnitsLinesProps)
  370. currentProps = newProps
  371. }
  372. return {
  373. get renderObject () { return renderObject },
  374. async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
  375. if (structureGroup) currentStructure = structureGroup.structure
  376. const group = structureGroup ? structureGroup.group : undefined
  377. if (!group && !currentGroup) {
  378. throw new Error('missing group')
  379. } else if (group && (!currentGroup || !renderObject)) {
  380. // console.log('unit-visual first create')
  381. await create(ctx, group, props)
  382. } else if (group && group.hashCode !== currentGroup.hashCode) {
  383. // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
  384. await create(ctx, group, props)
  385. } else {
  386. // console.log('unit-visual update')
  387. if (group && !sameGroupConformation(group, currentGroup)) {
  388. // console.log('unit-visual new conformation')
  389. currentGroup = group
  390. }
  391. await update(ctx, props)
  392. }
  393. },
  394. getLoci(pickingId: PickingId) {
  395. return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
  396. },
  397. mark(loci: Loci, action: MarkerAction) {
  398. if (!renderObject) return false
  399. const { tMarker } = renderObject.values
  400. const { groupCount, instanceCount } = locationIt
  401. function apply(interval: Interval) {
  402. const start = Interval.start(interval)
  403. const end = Interval.end(interval)
  404. return applyMarkerAction(tMarker.ref.value.array, start, end, action)
  405. }
  406. let changed = false
  407. if (isEveryLoci(loci)) {
  408. changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
  409. } else {
  410. changed = mark(loci, currentGroup, apply)
  411. }
  412. if (changed) {
  413. ValueCell.update(tMarker, tMarker.ref.value)
  414. }
  415. return changed
  416. },
  417. destroy() {
  418. // TODO
  419. renderObject = undefined
  420. }
  421. }
  422. }