symmetry-operator.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /**
  2. * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Vec3, Mat4, Mat3, Quat } from '../linear-algebra/3d'
  7. import { lerp as scalar_lerp } from '../../mol-math/interpolate';
  8. import { defaults } from '../../mol-util';
  9. interface SymmetryOperator {
  10. readonly name: string,
  11. readonly assembly: {
  12. /** pointer to `pdbx_struct_assembly.id` or empty string */
  13. readonly id: string
  14. /** pointers to `pdbx_struct_oper_list.id` or empty list */
  15. readonly operList: string[]
  16. }
  17. /** pointer to `struct_ncs_oper.id` or empty string */
  18. readonly ncsId: string,
  19. readonly hkl: Vec3,
  20. /** spacegroup symmetry operator index, -1 if not applicable */
  21. readonly spgrOp: number,
  22. readonly matrix: Mat4,
  23. // cache the inverse of the transform
  24. readonly inverse: Mat4,
  25. // optimize the identity case
  26. readonly isIdentity: boolean
  27. }
  28. namespace SymmetryOperator {
  29. export const DefaultName = '1_555'
  30. export const Default: SymmetryOperator = create(DefaultName, Mat4.identity(), { id: '', operList: [] });
  31. export const RotationTranslationEpsilon = 0.005;
  32. export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3, spgrOp?: number): SymmetryOperator {
  33. const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero();
  34. spgrOp = defaults(spgrOp, -1)
  35. ncsId = ncsId || ''
  36. if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, spgrOp, ncsId };
  37. if (!Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
  38. return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, spgrOp, ncsId };
  39. }
  40. export function checkIfRotationAndTranslation(rot: Mat3, offset: Vec3) {
  41. const matrix = Mat4.identity();
  42. for (let i = 0; i < 3; i++) {
  43. for (let j = 0; j < 3; j++) {
  44. Mat4.setValue(matrix, i, j, Mat3.getValue(rot, i, j));
  45. }
  46. }
  47. Mat4.setTranslation(matrix, offset);
  48. return Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon);
  49. }
  50. export function ofRotationAndOffset(name: string, rot: Mat3, offset: Vec3, ncsId?: string) {
  51. const t = Mat4.identity();
  52. for (let i = 0; i < 3; i++) {
  53. for (let j = 0; j < 3; j++) {
  54. Mat4.setValue(t, i, j, Mat3.getValue(rot, i, j));
  55. }
  56. }
  57. Mat4.setTranslation(t, offset);
  58. return create(name, t, { id: '', operList: [] }, ncsId);
  59. }
  60. const _q1 = Quat.identity(), _q2 = Quat.zero(), _q3 = Quat.zero(), _axis = Vec3.zero();
  61. export function lerpFromIdentity(out: Mat4, op: SymmetryOperator, t: number): Mat4 {
  62. const m = op.inverse;
  63. if (op.isIdentity) return Mat4.copy(out, m);
  64. const _t = 1 - t;
  65. // interpolate rotation
  66. Mat4.getRotation(_q2, m);
  67. Quat.slerp(_q2, _q1, _q2, _t);
  68. const angle = Quat.getAxisAngle(_axis, _q2);
  69. Mat4.fromRotation(out, angle, _axis);
  70. // interpolate translation
  71. Mat4.setValue(out, 0, 3, _t * Mat4.getValue(m, 0, 3));
  72. Mat4.setValue(out, 1, 3, _t * Mat4.getValue(m, 1, 3));
  73. Mat4.setValue(out, 2, 3, _t * Mat4.getValue(m, 2, 3));
  74. return out;
  75. }
  76. export function slerp(out: Mat4, src: Mat4, tar: Mat4, t: number): Mat4 {
  77. if (Math.abs(t) <= 0.00001) return Mat4.copy(out, src);
  78. if (Math.abs(t - 1) <= 0.00001) return Mat4.copy(out, tar);
  79. // interpolate rotation
  80. Mat4.getRotation(_q2, src);
  81. Mat4.getRotation(_q3, tar);
  82. Quat.slerp(_q3, _q2, _q3, t);
  83. const angle = Quat.getAxisAngle(_axis, _q3);
  84. Mat4.fromRotation(out, angle, _axis);
  85. // interpolate translation
  86. Mat4.setValue(out, 0, 3, scalar_lerp(Mat4.getValue(src, 0, 3), Mat4.getValue(tar, 0, 3), t));
  87. Mat4.setValue(out, 1, 3, scalar_lerp(Mat4.getValue(src, 1, 3), Mat4.getValue(tar, 1, 3), t));
  88. Mat4.setValue(out, 2, 3, scalar_lerp(Mat4.getValue(src, 2, 3), Mat4.getValue(tar, 2, 3), t));
  89. return out;
  90. }
  91. /**
  92. * Apply the 1st and then 2nd operator. ( = second.matrix * first.matrix).
  93. * Keep `name`, `assembly`, `ncsId`, `hkl` and `spgrOpId` properties from second.
  94. */
  95. export function compose(first: SymmetryOperator, second: SymmetryOperator) {
  96. const matrix = Mat4.mul(Mat4.zero(), second.matrix, first.matrix);
  97. return create(second.name, matrix, second.assembly, second.ncsId, second.hkl, second.spgrOp);
  98. }
  99. export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 }
  100. export interface ArrayMapping<T extends number> {
  101. readonly operator: SymmetryOperator,
  102. readonly invariantPosition: CoordinateMapper<T>,
  103. readonly position: CoordinateMapper<T>,
  104. x(index: T): number,
  105. y(index: T): number,
  106. z(index: T): number,
  107. r(index: T): number
  108. }
  109. export interface Coordinates { x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number> }
  110. export function createMapping<T extends number>(operator: SymmetryOperator, coords: Coordinates, radius: ((index: T) => number) | undefined): ArrayMapping<T> {
  111. const invariantPosition = SymmetryOperator.createCoordinateMapper(SymmetryOperator.Default, coords);
  112. const position = operator.isIdentity ? invariantPosition : SymmetryOperator.createCoordinateMapper(operator, coords);
  113. const { x, y, z } = createProjections(operator, coords);
  114. return { operator, invariantPosition, position, x, y, z, r: radius ? radius : _zeroRadius };
  115. }
  116. export function createCoordinateMapper<T extends number>(t: SymmetryOperator, coords: Coordinates): CoordinateMapper<T> {
  117. if (t.isIdentity) return identityPosition(coords);
  118. return generalPosition(t, coords);
  119. }
  120. }
  121. export { SymmetryOperator }
  122. function _zeroRadius(i: number) { return 0; }
  123. interface Projections { x(index: number): number, y(index: number): number, z(index: number): number }
  124. function createProjections(t: SymmetryOperator, coords: SymmetryOperator.Coordinates): Projections {
  125. if (t.isIdentity) return { x: projectCoord(coords.x), y: projectCoord(coords.y), z: projectCoord(coords.z) };
  126. return { x: projectX(t, coords), y: projectY(t, coords), z: projectZ(t, coords) };
  127. }
  128. function projectCoord(xs: ArrayLike<number>) {
  129. return (i: number) => xs[i];
  130. }
  131. function isW1(m: Mat4) {
  132. return m[3] === 0 && m[7] === 0 && m[11] === 0 && m[15] === 1;
  133. }
  134. function projectX({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
  135. const xx = m[0], yy = m[4], zz = m[8], tx = m[12];
  136. if (isW1(m)) {
  137. // this should always be the case.
  138. return (i: number) => xx * xs[i] + yy * ys[i] + zz * zs[i] + tx;
  139. }
  140. return (i: number) => {
  141. const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
  142. return (xx * x + yy * y + zz * z + tx) / w;
  143. }
  144. }
  145. function projectY({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
  146. const xx = m[1], yy = m[5], zz = m[9], ty = m[13];
  147. if (isW1(m)) {
  148. // this should always be the case.
  149. return (i: number) => xx * xs[i] + yy * ys[i] + zz * zs[i] + ty;
  150. }
  151. return (i: number) => {
  152. const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
  153. return (xx * x + yy * y + zz * z + ty) / w;
  154. }
  155. }
  156. function projectZ({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
  157. const xx = m[2], yy = m[6], zz = m[10], tz = m[14];
  158. if (isW1(m)) {
  159. // this should always be the case.
  160. return (i: number) => xx * xs[i] + yy * ys[i] + zz * zs[i] + tz;
  161. }
  162. return (i: number) => {
  163. const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
  164. return (xx * x + yy * y + zz * z + tz) / w;
  165. }
  166. }
  167. function identityPosition<T extends number>({ x, y, z }: SymmetryOperator.Coordinates): SymmetryOperator.CoordinateMapper<T> {
  168. return (i, s) => {
  169. s[0] = x[i];
  170. s[1] = y[i];
  171. s[2] = z[i];
  172. return s;
  173. }
  174. }
  175. function generalPosition<T extends number>({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
  176. if (isW1(m)) {
  177. // this should always be the case.
  178. return (i: T, r: Vec3): Vec3 => {
  179. const x = xs[i], y = ys[i], z = zs[i];
  180. r[0] = m[0] * x + m[4] * y + m[8] * z + m[12];
  181. r[1] = m[1] * x + m[5] * y + m[9] * z + m[13];
  182. r[2] = m[2] * x + m[6] * y + m[10] * z + m[14];
  183. return r;
  184. }
  185. }
  186. return (i: T, r: Vec3): Vec3 => {
  187. r[0] = xs[i];
  188. r[1] = ys[i];
  189. r[2] = zs[i];
  190. Vec3.transformMat4(r, r, m);
  191. return r;
  192. }
  193. }