dihedral.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Loci } from '../../../mol-model/loci';
  7. import { RuntimeContext } from '../../../mol-task';
  8. import { Lines } from '../../../mol-geo/geometry/lines/lines';
  9. import { Text } from '../../../mol-geo/geometry/text/text';
  10. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  11. import { ColorNames } from '../../../mol-util/color/names';
  12. import { ShapeRepresentation } from '../representation';
  13. import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../representation';
  14. import { Shape } from '../../../mol-model/shape';
  15. import { LinesBuilder } from '../../../mol-geo/geometry/lines/lines-builder';
  16. import { TextBuilder } from '../../../mol-geo/geometry/text/text-builder';
  17. import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
  18. import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
  19. import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
  20. import { arcLength, halfPI, radToDeg } from '../../../mol-math/misc';
  21. import { Circle } from '../../../mol-geo/primitive/circle';
  22. import { transformPrimitive } from '../../../mol-geo/primitive/primitive';
  23. import { MarkerActions, MarkerAction } from '../../../mol-util/marker-action';
  24. import { dihedralLabel } from '../../../mol-theme/label';
  25. import { MeasurementRepresentationCommonTextParams } from './common';
  26. import { Sphere3D } from '../../../mol-math/geometry';
  27. export interface DihedralData {
  28. quads: Loci.Bundle<4>[]
  29. }
  30. const SharedParams = {
  31. color: PD.Color(ColorNames.lightgreen),
  32. arcScale: PD.Numeric(0.7, { min: 0.01, max: 1, step: 0.01 })
  33. };
  34. const LinesParams = {
  35. ...Lines.Params,
  36. ...SharedParams,
  37. lineSizeAttenuation: PD.Boolean(true),
  38. linesSize: PD.Numeric(0.04, { min: 0.01, max: 5, step: 0.01 }),
  39. dashLength: PD.Numeric(0.04, { min: 0.01, max: 0.2, step: 0.01 }),
  40. };
  41. const VectorsParams = {
  42. ...LinesParams
  43. };
  44. type VectorsParams = typeof VectorsParams
  45. const ExtendersParams = {
  46. ...LinesParams
  47. };
  48. type ExtendersParams = typeof ExtendersParams
  49. const ArcParams = {
  50. ...LinesParams
  51. };
  52. type ArcParams = typeof ArcParams
  53. const SectorParams = {
  54. ...Mesh.Params,
  55. ...SharedParams,
  56. ignoreLight: PD.Boolean(true),
  57. sectorOpacity: PD.Numeric(0.75, { min: 0, max: 1, step: 0.01 }),
  58. };
  59. type SectorParams = typeof SectorParams
  60. const TextParams = {
  61. ...Text.Params,
  62. borderWidth: PD.Numeric(0.2, { min: 0, max: 0.5, step: 0.01 }),
  63. ...MeasurementRepresentationCommonTextParams
  64. };
  65. type TextParams = typeof TextParams
  66. const DihedralVisuals = {
  67. 'vectors': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, VectorsParams>) => ShapeRepresentation(getVectorsShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
  68. 'extenders': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, ExtendersParams>) => ShapeRepresentation(getExtendersShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
  69. 'connector': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, ExtendersParams>) => ShapeRepresentation(getConnectorShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
  70. 'arc': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, ArcParams>) => ShapeRepresentation(getArcShape, Lines.Utils, { modifyState: s => ({ ...s, pickable: false }) }),
  71. 'sector': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, SectorParams>) => ShapeRepresentation(getSectorShape, Mesh.Utils, { modifyProps: p => ({ ...p, alpha: p.sectorOpacity }), modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
  72. 'text': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, TextParams>) => ShapeRepresentation(getTextShape, Text.Utils, { modifyState: s => ({ ...s, markerActions: MarkerAction.None }) }),
  73. };
  74. export const DihedralParams = {
  75. ...VectorsParams,
  76. ...ExtendersParams,
  77. ...ArcParams,
  78. ...SectorParams,
  79. ...TextParams,
  80. visuals: PD.MultiSelect(['extenders', 'sector', 'text'], PD.objectToOptions(DihedralVisuals)),
  81. };
  82. export type DihedralParams = typeof DihedralParams
  83. export type DihedralProps = PD.Values<DihedralParams>
  84. //
  85. function getDihedralState() {
  86. return {
  87. sphereA: Sphere3D(),
  88. sphereB: Sphere3D(),
  89. sphereC: Sphere3D(),
  90. sphereD: Sphere3D(),
  91. dirBA: Vec3(),
  92. dirCD: Vec3(),
  93. projA: Vec3(),
  94. projD: Vec3(),
  95. arcPointA: Vec3(),
  96. arcPointD: Vec3(),
  97. arcDirA: Vec3(),
  98. arcDirD: Vec3(),
  99. arcCenter: Vec3(),
  100. arcNormal: Vec3(),
  101. radius: 0,
  102. angle: 0,
  103. };
  104. }
  105. type DihedralState = ReturnType<typeof getDihedralState>
  106. const tmpVec = Vec3();
  107. const tmpMat = Mat4();
  108. // TODO improper dihedrals are not handled correctly
  109. function setDihedralState(quad: Loci.Bundle<4>, state: DihedralState, arcScale: number) {
  110. const { sphereA, sphereB, sphereC, sphereD, dirBA, dirCD, projA, projD } = state;
  111. const { arcPointA, arcPointD, arcDirA, arcDirD, arcCenter, arcNormal } = state;
  112. const [lociA, lociB, lociC, lociD] = quad.loci;
  113. Loci.getBoundingSphere(lociA, sphereA);
  114. Loci.getBoundingSphere(lociB, sphereB);
  115. Loci.getBoundingSphere(lociC, sphereC);
  116. Loci.getBoundingSphere(lociD, sphereD);
  117. Vec3.add(arcCenter, sphereB.center, sphereC.center);
  118. Vec3.scale(arcCenter, arcCenter, 0.5);
  119. Vec3.sub(dirBA, sphereA.center, sphereB.center);
  120. Vec3.sub(dirCD, sphereD.center, sphereC.center);
  121. Vec3.add(arcPointA, arcCenter, dirBA);
  122. Vec3.add(arcPointD, arcCenter, dirCD);
  123. Vec3.sub(arcNormal, sphereC.center, sphereB.center);
  124. Vec3.orthogonalize(arcDirA, arcNormal, dirBA);
  125. Vec3.orthogonalize(arcDirD, arcNormal, dirCD);
  126. Vec3.projectPointOnVector(projA, arcPointA, arcDirA, arcCenter);
  127. Vec3.projectPointOnVector(projD, arcPointD, arcDirD, arcCenter);
  128. const len = Math.min(Vec3.distance(projA, arcCenter), Vec3.distance(projD, arcCenter));
  129. const radius = len * arcScale;
  130. Vec3.setMagnitude(arcDirA, arcDirA, radius);
  131. Vec3.setMagnitude(arcDirD, arcDirD, radius);
  132. Vec3.add(arcPointA, arcCenter, arcDirA);
  133. Vec3.add(arcPointD, arcCenter, arcDirD);
  134. state.radius = radius;
  135. state.angle = Vec3.dihedralAngle(sphereA.center, sphereB.center, sphereC.center, sphereD.center);
  136. Vec3.matchDirection(tmpVec, arcNormal, Vec3.sub(tmpVec, arcPointA, sphereA.center));
  137. const angleA = Vec3.angle(dirBA, tmpVec);
  138. const lenA = radius / Math.cos(angleA > halfPI ? angleA - halfPI : angleA);
  139. Vec3.add(projA, sphereB.center, Vec3.setMagnitude(tmpVec, dirBA, lenA));
  140. Vec3.matchDirection(tmpVec, arcNormal, Vec3.sub(tmpVec, arcPointD, sphereD.center));
  141. const angleD = Vec3.angle(dirCD, tmpVec);
  142. const lenD = radius / Math.cos(angleD > halfPI ? angleD - halfPI : angleD);
  143. Vec3.add(projD, sphereC.center, Vec3.setMagnitude(tmpVec, dirCD, lenD));
  144. return state;
  145. }
  146. function getCircle(state: DihedralState, segmentLength?: number) {
  147. const { radius, angle } = state;
  148. const segments = segmentLength ? arcLength(angle, radius) / segmentLength : 32;
  149. Mat4.targetTo(tmpMat, state.arcCenter, angle < 0 ? state.arcPointD : state.arcPointA, state.arcNormal);
  150. Mat4.setTranslation(tmpMat, state.arcCenter);
  151. Mat4.mul(tmpMat, tmpMat, Mat4.rotY180);
  152. const circle = Circle({ radius, thetaLength: Math.abs(angle), segments });
  153. return transformPrimitive(circle, tmpMat);
  154. }
  155. const tmpState = getDihedralState();
  156. function getDihedralName(data: DihedralData) {
  157. return data.quads.length === 1 ? `Dihedral ${dihedralLabel(data.quads[0], { measureOnly: true })}` : `${data.quads.length} Dihedrals`;
  158. }
  159. //
  160. function buildVectorsLines(data: DihedralData, props: DihedralProps, lines?: Lines): Lines {
  161. const builder = LinesBuilder.create(128, 64, lines);
  162. for (let i = 0, il = data.quads.length; i < il; ++i) {
  163. setDihedralState(data.quads[i], tmpState, props.arcScale);
  164. builder.addFixedLengthDashes(tmpState.arcCenter, tmpState.arcPointA, props.dashLength, i);
  165. builder.addFixedLengthDashes(tmpState.arcCenter, tmpState.arcPointD, props.dashLength, i);
  166. }
  167. return builder.getLines();
  168. }
  169. function getVectorsShape(ctx: RuntimeContext, data: DihedralData, props: DihedralProps, shape?: Shape<Lines>) {
  170. const lines = buildVectorsLines(data, props, shape && shape.geometry);
  171. const name = getDihedralName(data);
  172. return Shape.create(name, data, lines, () => props.color, () => props.linesSize, () => '');
  173. }
  174. //
  175. function buildConnectorLine(data: DihedralData, props: DihedralProps, lines?: Lines): Lines {
  176. const builder = LinesBuilder.create(128, 64, lines);
  177. for (let i = 0, il = data.quads.length; i < il; ++i) {
  178. setDihedralState(data.quads[i], tmpState, props.arcScale);
  179. builder.addFixedLengthDashes(tmpState.sphereB.center, tmpState.sphereC.center, props.dashLength, i);
  180. }
  181. return builder.getLines();
  182. }
  183. function getConnectorShape(ctx: RuntimeContext, data: DihedralData, props: DihedralProps, shape?: Shape<Lines>) {
  184. const lines = buildConnectorLine(data, props, shape && shape.geometry);
  185. const name = getDihedralName(data);
  186. return Shape.create(name, data, lines, () => props.color, () => props.linesSize, () => '');
  187. }
  188. //
  189. function buildExtendersLines(data: DihedralData, props: DihedralProps, lines?: Lines): Lines {
  190. const builder = LinesBuilder.create(128, 64, lines);
  191. for (let i = 0, il = data.quads.length; i < il; ++i) {
  192. setDihedralState(data.quads[i], tmpState, props.arcScale);
  193. builder.addFixedLengthDashes(tmpState.arcPointA, tmpState.projA, props.dashLength, i);
  194. builder.addFixedLengthDashes(tmpState.arcPointD, tmpState.projD, props.dashLength, i);
  195. }
  196. return builder.getLines();
  197. }
  198. function getExtendersShape(ctx: RuntimeContext, data: DihedralData, props: DihedralProps, shape?: Shape<Lines>) {
  199. const lines = buildExtendersLines(data, props, shape && shape.geometry);
  200. const name = getDihedralName(data);
  201. return Shape.create(name, data, lines, () => props.color, () => props.linesSize, () => '');
  202. }
  203. //
  204. function buildArcLines(data: DihedralData, props: DihedralProps, lines?: Lines): Lines {
  205. const builder = LinesBuilder.create(128, 64, lines);
  206. for (let i = 0, il = data.quads.length; i < il; ++i) {
  207. setDihedralState(data.quads[i], tmpState, props.arcScale);
  208. const circle = getCircle(tmpState, props.dashLength);
  209. const { indices, vertices } = circle;
  210. for (let j = 0, jl = indices.length; j < jl; j += 3) {
  211. if (j % 2 === 1) continue; // draw every other segment to get dashes
  212. const start = indices[j] * 3;
  213. const end = indices[j + 1] * 3;
  214. const startX = vertices[start];
  215. const startY = vertices[start + 1];
  216. const startZ = vertices[start + 2];
  217. const endX = vertices[end];
  218. const endY = vertices[end + 1];
  219. const endZ = vertices[end + 2];
  220. builder.add(startX, startY, startZ, endX, endY, endZ, i);
  221. }
  222. }
  223. return builder.getLines();
  224. }
  225. function getArcShape(ctx: RuntimeContext, data: DihedralData, props: DihedralProps, shape?: Shape<Lines>) {
  226. const lines = buildArcLines(data, props, shape && shape.geometry);
  227. const name = getDihedralName(data);
  228. return Shape.create(name, data, lines, () => props.color, () => props.linesSize, () => '');
  229. }
  230. //
  231. function buildSectorMesh(data: DihedralData, props: DihedralProps, mesh?: Mesh): Mesh {
  232. const state = MeshBuilder.createState(128, 64, mesh);
  233. for (let i = 0, il = data.quads.length; i < il; ++i) {
  234. setDihedralState(data.quads[i], tmpState, props.arcScale);
  235. const circle = getCircle(tmpState);
  236. state.currentGroup = i;
  237. MeshBuilder.addPrimitive(state, Mat4.id, circle);
  238. MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle);
  239. }
  240. return MeshBuilder.getMesh(state);
  241. }
  242. function getSectorShape(ctx: RuntimeContext, data: DihedralData, props: DihedralProps, shape?: Shape<Mesh>) {
  243. const mesh = buildSectorMesh(data, props, shape && shape.geometry);
  244. const name = getDihedralName(data);
  245. const getLabel = (groupId: number ) => dihedralLabel(data.quads[groupId]);
  246. return Shape.create(name, data, mesh, () => props.color, () => 1, getLabel);
  247. }
  248. //
  249. function buildText(data: DihedralData, props: DihedralProps, text?: Text): Text {
  250. const builder = TextBuilder.create(props, 128, 64, text);
  251. for (let i = 0, il = data.quads.length; i < il; ++i) {
  252. setDihedralState(data.quads[i], tmpState, props.arcScale);
  253. Vec3.add(tmpVec, tmpState.arcDirA, tmpState.arcDirD);
  254. Vec3.setMagnitude(tmpVec, tmpVec, tmpState.radius);
  255. Vec3.add(tmpVec, tmpState.arcCenter, tmpVec);
  256. const angle = Math.abs(radToDeg(tmpState.angle)).toFixed(2);
  257. const label = props.customText || `${angle}\u00B0`;
  258. const radius = Math.max(2, tmpState.sphereA.radius, tmpState.sphereB.radius, tmpState.sphereC.radius, tmpState.sphereD.radius);
  259. const scale = radius / 2;
  260. builder.add(label, tmpVec[0], tmpVec[1], tmpVec[2], 0.1, scale, i);
  261. }
  262. return builder.getText();
  263. }
  264. function getTextShape(ctx: RuntimeContext, data: DihedralData, props: DihedralProps, shape?: Shape<Text>) {
  265. const text = buildText(data, props, shape && shape.geometry);
  266. const name = getDihedralName(data);
  267. const getLabel = (groupId: number ) => dihedralLabel(data.quads[groupId]);
  268. return Shape.create(name, data, text, () => props.textColor, () => props.textSize, getLabel);
  269. }
  270. //
  271. export type DihedralRepresentation = Representation<DihedralData, DihedralParams>
  272. export function DihedralRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<DihedralData, DihedralParams>): DihedralRepresentation {
  273. return Representation.createMulti('Dihedral', ctx, getParams, Representation.StateBuilder, DihedralVisuals as unknown as Representation.Def<DihedralData, DihedralParams>);
  274. }