units-visual.ts 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  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 { VisualUpdateState, StructureMeshParams, StructurePointsParams, StructureLinesParams, StructureDirectVolumeParams, StructureProps } 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, DirectVolume2dRenderObject, DirectVolume3dRenderObject } from 'mol-gl/render-object';
  17. import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject, createUnitsDirectVolumeRenderObject } 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, Geometry } from '../../geometry/geometry';
  22. import { createColors, ColorProps } from '../../geometry/color-data';
  23. import { createSizes, SizeProps } from '../../geometry/size-data';
  24. import { Lines } from '../../geometry/lines/lines';
  25. import { MultiSelectParam, paramDefaultValues } from 'mol-view/parameter';
  26. import { DirectVolume2d, DirectVolume3d } from '../../geometry/direct-volume/direct-volume';
  27. export const UnitKindInfo = {
  28. 'atomic': {},
  29. 'spheres': {},
  30. 'gaussians': {},
  31. }
  32. export type UnitKind = keyof typeof UnitKindInfo
  33. export const UnitKindNames = Object.keys(UnitKindInfo)
  34. export const UnitKindOptions = UnitKindNames.map(n => [n, n] as [UnitKind, string])
  35. export type StructureGroup = { structure: Structure, group: Unit.SymmetryGroup }
  36. export interface UnitsVisual<P extends RepresentationProps = {}> extends Visual<StructureGroup, P> { }
  37. function sameGroupConformation(groupA: Unit.SymmetryGroup, groupB: Unit.SymmetryGroup) {
  38. return (
  39. groupA.units.length === groupB.units.length &&
  40. Unit.conformationId(groupA.units[0]) === Unit.conformationId(groupB.units[0])
  41. )
  42. }
  43. function includesUnitKind(unitKinds: UnitKind[], unit: Unit) {
  44. for (let i = 0, il = unitKinds.length; i < il; ++i) {
  45. if (Unit.isAtomic(unit) && unitKinds[i] === 'atomic') return true
  46. if (Unit.isSpheres(unit) && unitKinds[i] === 'spheres') return true
  47. if (Unit.isGaussians(unit) && unitKinds[i] === 'gaussians') return true
  48. }
  49. return false
  50. }
  51. function sizeChanged(oldProps: SizeProps, newProps: SizeProps) {
  52. return (
  53. oldProps.sizeTheme !== newProps.sizeTheme ||
  54. oldProps.sizeValue !== newProps.sizeValue ||
  55. oldProps.sizeFactor !== newProps.sizeFactor
  56. )
  57. }
  58. function colorChanged(oldProps: ColorProps, newProps: ColorProps) {
  59. return (
  60. oldProps.colorTheme !== newProps.colorTheme ||
  61. oldProps.colorValue !== newProps.colorValue
  62. )
  63. }
  64. const UnitsParams = {
  65. unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', ['atomic', 'spheres'], UnitKindOptions),
  66. }
  67. interface UnitsVisualBuilder<P extends StructureProps, G extends Geometry> {
  68. defaultProps: P
  69. createGeometry(ctx: RuntimeContext, unit: Unit, structure: Structure, props: P, geometry?: G): Promise<G>
  70. createLocationIterator(group: Unit.SymmetryGroup): LocationIterator
  71. getLoci(pickingId: PickingId, group: Unit.SymmetryGroup, id: number): Loci
  72. mark(loci: Loci, group: Unit.SymmetryGroup, apply: (interval: Interval) => boolean): boolean
  73. setUpdateState(state: VisualUpdateState, newProps: P, currentProps: P): void
  74. }
  75. // mesh
  76. export const UnitsMeshParams = {
  77. ...StructureMeshParams,
  78. ...UnitsParams,
  79. }
  80. export const DefaultUnitsMeshProps = paramDefaultValues(UnitsMeshParams)
  81. export type UnitsMeshProps = typeof DefaultUnitsMeshProps
  82. export interface UnitsMeshVisualBuilder<P extends UnitsMeshProps> extends UnitsVisualBuilder<P, Mesh> { }
  83. export function UnitsMeshVisual<P extends UnitsMeshProps>(builder: UnitsMeshVisualBuilder<P>): UnitsVisual<P> {
  84. const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
  85. const updateState = VisualUpdateState.create()
  86. let renderObject: MeshRenderObject | undefined
  87. let currentProps: P
  88. let mesh: Mesh
  89. let currentGroup: Unit.SymmetryGroup
  90. let currentStructure: Structure
  91. let locationIt: LocationIterator
  92. let currentConformationId: UUID
  93. async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
  94. currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure })
  95. currentGroup = group
  96. const unit = group.units[0]
  97. currentConformationId = Unit.conformationId(unit)
  98. mesh = includesUnitKind(currentProps.unitKinds, unit)
  99. ? await createGeometry(ctx, unit, currentStructure, currentProps, mesh)
  100. : Mesh.createEmpty(mesh)
  101. // TODO create empty location iterator when not in unitKinds
  102. locationIt = createLocationIterator(group)
  103. renderObject = await createUnitsMeshRenderObject(ctx, group, mesh, locationIt, currentProps)
  104. }
  105. async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
  106. if (!renderObject) return
  107. const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
  108. const unit = currentGroup.units[0]
  109. locationIt.reset()
  110. VisualUpdateState.reset(updateState)
  111. setUpdateState(updateState, newProps, currentProps)
  112. const newConformationId = Unit.conformationId(unit)
  113. if (newConformationId !== currentConformationId) {
  114. currentConformationId = newConformationId
  115. updateState.createGeometry = true
  116. }
  117. if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
  118. if (sizeChanged(currentProps, newProps)) updateState.createGeometry = true
  119. if (colorChanged(currentProps, newProps)) updateState.updateColor = true
  120. if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
  121. //
  122. if (updateState.updateTransform) {
  123. locationIt = createLocationIterator(currentGroup)
  124. const { instanceCount, groupCount } = locationIt
  125. createUnitsTransform(currentGroup, renderObject.values)
  126. createMarkers(instanceCount * groupCount, renderObject.values)
  127. updateState.updateColor = true
  128. }
  129. if (updateState.createGeometry) {
  130. mesh = includesUnitKind(newProps.unitKinds, unit)
  131. ? await createGeometry(ctx, unit, currentStructure, newProps, mesh)
  132. : Mesh.createEmpty(mesh)
  133. ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3)
  134. updateState.updateColor = true
  135. }
  136. if (updateState.updateColor) {
  137. await createColors(ctx, locationIt, newProps, renderObject.values)
  138. }
  139. // TODO why do I need to cast here?
  140. Mesh.updateValues(renderObject.values, newProps as UnitsMeshProps)
  141. updateRenderableState(renderObject.state, newProps as UnitsMeshProps)
  142. currentProps = newProps
  143. }
  144. return {
  145. get renderObject () { return renderObject },
  146. async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
  147. if (structureGroup) currentStructure = structureGroup.structure
  148. const group = structureGroup ? structureGroup.group : undefined
  149. if (!group && !currentGroup) {
  150. throw new Error('missing group')
  151. } else if (group && (!currentGroup || !renderObject)) {
  152. // console.log('unit-visual first create')
  153. await create(ctx, group, props)
  154. } else if (group && group.hashCode !== currentGroup.hashCode) {
  155. // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
  156. await create(ctx, group, props)
  157. } else {
  158. // console.log('unit-visual update')
  159. if (group && !sameGroupConformation(group, currentGroup)) {
  160. // console.log('unit-visual new conformation')
  161. currentGroup = group
  162. }
  163. await update(ctx, props)
  164. }
  165. },
  166. getLoci(pickingId: PickingId) {
  167. return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
  168. },
  169. mark(loci: Loci, action: MarkerAction) {
  170. if (!renderObject) return false
  171. const { tMarker } = renderObject.values
  172. const { groupCount, instanceCount } = locationIt
  173. function apply(interval: Interval) {
  174. const start = Interval.start(interval)
  175. const end = Interval.end(interval)
  176. return applyMarkerAction(tMarker.ref.value.array, start, end, action)
  177. }
  178. let changed = false
  179. if (isEveryLoci(loci)) {
  180. changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
  181. } else {
  182. changed = mark(loci, currentGroup, apply)
  183. }
  184. if (changed) {
  185. ValueCell.update(tMarker, tMarker.ref.value)
  186. }
  187. return changed
  188. },
  189. destroy() {
  190. // TODO
  191. renderObject = undefined
  192. }
  193. }
  194. }
  195. // points
  196. export const UnitsPointsParams = {
  197. ...StructurePointsParams,
  198. ...UnitsParams,
  199. }
  200. export const DefaultUnitsPointsProps = paramDefaultValues(UnitsPointsParams)
  201. export type UnitsPointsProps = typeof DefaultUnitsPointsProps
  202. export interface UnitsPointVisualBuilder<P extends UnitsPointsProps> extends UnitsVisualBuilder<P, Points> { }
  203. export function UnitsPointsVisual<P extends UnitsPointsProps>(builder: UnitsPointVisualBuilder<P>): UnitsVisual<P> {
  204. const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
  205. const updateState = VisualUpdateState.create()
  206. let renderObject: PointsRenderObject | undefined
  207. let currentProps: P
  208. let points: Points
  209. let currentGroup: Unit.SymmetryGroup
  210. let currentStructure: Structure
  211. let locationIt: LocationIterator
  212. let currentConformationId: UUID
  213. async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
  214. currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure })
  215. currentGroup = group
  216. const unit = group.units[0]
  217. currentConformationId = Unit.conformationId(unit)
  218. points = includesUnitKind(currentProps.unitKinds, unit)
  219. ? await createGeometry(ctx, unit, currentStructure, currentProps, points)
  220. : Points.createEmpty(points)
  221. // TODO create empty location iterator when not in unitKinds
  222. locationIt = createLocationIterator(group)
  223. renderObject = await createUnitsPointsRenderObject(ctx, group, points, locationIt, currentProps)
  224. }
  225. async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
  226. if (!renderObject) return
  227. const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
  228. const unit = currentGroup.units[0]
  229. locationIt.reset()
  230. VisualUpdateState.reset(updateState)
  231. setUpdateState(updateState, newProps, currentProps)
  232. const newConformationId = Unit.conformationId(unit)
  233. if (newConformationId !== currentConformationId) {
  234. currentConformationId = newConformationId
  235. updateState.createGeometry = true
  236. }
  237. if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
  238. if (sizeChanged(currentProps, newProps)) updateState.updateSize = true
  239. if (colorChanged(currentProps, newProps)) updateState.updateColor = true
  240. if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
  241. //
  242. if (updateState.updateTransform) {
  243. locationIt = createLocationIterator(currentGroup)
  244. const { instanceCount, groupCount } = locationIt
  245. createUnitsTransform(currentGroup, renderObject.values)
  246. createMarkers(instanceCount * groupCount, renderObject.values)
  247. updateState.updateColor = true
  248. }
  249. if (updateState.createGeometry) {
  250. points = includesUnitKind(newProps.unitKinds, unit)
  251. ? await createGeometry(ctx, unit, currentStructure, newProps, points)
  252. : Points.createEmpty(points)
  253. ValueCell.update(renderObject.values.drawCount, points.pointCount)
  254. updateState.updateColor = true
  255. }
  256. if (updateState.updateSize) {
  257. await createSizes(ctx, locationIt, newProps, renderObject.values)
  258. }
  259. if (updateState.updateColor) {
  260. await createColors(ctx, locationIt, newProps, renderObject.values)
  261. }
  262. // TODO why do I need to cast here?
  263. Points.updateValues(renderObject.values, newProps as UnitsPointsProps)
  264. updateRenderableState(renderObject.state, newProps as UnitsPointsProps)
  265. currentProps = newProps
  266. }
  267. return {
  268. get renderObject () { return renderObject },
  269. async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
  270. if (structureGroup) currentStructure = structureGroup.structure
  271. const group = structureGroup ? structureGroup.group : undefined
  272. if (!group && !currentGroup) {
  273. throw new Error('missing group')
  274. } else if (group && (!currentGroup || !renderObject)) {
  275. // console.log('unit-visual first create')
  276. await create(ctx, group, props)
  277. } else if (group && group.hashCode !== currentGroup.hashCode) {
  278. // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
  279. await create(ctx, group, props)
  280. } else {
  281. // console.log('unit-visual update')
  282. if (group && !sameGroupConformation(group, currentGroup)) {
  283. // console.log('unit-visual new conformation')
  284. currentGroup = group
  285. }
  286. await update(ctx, props)
  287. }
  288. },
  289. getLoci(pickingId: PickingId) {
  290. return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
  291. },
  292. mark(loci: Loci, action: MarkerAction) {
  293. if (!renderObject) return false
  294. const { tMarker } = renderObject.values
  295. const { groupCount, instanceCount } = locationIt
  296. function apply(interval: Interval) {
  297. const start = Interval.start(interval)
  298. const end = Interval.end(interval)
  299. return applyMarkerAction(tMarker.ref.value.array, start, end, action)
  300. }
  301. let changed = false
  302. if (isEveryLoci(loci)) {
  303. changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
  304. } else {
  305. changed = mark(loci, currentGroup, apply)
  306. }
  307. if (changed) {
  308. ValueCell.update(tMarker, tMarker.ref.value)
  309. }
  310. return changed
  311. },
  312. destroy() {
  313. // TODO
  314. renderObject = undefined
  315. }
  316. }
  317. }
  318. // lines
  319. export const UnitsLinesParams = {
  320. ...StructureLinesParams,
  321. ...UnitsParams,
  322. }
  323. export const DefaultUnitsLinesProps = paramDefaultValues(UnitsLinesParams)
  324. export type UnitsLinesProps = typeof DefaultUnitsLinesProps
  325. export interface UnitsLinesVisualBuilder<P extends UnitsLinesProps> extends UnitsVisualBuilder<P, Lines> { }
  326. export function UnitsLinesVisual<P extends UnitsLinesProps>(builder: UnitsLinesVisualBuilder<P>): UnitsVisual<P> {
  327. const { defaultProps, createGeometry, createLocationIterator, getLoci, mark, setUpdateState } = builder
  328. const updateState = VisualUpdateState.create()
  329. let renderObject: LinesRenderObject | undefined
  330. let currentProps: P
  331. let lines: Lines
  332. let currentGroup: Unit.SymmetryGroup
  333. let currentStructure: Structure
  334. let locationIt: LocationIterator
  335. let currentConformationId: UUID
  336. async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
  337. currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure })
  338. currentGroup = group
  339. const unit = group.units[0]
  340. currentConformationId = Unit.conformationId(unit)
  341. lines = includesUnitKind(currentProps.unitKinds, unit)
  342. ? await createGeometry(ctx, unit, currentStructure, currentProps, lines)
  343. : Lines.createEmpty(lines)
  344. // TODO create empty location iterator when not in unitKinds
  345. locationIt = createLocationIterator(group)
  346. renderObject = await createUnitsLinesRenderObject(ctx, group, lines, locationIt, currentProps)
  347. }
  348. async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
  349. if (!renderObject) return
  350. const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
  351. const unit = currentGroup.units[0]
  352. locationIt.reset()
  353. VisualUpdateState.reset(updateState)
  354. setUpdateState(updateState, newProps, currentProps)
  355. const newConformationId = Unit.conformationId(unit)
  356. if (newConformationId !== currentConformationId) {
  357. currentConformationId = newConformationId
  358. updateState.createGeometry = true
  359. }
  360. if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
  361. if (sizeChanged(currentProps, newProps)) updateState.updateSize = true
  362. if (colorChanged(currentProps, newProps)) updateState.updateColor = true
  363. if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
  364. //
  365. if (updateState.updateTransform) {
  366. locationIt = createLocationIterator(currentGroup)
  367. const { instanceCount, groupCount } = locationIt
  368. createUnitsTransform(currentGroup, renderObject.values)
  369. createMarkers(instanceCount * groupCount, renderObject.values)
  370. updateState.updateColor = true
  371. }
  372. if (updateState.createGeometry) {
  373. lines = includesUnitKind(newProps.unitKinds, unit)
  374. ? await createGeometry(ctx, unit, currentStructure, newProps, lines)
  375. : Lines.createEmpty(lines)
  376. ValueCell.update(renderObject.values.drawCount, lines.lineCount * 2 * 3)
  377. updateState.updateColor = true
  378. }
  379. if (updateState.updateSize) {
  380. await createSizes(ctx, locationIt, newProps, renderObject.values)
  381. }
  382. if (updateState.updateColor) {
  383. await createColors(ctx, locationIt, newProps, renderObject.values)
  384. }
  385. // TODO why do I need to cast here?
  386. Lines.updateValues(renderObject.values, newProps as UnitsLinesProps)
  387. updateRenderableState(renderObject.state, newProps as UnitsLinesProps)
  388. currentProps = newProps
  389. }
  390. return {
  391. get renderObject () { return renderObject },
  392. async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
  393. if (structureGroup) currentStructure = structureGroup.structure
  394. const group = structureGroup ? structureGroup.group : undefined
  395. if (!group && !currentGroup) {
  396. throw new Error('missing group')
  397. } else if (group && (!currentGroup || !renderObject)) {
  398. // console.log('unit-visual first create')
  399. await create(ctx, group, props)
  400. } else if (group && group.hashCode !== currentGroup.hashCode) {
  401. // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
  402. await create(ctx, group, props)
  403. } else {
  404. // console.log('unit-visual update')
  405. if (group && !sameGroupConformation(group, currentGroup)) {
  406. // console.log('unit-visual new conformation')
  407. currentGroup = group
  408. }
  409. await update(ctx, props)
  410. }
  411. },
  412. getLoci(pickingId: PickingId) {
  413. return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
  414. },
  415. mark(loci: Loci, action: MarkerAction) {
  416. if (!renderObject) return false
  417. const { tMarker } = renderObject.values
  418. const { groupCount, instanceCount } = locationIt
  419. function apply(interval: Interval) {
  420. const start = Interval.start(interval)
  421. const end = Interval.end(interval)
  422. return applyMarkerAction(tMarker.ref.value.array, start, end, action)
  423. }
  424. let changed = false
  425. if (isEveryLoci(loci)) {
  426. changed = apply(Interval.ofBounds(0, groupCount * instanceCount))
  427. } else {
  428. changed = mark(loci, currentGroup, apply)
  429. }
  430. if (changed) {
  431. ValueCell.update(tMarker, tMarker.ref.value)
  432. }
  433. return changed
  434. },
  435. destroy() {
  436. // TODO
  437. renderObject = undefined
  438. }
  439. }
  440. }
  441. // direct-volume
  442. export const UnitsDirectVolumeParams = {
  443. ...StructureDirectVolumeParams,
  444. ...UnitsParams,
  445. }
  446. export const DefaultUnitsDirectVolumeProps = paramDefaultValues(UnitsDirectVolumeParams)
  447. export type UnitsDirectVolumeProps = typeof DefaultUnitsDirectVolumeProps
  448. export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeProps> extends UnitsVisualBuilder<P, DirectVolume2d | DirectVolume3d> { }
  449. export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builder: UnitsDirectVolumeVisualBuilder<P>): UnitsVisual<P> {
  450. const { defaultProps, createGeometry, createLocationIterator, getLoci, setUpdateState } = builder
  451. const updateState = VisualUpdateState.create()
  452. let renderObject: DirectVolume2dRenderObject | DirectVolume3dRenderObject | undefined
  453. let currentProps: P
  454. let directVolume: DirectVolume2d | DirectVolume3d
  455. let currentGroup: Unit.SymmetryGroup
  456. let currentStructure: Structure
  457. let locationIt: LocationIterator
  458. let currentConformationId: UUID
  459. async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
  460. const { webgl } = props
  461. if (webgl === undefined) throw new Error('UnitsDirectVolumeVisual requires `webgl` in props')
  462. currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure })
  463. currentGroup = group
  464. const unit = group.units[0]
  465. currentConformationId = Unit.conformationId(unit)
  466. directVolume = includesUnitKind(currentProps.unitKinds, unit)
  467. ? await createGeometry(ctx, unit, currentStructure, currentProps, directVolume)
  468. : (webgl.isWebGL2 ?
  469. DirectVolume2d.createEmpty(directVolume as DirectVolume2d) :
  470. DirectVolume3d.createEmpty(directVolume as DirectVolume3d))
  471. console.log('directVolume', directVolume)
  472. // TODO create empty location iterator when not in unitKinds
  473. locationIt = createLocationIterator(group)
  474. renderObject = await createUnitsDirectVolumeRenderObject(ctx, group, directVolume, locationIt, currentProps)
  475. }
  476. async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
  477. const { webgl } = props
  478. if (webgl === undefined) throw new Error('UnitsDirectVolumeVisual requires `webgl` in props')
  479. if (!renderObject) return
  480. const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
  481. const unit = currentGroup.units[0]
  482. locationIt.reset()
  483. VisualUpdateState.reset(updateState)
  484. setUpdateState(updateState, newProps, currentProps)
  485. const newConformationId = Unit.conformationId(unit)
  486. if (newConformationId !== currentConformationId) {
  487. currentConformationId = newConformationId
  488. updateState.createGeometry = true
  489. }
  490. if (currentGroup.units.length !== locationIt.instanceCount) updateState.updateTransform = true
  491. if (sizeChanged(currentProps, newProps)) updateState.createGeometry = true
  492. if (colorChanged(currentProps, newProps)) updateState.updateColor = true
  493. if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) updateState.createGeometry = true
  494. //
  495. // if (updateState.updateTransform) {
  496. // locationIt = createLocationIterator(currentGroup)
  497. // const { instanceCount, groupCount } = locationIt
  498. // createUnitsTransform(currentGroup, renderObject.values)
  499. // createMarkers(instanceCount * groupCount, renderObject.values)
  500. // updateState.updateColor = true
  501. // }
  502. if (updateState.createGeometry) {
  503. directVolume = includesUnitKind(newProps.unitKinds, unit)
  504. ? await createGeometry(ctx, unit, currentStructure, newProps, directVolume)
  505. : (webgl.isWebGL2 ?
  506. DirectVolume2d.createEmpty(directVolume as DirectVolume2d) :
  507. DirectVolume3d.createEmpty(directVolume as DirectVolume3d))
  508. updateState.updateColor = true
  509. }
  510. // if (updateState.updateColor) {
  511. // await createColors(ctx, locationIt, newProps, renderObject.values)
  512. // }
  513. if (renderObject.type === 'direct-volume-2d') {
  514. DirectVolume2d.updateValues(renderObject.values, newProps)
  515. } else {
  516. DirectVolume3d.updateValues(renderObject.values, newProps)
  517. }
  518. updateRenderableState(renderObject.state, newProps)
  519. currentProps = newProps
  520. }
  521. return {
  522. get renderObject () { return renderObject },
  523. async createOrUpdate(ctx: RuntimeContext, props: Partial<P> = {}, structureGroup?: StructureGroup) {
  524. if (structureGroup) currentStructure = structureGroup.structure
  525. const group = structureGroup ? structureGroup.group : undefined
  526. if (!group && !currentGroup) {
  527. throw new Error('missing group')
  528. } else if (group && (!currentGroup || !renderObject)) {
  529. // console.log('unit-visual first create')
  530. await create(ctx, group, props)
  531. } else if (group && group.hashCode !== currentGroup.hashCode) {
  532. // console.log('unit-visual group.hashCode !== currentGroup.hashCode')
  533. await create(ctx, group, props)
  534. } else {
  535. // console.log('unit-visual update')
  536. if (group && !sameGroupConformation(group, currentGroup)) {
  537. // console.log('unit-visual new conformation')
  538. currentGroup = group
  539. }
  540. await update(ctx, props)
  541. }
  542. },
  543. getLoci(pickingId: PickingId) {
  544. return renderObject ? getLoci(pickingId, currentGroup, renderObject.id) : EmptyLoci
  545. },
  546. mark(loci: Loci, action: MarkerAction) {
  547. // TODO
  548. return false
  549. },
  550. destroy() {
  551. // TODO
  552. renderObject = undefined
  553. }
  554. }
  555. }