bond.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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. // TODO multiple cylinders for higher bond orders
  8. import { ValueCell } from 'mol-util/value-cell'
  9. import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
  10. import { Unit, Element, Link } from 'mol-model/structure';
  11. import { UnitsRepresentation, DefaultStructureProps } from './index';
  12. import { Task } from 'mol-task'
  13. import { createTransforms } from './utils';
  14. import { fillSerial } from 'mol-gl/renderable/util';
  15. import { RenderableState, MeshValues } from 'mol-gl/renderable';
  16. import { getMeshData } from '../../util/mesh-data';
  17. import { Mesh } from '../../shape/mesh';
  18. import { PickingId } from '../../util/picking';
  19. import { MeshBuilder } from '../../shape/mesh-builder';
  20. import { Vec3, Mat4 } from 'mol-math/linear-algebra';
  21. import { createUniformColor } from '../../util/color-data';
  22. import { defaults } from 'mol-util';
  23. import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
  24. import { MarkerAction, applyMarkerAction, createMarkers } from '../../util/marker-data';
  25. function createBondMesh(unit: Unit, mesh?: Mesh) {
  26. return Task.create('Cylinder mesh', async ctx => {
  27. if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
  28. const elements = unit.elements;
  29. const bonds = unit.links
  30. const { edgeCount, a, b } = bonds
  31. if (!edgeCount) return Mesh.createEmpty(mesh)
  32. // TODO calculate vertextCount properly
  33. const vertexCount = 32 * edgeCount
  34. const meshBuilder = MeshBuilder.create(vertexCount, vertexCount / 2, mesh)
  35. const va = Vec3.zero()
  36. const vb = Vec3.zero()
  37. const vt = Vec3.zero()
  38. const m = Mat4.identity()
  39. const { x, y, z } = unit.conformation
  40. const l = Element.Location()
  41. l.unit = unit
  42. for (let edgeIndex = 0, _eI = edgeCount * 2; edgeIndex < _eI; ++edgeIndex) {
  43. const aI = elements[a[edgeIndex]], bI = elements[b[edgeIndex]];
  44. // each edge is included twice because of the "adjacency list" structure
  45. // keep only the 1st occurence.
  46. if (aI >= bI) continue;
  47. va[0] = x(aI); va[1] = y(aI); va[2] = z(aI)
  48. vb[0] = x(bI); vb[1] = y(bI); vb[2] = z(bI)
  49. Vec3.scale(vt, Vec3.add(vt, va, vb), 0.5)
  50. Vec3.makeRotation(m, Vec3.create(0, 1, 0), Vec3.sub(vb, vb, va))
  51. Mat4.setTranslation(m, vt)
  52. meshBuilder.setId(edgeIndex)
  53. meshBuilder.addCylinder(m, { radiusTop: 0.2, radiusBottom: 0.2 })
  54. if (edgeIndex % 10000 === 0 && ctx.shouldUpdate) {
  55. await ctx.update({ message: 'Cylinder mesh', current: edgeIndex, max: edgeCount });
  56. }
  57. }
  58. return meshBuilder.getMesh()
  59. })
  60. }
  61. export const DefaultBondProps = {
  62. ...DefaultStructureProps,
  63. flipSided: false,
  64. flatShaded: false,
  65. }
  66. export type BondProps = Partial<typeof DefaultBondProps>
  67. export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps> {
  68. const renderObjects: RenderObject[] = []
  69. let cylinders: MeshRenderObject
  70. let currentProps: typeof DefaultBondProps
  71. let mesh: Mesh
  72. let currentGroup: Unit.SymmetryGroup
  73. // let vertexMap: VertexMap
  74. return {
  75. renderObjects,
  76. create(group: Unit.SymmetryGroup, props: BondProps = {}) {
  77. currentProps = Object.assign({}, DefaultBondProps, props)
  78. return Task.create('Bond.create', async ctx => {
  79. renderObjects.length = 0 // clear
  80. currentGroup = group
  81. const unit = group.units[0]
  82. const elementCount = Unit.isAtomic(unit) ? unit.links.edgeCount * 2 : 0
  83. const instanceCount = group.units.length
  84. mesh = await createBondMesh(unit).runAsChild(ctx, 'Computing bond mesh')
  85. // console.log(mesh)
  86. // vertexMap = VertexMap.fromMesh(mesh)
  87. await ctx.update('Computing bond transforms');
  88. const transforms = createTransforms(group)
  89. await ctx.update('Computing bond colors');
  90. const color = createUniformColor({ value: 0xFF0000 })
  91. await ctx.update('Computing bond marks');
  92. const marker = createMarkers(instanceCount * elementCount)
  93. const values: MeshValues = {
  94. ...getMeshData(mesh),
  95. aTransform: transforms,
  96. aInstanceId: ValueCell.create(fillSerial(new Float32Array(instanceCount))),
  97. ...color,
  98. ...marker,
  99. uAlpha: ValueCell.create(defaults(props.alpha, 1.0)),
  100. uInstanceCount: ValueCell.create(instanceCount),
  101. uElementCount: ValueCell.create(elementCount),
  102. elements: mesh.indexBuffer,
  103. drawCount: ValueCell.create(mesh.triangleCount * 3),
  104. instanceCount: ValueCell.create(instanceCount),
  105. dDoubleSided: ValueCell.create(defaults(props.doubleSided, true)),
  106. dFlatShaded: ValueCell.create(defaults(props.flatShaded, false)),
  107. dFlipSided: ValueCell.create(defaults(props.flipSided, false)),
  108. dUseFog: ValueCell.create(defaults(props.useFog, true)),
  109. }
  110. const state: RenderableState = {
  111. depthMask: defaults(props.depthMask, true),
  112. visible: defaults(props.visible, true)
  113. }
  114. cylinders = createMeshRenderObject(values, state)
  115. renderObjects.push(cylinders)
  116. })
  117. },
  118. update(props: BondProps) {
  119. const newProps = Object.assign({}, currentProps, props)
  120. return Task.create('Bond.update', async ctx => {
  121. if (!cylinders) return false
  122. // TODO
  123. ValueCell.updateIfChanged(cylinders.values.uAlpha, newProps.alpha)
  124. ValueCell.updateIfChanged(cylinders.values.dDoubleSided, newProps.doubleSided)
  125. ValueCell.updateIfChanged(cylinders.values.dFlipSided, newProps.flipSided)
  126. ValueCell.updateIfChanged(cylinders.values.dFlatShaded, newProps.flatShaded)
  127. cylinders.state.visible = newProps.visible
  128. cylinders.state.depthMask = newProps.depthMask
  129. return true
  130. })
  131. },
  132. getLoci(pickingId: PickingId) {
  133. const { objectId, instanceId, elementId } = pickingId
  134. const unit = currentGroup.units[instanceId]
  135. if (cylinders.id === objectId && Unit.isAtomic(unit)) {
  136. return Link.Loci([{
  137. aUnit: unit,
  138. aIndex: unit.links.a[elementId],
  139. bUnit: unit,
  140. bIndex: unit.links.b[elementId]
  141. }])
  142. }
  143. return EmptyLoci
  144. },
  145. mark(loci: Loci, action: MarkerAction) {
  146. const group = currentGroup
  147. const tMarker = cylinders.values.tMarker
  148. const unit = group.units[0]
  149. if (!Unit.isAtomic(unit)) return
  150. const elementCount = unit.links.edgeCount * 2
  151. const instanceCount = group.units.length
  152. let changed = false
  153. const array = tMarker.ref.value.array
  154. if (isEveryLoci(loci)) {
  155. applyMarkerAction(array, 0, elementCount * instanceCount, action)
  156. changed = true
  157. } else if (Link.isLoci(loci)) {
  158. for (const b of loci.links) {
  159. const unitIdx = Unit.findUnitById(b.aUnit.id, group.units)
  160. if (unitIdx !== -1) {
  161. const _idx = unit.links.getEdgeIndex(b.aIndex, b.bIndex)
  162. if (_idx !== -1) {
  163. const idx = _idx
  164. if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
  165. changed = true
  166. }
  167. }
  168. }
  169. }
  170. } else {
  171. return
  172. }
  173. if (changed) {
  174. ValueCell.update(tMarker, tMarker.ref.value)
  175. }
  176. }
  177. }
  178. }