dihedral.ts 13 KB

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