carbohydrate-symbol-mesh.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /**
  2. * Copyright (c) 2018-2020 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, ShiftedHexagonalPrism, HexagonalPrism, HeptagonalPrism } from '../../../mol-geo/primitive/prism';
  12. import { Structure, StructureElement, Unit } 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, SaccharideShape } 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 { getAltResidueLociFromId } from './util/common';
  28. import { BaseGeometry } from '../../../mol-geo/geometry/base';
  29. const t = Mat4.identity();
  30. const sVec = Vec3.zero();
  31. const pd = Vec3.zero();
  32. const SideFactor = 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4)
  33. const box = Box();
  34. const perforatedBox = PerforatedBox();
  35. const octagonalPyramid = OctagonalPyramid();
  36. const perforatedOctagonalPyramid = PerforatedOctagonalPyramid();
  37. const star = Star({ outerRadius: 1, innerRadius: 0.5, thickness: 0.5, pointCount: 5 });
  38. const octahedron = Octahedron();
  39. const perforatedOctahedron = PerforatedOctahedron();
  40. const diamondPrism = DiamondPrism();
  41. const pentagonalPrism = PentagonalPrism();
  42. const hexagonalPrism = HexagonalPrism();
  43. const shiftedHexagonalPrism = ShiftedHexagonalPrism();
  44. const heptagonalPrism = HeptagonalPrism();
  45. function createCarbohydrateSymbolMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<CarbohydrateSymbolParams>, mesh?: Mesh) {
  46. const builderState = MeshBuilder.createState(256, 128, mesh);
  47. const { detail, sizeFactor } = props;
  48. const carbohydrates = structure.carbohydrates;
  49. const n = carbohydrates.elements.length;
  50. const l = StructureElement.Location.create(structure);
  51. for (let i = 0; i < n; ++i) {
  52. const c = carbohydrates.elements[i];
  53. const ring = c.unit.rings.all[c.ringIndex];
  54. const shapeType = getSaccharideShape(c.component.type, ring.length);
  55. l.unit = c.unit;
  56. l.element = c.unit.elements[ring[0]];
  57. const size = theme.size.size(l);
  58. const radius = size * sizeFactor;
  59. const side = size * sizeFactor * SideFactor;
  60. const { center, normal, direction } = c.geometry;
  61. Vec3.add(pd, center, direction);
  62. Mat4.targetTo(t, center, pd, normal);
  63. Mat4.setTranslation(t, center);
  64. builderState.currentGroup = i * 2;
  65. switch (shapeType) {
  66. case SaccharideShape.FilledSphere:
  67. addSphere(builderState, center, radius, detail);
  68. break;
  69. case SaccharideShape.FilledCube:
  70. Mat4.scaleUniformly(t, t, side);
  71. MeshBuilder.addPrimitive(builderState, t, box);
  72. break;
  73. case SaccharideShape.CrossedCube:
  74. Mat4.scaleUniformly(t, t, side);
  75. MeshBuilder.addPrimitive(builderState, t, perforatedBox);
  76. Mat4.mul(t, t, Mat4.rotZ90X180);
  77. builderState.currentGroup += 1;
  78. MeshBuilder.addPrimitive(builderState, t, perforatedBox);
  79. break;
  80. case SaccharideShape.FilledCone:
  81. Mat4.scaleUniformly(t, t, side * 1.2);
  82. MeshBuilder.addPrimitive(builderState, t, octagonalPyramid);
  83. break;
  84. case SaccharideShape.DevidedCone:
  85. Mat4.scaleUniformly(t, t, side * 1.2);
  86. MeshBuilder.addPrimitive(builderState, t, perforatedOctagonalPyramid);
  87. Mat4.mul(t, t, Mat4.rotZ90);
  88. builderState.currentGroup += 1;
  89. MeshBuilder.addPrimitive(builderState, t, perforatedOctagonalPyramid);
  90. break;
  91. case SaccharideShape.FlatBox:
  92. Mat4.mul(t, t, Mat4.rotZY90);
  93. Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2));
  94. MeshBuilder.addPrimitive(builderState, t, box);
  95. break;
  96. case SaccharideShape.FilledStar:
  97. Mat4.scaleUniformly(t, t, side);
  98. Mat4.mul(t, t, Mat4.rotZY90);
  99. MeshBuilder.addPrimitive(builderState, t, star);
  100. break;
  101. case SaccharideShape.FilledDiamond:
  102. Mat4.mul(t, t, Mat4.rotZY90);
  103. Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4));
  104. MeshBuilder.addPrimitive(builderState, t, octahedron);
  105. break;
  106. case SaccharideShape.DividedDiamond:
  107. Mat4.mul(t, t, Mat4.rotZY90);
  108. Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4));
  109. MeshBuilder.addPrimitive(builderState, t, perforatedOctahedron);
  110. Mat4.mul(t, t, Mat4.rotY90);
  111. builderState.currentGroup += 1;
  112. MeshBuilder.addPrimitive(builderState, t, perforatedOctahedron);
  113. break;
  114. case SaccharideShape.FlatDiamond:
  115. Mat4.mul(t, t, Mat4.rotZY90);
  116. Mat4.scale(t, t, Vec3.set(sVec, side, side / 2, side / 2));
  117. MeshBuilder.addPrimitive(builderState, t, diamondPrism);
  118. break;
  119. case SaccharideShape.DiamondPrism:
  120. Mat4.mul(t, t, Mat4.rotZY90);
  121. Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2));
  122. MeshBuilder.addPrimitive(builderState, t, diamondPrism);
  123. break;
  124. case SaccharideShape.PentagonalPrism:
  125. case SaccharideShape.Pentagon:
  126. Mat4.mul(t, t, Mat4.rotZY90);
  127. Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2));
  128. MeshBuilder.addPrimitive(builderState, t, pentagonalPrism);
  129. break;
  130. case SaccharideShape.HexagonalPrism:
  131. Mat4.mul(t, t, Mat4.rotZY90);
  132. Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2));
  133. MeshBuilder.addPrimitive(builderState, t, hexagonalPrism);
  134. break;
  135. case SaccharideShape.HeptagonalPrism:
  136. Mat4.mul(t, t, Mat4.rotZY90);
  137. Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2));
  138. MeshBuilder.addPrimitive(builderState, t, heptagonalPrism);
  139. break;
  140. case SaccharideShape.FlatHexagon:
  141. default:
  142. Mat4.mul(t, t, Mat4.rotZYZ90);
  143. Mat4.scale(t, t, Vec3.set(sVec, side / 1.5, side, side / 2));
  144. MeshBuilder.addPrimitive(builderState, t, shiftedHexagonalPrism);
  145. break;
  146. }
  147. }
  148. return MeshBuilder.getMesh(builderState);
  149. }
  150. export const CarbohydrateSymbolParams = {
  151. ...ComplexMeshParams,
  152. detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
  153. sizeFactor: PD.Numeric(1.75, { min: 0, max: 10, step: 0.01 }),
  154. };
  155. export type CarbohydrateSymbolParams = typeof CarbohydrateSymbolParams
  156. export function CarbohydrateSymbolVisual(materialId: number): ComplexVisual<CarbohydrateSymbolParams> {
  157. return ComplexMeshVisual<CarbohydrateSymbolParams>({
  158. defaultProps: PD.getDefaultValues(CarbohydrateSymbolParams),
  159. createGeometry: createCarbohydrateSymbolMesh,
  160. createLocationIterator: CarbohydrateElementIterator,
  161. getLoci: getCarbohydrateLoci,
  162. eachLocation: eachCarbohydrate,
  163. setUpdateState: (state: VisualUpdateState, newProps: PD.Values<CarbohydrateSymbolParams>, currentProps: PD.Values<CarbohydrateSymbolParams>) => {
  164. state.createGeometry = (
  165. newProps.sizeFactor !== currentProps.sizeFactor ||
  166. newProps.detail !== currentProps.detail
  167. );
  168. }
  169. }, materialId);
  170. }
  171. function CarbohydrateElementIterator(structure: Structure): LocationIterator {
  172. const carbElements = structure.carbohydrates.elements;
  173. const groupCount = carbElements.length * 2;
  174. const instanceCount = 1;
  175. const location = StructureElement.Location.create(structure);
  176. function getLocation (groupIndex: number, instanceIndex: number) {
  177. const carb = carbElements[Math.floor(groupIndex / 2)];
  178. const ring = carb.unit.rings.all[carb.ringIndex];
  179. location.unit = carb.unit;
  180. location.element = carb.unit.elements[ring[0]];
  181. return location;
  182. }
  183. function isSecondary (elementIndex: number, instanceIndex: number) {
  184. return (elementIndex % 2) === 1;
  185. }
  186. return LocationIterator(groupCount, instanceCount, 1, getLocation, true, isSecondary);
  187. }
  188. /** Return a Loci for the elements of the whole residue of a carbohydrate. */
  189. function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: number) {
  190. const { objectId, groupId } = pickingId;
  191. if (id === objectId) {
  192. const carb = structure.carbohydrates.elements[Math.floor(groupId / 2)];
  193. return getAltResidueLociFromId(structure, carb.unit, carb.residueIndex, carb.altId);
  194. }
  195. return EmptyLoci;
  196. }
  197. /** For each carbohydrate (usually a monosaccharide) when all its residue's elements are in a loci. */
  198. function eachCarbohydrate(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
  199. const { getElementIndices } = structure.carbohydrates;
  200. let changed = false;
  201. if (!StructureElement.Loci.is(loci)) return false;
  202. if (!Structure.areEquivalent(loci.structure, structure)) return false;
  203. for (const { unit, indices } of loci.elements) {
  204. if (!Unit.isAtomic(unit)) continue;
  205. OrderedSet.forEach(indices, v => {
  206. // TODO avoid duplicate calls to apply
  207. const elementIndices = getElementIndices(unit, unit.elements[v]);
  208. for (let i = 0, il = elementIndices.length; i < il; ++i) {
  209. if (apply(Interval.ofSingleton(elementIndices[i] * 2))) changed = true;
  210. }
  211. });
  212. }
  213. return changed;
  214. }