spacefill.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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 { ValueCell } from 'mol-util/value-cell'
  8. import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
  9. import { Unit, Element, Queries } from 'mol-model/structure';
  10. import { UnitsRepresentation, DefaultStructureProps } from './index';
  11. import { Task } from 'mol-task'
  12. import { createTransforms, createColors, createSphereMesh, applyElementFlags } from './utils';
  13. import VertexMap from '../../shape/vertex-map';
  14. import { deepEqual, defaults } from 'mol-util';
  15. import { fillSerial } from 'mol-gl/renderable/util';
  16. import { RenderableState, MeshValues } from 'mol-gl/renderable';
  17. import { getMeshData } from '../../util/mesh-data';
  18. import { Mesh } from '../../shape/mesh';
  19. import { PickingId } from '../../util/picking';
  20. import { SortedArray } from 'mol-data/int';
  21. import { createFlags, FlagAction } from '../../util/flag-data';
  22. import { Loci } from 'mol-model/loci';
  23. function createSpacefillMesh(unit: Unit, detail: number, mesh?: Mesh) {
  24. let radius: Element.Property<number>
  25. if (Unit.isAtomic(unit)) {
  26. radius = Queries.props.atom.vdw_radius
  27. } else if (Unit.isSpheres(unit)) {
  28. radius = Queries.props.coarse.sphere_radius
  29. } else {
  30. console.warn('Unsupported unit type')
  31. return Task.constant('Empty mesh', Mesh.createEmpty(mesh))
  32. }
  33. return createSphereMesh(unit, (l) => radius(l) * 1.0, detail, mesh)
  34. }
  35. export const DefaultSpacefillProps = {
  36. ...DefaultStructureProps,
  37. flipSided: false,
  38. flatShaded: false,
  39. detail: 0,
  40. }
  41. export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
  42. export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
  43. const renderObjects: RenderObject[] = []
  44. let spheres: MeshRenderObject
  45. let currentProps: typeof DefaultSpacefillProps
  46. let mesh: Mesh
  47. let currentGroup: Unit.SymmetryGroup
  48. let vertexMap: VertexMap
  49. return {
  50. renderObjects,
  51. create(group: Unit.SymmetryGroup, props: SpacefillProps = {}) {
  52. currentProps = Object.assign({}, DefaultSpacefillProps, props)
  53. return Task.create('Spacefill.create', async ctx => {
  54. renderObjects.length = 0 // clear
  55. currentGroup = group
  56. const { detail, colorTheme } = { ...DefaultSpacefillProps, ...props }
  57. mesh = await createSpacefillMesh(group.units[0], detail).runAsChild(ctx, 'Computing spacefill mesh')
  58. // console.log(mesh)
  59. vertexMap = VertexMap.fromMesh(mesh)
  60. await ctx.update('Computing spacefill transforms');
  61. const transforms = createTransforms(group)
  62. await ctx.update('Computing spacefill colors');
  63. const color = createColors(group, vertexMap, colorTheme)
  64. await ctx.update('Computing spacefill flags');
  65. const flag = createFlags(group)
  66. const instanceCount = group.units.length
  67. const values: MeshValues = {
  68. ...getMeshData(mesh),
  69. aTransform: transforms,
  70. aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
  71. ...color,
  72. ...flag,
  73. uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
  74. uInstanceCount: ValueCell.create(instanceCount),
  75. uElementCount: ValueCell.create(group.elements.length),
  76. elements: mesh.indexBuffer,
  77. drawCount: ValueCell.create(mesh.triangleCount * 3),
  78. instanceCount: ValueCell.create(instanceCount),
  79. dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)),
  80. dFlatShaded: ValueCell.create(defaults(props.flatShaded, false)),
  81. dFlipSided: ValueCell.create(defaults(props.flipSided, false)),
  82. }
  83. const state: RenderableState = {
  84. depthMask: defaults(props.depthMask, true),
  85. visible: defaults(props.visible, true)
  86. }
  87. spheres = createMeshRenderObject(values, state)
  88. renderObjects.push(spheres)
  89. })
  90. },
  91. update(props: SpacefillProps) {
  92. const newProps = Object.assign({}, currentProps, props)
  93. return Task.create('Spacefill.update', async ctx => {
  94. if (!spheres) return false
  95. let updateColor = false
  96. if (newProps.detail !== currentProps.detail) {
  97. mesh = await createSpacefillMesh(currentGroup.units[0], newProps.detail, mesh).runAsChild(ctx, 'Computing spacefill mesh')
  98. ValueCell.update(spheres.values.drawCount, mesh.triangleCount * 3)
  99. // TODO update in-place
  100. vertexMap = VertexMap.fromMesh(mesh)
  101. updateColor = true
  102. }
  103. if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
  104. updateColor = true
  105. }
  106. if (updateColor) {
  107. await ctx.update('Computing spacefill colors');
  108. createColors(currentGroup, vertexMap, newProps.colorTheme, spheres.values)
  109. }
  110. ValueCell.updateIfChanged(spheres.values.uAlpha, newProps.alpha)
  111. ValueCell.updateIfChanged(spheres.values.dDoubleSided, newProps.doubleSided)
  112. ValueCell.updateIfChanged(spheres.values.dFlipSided, newProps.flipSided)
  113. ValueCell.updateIfChanged(spheres.values.dFlatShaded, newProps.flatShaded)
  114. spheres.state.visible = newProps.visible
  115. spheres.state.depthMask = newProps.depthMask
  116. currentProps = newProps
  117. return true
  118. })
  119. },
  120. getLoci(pickingId: PickingId) {
  121. const { objectId, instanceId, elementId } = pickingId
  122. if (spheres.id === objectId) {
  123. const unit = currentGroup.units[instanceId]
  124. const indices = SortedArray.ofSingleton(elementId);
  125. return Element.Loci([{ unit, indices }])
  126. }
  127. return null
  128. },
  129. applyFlags(loci: Loci, action: FlagAction) {
  130. applyElementFlags(spheres.values.tFlag, currentGroup, loci, action)
  131. }
  132. }
  133. }