dihedral.ts 12 KB

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