carbohydrate-symbol-mesh.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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 { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
  7. import { Box, PerforatedBox } from '../../../mol-geo/primitive/box';
  8. import { OctagonalPyramid, PerforatedOctagonalPyramid } from '../../../mol-geo/primitive/pyramid';
  9. import { Star } from '../../../mol-geo/primitive/star';
  10. import { Octahedron, PerforatedOctahedron } from '../../../mol-geo/primitive/octahedron';
  11. import { DiamondPrism, PentagonalPrism, HexagonalPrism } from '../../../mol-geo/primitive/prism';
  12. import { Structure, StructureElement } from '../../../mol-model/structure';
  13. import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
  14. import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
  15. import { getSaccharideShape, SaccharideShapes } from '../../../mol-model/structure/structure/carbohydrates/constants';
  16. import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
  17. import { ComplexMeshParams, ComplexMeshVisual } from '../complex-visual';
  18. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  19. import { ComplexVisual } from '../representation';
  20. import { VisualUpdateState } from '../../util';
  21. import { LocationIterator } from '../../../mol-geo/util/location-iterator';
  22. import { PickingId } from '../../../mol-geo/geometry/picking';
  23. import { OrderedSet, Interval } from '../../../mol-data/int';
  24. import { EmptyLoci, Loci } from '../../../mol-model/loci';
  25. import { VisualContext } from '../../../mol-repr/visual';
  26. import { Theme } from '../../../mol-theme/theme';
  27. import { getAltResidueLoci } from './util/common';
  28. const t = Mat4.identity()
  29. const sVec = Vec3.zero()
  30. const pd = Vec3.zero()
  31. const SideFactor = 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4)
  32. const box = Box()
  33. const perforatedBox = PerforatedBox()
  34. const octagonalPyramid = OctagonalPyramid()
  35. const perforatedOctagonalPyramid = PerforatedOctagonalPyramid()
  36. const star = Star({ outerRadius: 1, innerRadius: 0.5, thickness: 0.5, pointCount: 5 })
  37. const octahedron = Octahedron()
  38. const perforatedOctahedron = PerforatedOctahedron()
  39. const diamondPrism = DiamondPrism()
  40. const pentagonalPrism = PentagonalPrism()
  41. const hexagonalPrism = HexagonalPrism()
  42. function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CarbohydrateSymbolParams>, mesh?: Mesh) {
  43. const builderState = MeshBuilder.createState(256, 128, mesh)
  44. const { detail, sizeFactor } = props
  45. const carbohydrates = structure.carbohydrates
  46. const n = carbohydrates.elements.length
  47. const l = StructureElement.create()
  48. for (let i = 0; i < n; ++i) {
  49. const c = carbohydrates.elements[i];
  50. const shapeType = getSaccharideShape(c.component.type)
  51. l.unit = c.unit
  52. l.element = c.unit.elements[c.anomericCarbon]
  53. const size = theme.size.size(l)
  54. const radius = size * sizeFactor
  55. const side = size * sizeFactor * SideFactor
  56. const { center, normal, direction } = c.geometry
  57. Vec3.add(pd, center, direction)
  58. Mat4.targetTo(t, center, pd, normal)
  59. Mat4.setTranslation(t, center)
  60. builderState.currentGroup = i * 2
  61. switch (shapeType) {
  62. case SaccharideShapes.FilledSphere:
  63. addSphere(builderState, center, radius, detail)
  64. break;
  65. case SaccharideShapes.FilledCube:
  66. Mat4.scaleUniformly(t, t, side)
  67. MeshBuilder.addPrimitive(builderState, t, box)
  68. break;
  69. case SaccharideShapes.CrossedCube:
  70. Mat4.scaleUniformly(t, t, side)
  71. MeshBuilder.addPrimitive(builderState, t, perforatedBox)
  72. Mat4.mul(t, t, Mat4.rotZ90X180)
  73. builderState.currentGroup += 1
  74. MeshBuilder.addPrimitive(builderState, t, perforatedBox)
  75. break;
  76. case SaccharideShapes.FilledCone:
  77. Mat4.scaleUniformly(t, t, side * 1.2)
  78. MeshBuilder.addPrimitive(builderState, t, octagonalPyramid)
  79. break
  80. case SaccharideShapes.DevidedCone:
  81. Mat4.scaleUniformly(t, t, side * 1.2)
  82. MeshBuilder.addPrimitive(builderState, t, perforatedOctagonalPyramid)
  83. Mat4.mul(t, t, Mat4.rotZ90)
  84. builderState.currentGroup += 1
  85. MeshBuilder.addPrimitive(builderState, t, perforatedOctagonalPyramid)
  86. break
  87. case SaccharideShapes.FlatBox:
  88. Mat4.mul(t, t, Mat4.rotZY90)
  89. Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
  90. MeshBuilder.addPrimitive(builderState, t, box)
  91. break
  92. case SaccharideShapes.FilledStar:
  93. Mat4.scaleUniformly(t, t, side)
  94. Mat4.mul(t, t, Mat4.rotZY90)
  95. MeshBuilder.addPrimitive(builderState, t, star)
  96. break
  97. case SaccharideShapes.FilledDiamond:
  98. Mat4.mul(t, t, Mat4.rotZY90)
  99. Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
  100. MeshBuilder.addPrimitive(builderState, t, octahedron)
  101. break
  102. case SaccharideShapes.DividedDiamond:
  103. Mat4.mul(t, t, Mat4.rotZY90)
  104. Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
  105. MeshBuilder.addPrimitive(builderState, t, perforatedOctahedron)
  106. Mat4.mul(t, t, Mat4.rotY90)
  107. builderState.currentGroup += 1
  108. MeshBuilder.addPrimitive(builderState, t, perforatedOctahedron)
  109. break
  110. case SaccharideShapes.FlatDiamond:
  111. Mat4.mul(t, t, Mat4.rotZY90)
  112. Mat4.scale(t, t, Vec3.set(sVec, side, side / 2, side / 2))
  113. MeshBuilder.addPrimitive(builderState, t, diamondPrism)
  114. break
  115. case SaccharideShapes.Pentagon:
  116. Mat4.mul(t, t, Mat4.rotZY90)
  117. Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
  118. MeshBuilder.addPrimitive(builderState, t, pentagonalPrism)
  119. break
  120. case SaccharideShapes.FlatHexagon:
  121. default:
  122. Mat4.mul(t, t, Mat4.rotZYZ90)
  123. Mat4.scale(t, t, Vec3.set(sVec, side / 1.5, side , side / 2))
  124. MeshBuilder.addPrimitive(builderState, t, hexagonalPrism)
  125. break
  126. }
  127. }
  128. return MeshBuilder.getMesh(builderState)
  129. }
  130. export const CarbohydrateSymbolParams = {
  131. ...ComplexMeshParams,
  132. detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
  133. sizeFactor: PD.Numeric(1.75, { min: 0, max: 10, step: 0.01 }),
  134. }
  135. export type CarbohydrateSymbolParams = typeof CarbohydrateSymbolParams
  136. export function CarbohydrateSymbolVisual(materialId: number): ComplexVisual<CarbohydrateSymbolParams> {
  137. return ComplexMeshVisual<CarbohydrateSymbolParams>({
  138. defaultProps: PD.getDefaultValues(CarbohydrateSymbolParams),
  139. createGeometry: createCarbohydrateSymbolMesh,
  140. createLocationIterator: CarbohydrateElementIterator,
  141. getLoci: getCarbohydrateLoci,
  142. eachLocation: eachCarbohydrate,
  143. setUpdateState: (state: VisualUpdateState, newProps: PD.Values<CarbohydrateSymbolParams>, currentProps: PD.Values<CarbohydrateSymbolParams>) => {
  144. state.createGeometry = (
  145. newProps.sizeFactor !== currentProps.sizeFactor ||
  146. newProps.detail !== currentProps.detail
  147. )
  148. }
  149. }, materialId)
  150. }
  151. function CarbohydrateElementIterator(structure: Structure): LocationIterator {
  152. const carbElements = structure.carbohydrates.elements
  153. const groupCount = carbElements.length * 2
  154. const instanceCount = 1
  155. const location = StructureElement.create()
  156. function getLocation (groupIndex: number, instanceIndex: number) {
  157. const carb = carbElements[Math.floor(groupIndex / 2)]
  158. location.unit = carb.unit
  159. location.element = carb.anomericCarbon
  160. return location
  161. }
  162. function isSecondary (elementIndex: number, instanceIndex: number) {
  163. return (elementIndex % 2) === 1
  164. }
  165. return LocationIterator(groupCount, instanceCount, getLocation, true, isSecondary)
  166. }
  167. /** Return a Loci for the elements of the whole residue of a carbohydrate. */
  168. function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: number) {
  169. const { objectId, groupId } = pickingId
  170. if (id === objectId) {
  171. const carb = structure.carbohydrates.elements[Math.floor(groupId / 2)]
  172. return getAltResidueLoci(structure, carb.unit, carb.anomericCarbon)
  173. }
  174. return EmptyLoci
  175. }
  176. /** For each carbohydrate (usually a monosaccharide) when all its residue's elements are in a loci. */
  177. function eachCarbohydrate(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
  178. const { getElementIndex, getAnomericCarbons } = structure.carbohydrates
  179. let changed = false
  180. if (!StructureElement.isLoci(loci)) return false
  181. if (!Structure.areEquivalent(loci.structure, structure)) return false
  182. for (const e of loci.elements) {
  183. // TODO make more efficient by handling/grouping `e.indices` by residue index
  184. // TODO only call apply when the full alt-residue of the unit is part of `e`
  185. OrderedSet.forEach(e.indices, v => {
  186. const { model, elements } = e.unit
  187. const { index } = model.atomicHierarchy.residueAtomSegments
  188. const rI = index[elements[v]]
  189. const eIndices = getAnomericCarbons(e.unit, rI)
  190. for (let i = 0, il = eIndices.length; i < il; ++i) {
  191. const eI = eIndices[i]
  192. if (!OrderedSet.has(e.indices, OrderedSet.indexOf(elements, eI))) continue
  193. const idx = getElementIndex(e.unit, eI)
  194. if (idx !== undefined) {
  195. if (apply(Interval.ofBounds(idx * 2, idx * 2 + 2))) changed = true
  196. }
  197. }
  198. })
  199. }
  200. return changed
  201. }