symmetry-operator.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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 } from '../linear-algebra/3d'
  7. interface SymmetryOperator {
  8. readonly name: string,
  9. readonly hkl: Vec3,
  10. readonly matrix: Mat4,
  11. // cache the inverse of the transform
  12. readonly inverse: Mat4,
  13. // optimize the identity case
  14. readonly isIdentity: boolean
  15. }
  16. namespace SymmetryOperator {
  17. export const DefaultName = '1_555'
  18. export const Default: SymmetryOperator = create(DefaultName, Mat4.identity());
  19. const RotationEpsilon = 0.0001;
  20. export function create(name: string, matrix: Mat4, hkl?: Vec3): SymmetryOperator {
  21. const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero();
  22. if (Mat4.isIdentity(matrix)) return { name, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl };
  23. if (!Mat4.isRotationAndTranslation(matrix, RotationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
  24. return { name, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl };
  25. }
  26. export function checkIfRotationAndTranslation(rot: Mat3, offset: Vec3) {
  27. const matrix = Mat4.identity();
  28. for (let i = 0; i < 3; i++) {
  29. for (let j = 0; j < 3; j++) {
  30. Mat4.setValue(matrix, i, j, Mat3.getValue(rot, i, j));
  31. }
  32. }
  33. Mat4.setTranslation(matrix, offset);
  34. return Mat4.isRotationAndTranslation(matrix, RotationEpsilon);
  35. }
  36. export function ofRotationAndOffset(name: string, rot: Mat3, offset: Vec3) {
  37. const t = Mat4.identity();
  38. for (let i = 0; i < 3; i++) {
  39. for (let j = 0; j < 3; j++) {
  40. Mat4.setValue(t, i, j, Mat3.getValue(rot, i, j));
  41. }
  42. }
  43. Mat4.setTranslation(t, offset);
  44. return create(name, t);
  45. }
  46. /** Apply the 1st and then 2nd operator. ( = second.matrix * first.matrix). */
  47. export function compose(first: SymmetryOperator, second: SymmetryOperator) {
  48. const matrix = Mat4.mul(Mat4.zero(), second.matrix, first.matrix);
  49. return create(second.name, matrix, second.hkl);
  50. }
  51. export interface CoordinateMapper<T extends number> { (index: T, slot: Vec3): Vec3 }
  52. export interface ArrayMapping<T extends number> {
  53. readonly operator: SymmetryOperator,
  54. readonly invariantPosition: CoordinateMapper<T>,
  55. readonly position: CoordinateMapper<T>,
  56. x(index: T): number,
  57. y(index: T): number,
  58. z(index: T): number,
  59. r(index: T): number
  60. }
  61. export interface Coordinates { x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number> }
  62. export function createMapping<T extends number>(operator: SymmetryOperator, coords: Coordinates, radius: ((index: T) => number) | undefined): ArrayMapping<T> {
  63. const invariantPosition = SymmetryOperator.createCoordinateMapper(SymmetryOperator.Default, coords);
  64. const position = operator.isIdentity ? invariantPosition : SymmetryOperator.createCoordinateMapper(operator, coords);
  65. const { x, y, z } = createProjections(operator, coords);
  66. return { operator, invariantPosition, position, x, y, z, r: radius ? radius : _zeroRadius };
  67. }
  68. export function createCoordinateMapper<T extends number>(t: SymmetryOperator, coords: Coordinates): CoordinateMapper<T> {
  69. if (t.isIdentity) return identityPosition(coords);
  70. return generalPosition(t, coords);
  71. }
  72. }
  73. export { SymmetryOperator }
  74. function _zeroRadius(i: number) { return 0; }
  75. interface Projections { x(index: number): number, y(index: number): number, z(index: number): number }
  76. function createProjections(t: SymmetryOperator, coords: SymmetryOperator.Coordinates): Projections {
  77. if (t.isIdentity) return { x: projectCoord(coords.x), y: projectCoord(coords.y), z: projectCoord(coords.z) };
  78. return { x: projectX(t, coords), y: projectY(t, coords), z: projectZ(t, coords) };
  79. }
  80. function projectCoord(xs: ArrayLike<number>) {
  81. return (i: number) => xs[i];
  82. }
  83. function isW1(m: Mat4) {
  84. return m[3] === 0 && m[7] === 0 && m[11] === 0 && m[15] === 1;
  85. }
  86. function projectX({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
  87. const xx = m[0], yy = m[4], zz = m[8], tx = m[12];
  88. if (isW1(m)) {
  89. // this should always be the case.
  90. return (i: number) => xx * xs[i] + yy * ys[i] + zz * zs[i] + tx;
  91. }
  92. return (i: number) => {
  93. const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
  94. return (xx * x + yy * y + zz * z + tx) / w;
  95. }
  96. }
  97. function projectY({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
  98. const xx = m[1], yy = m[5], zz = m[9], ty = m[13];
  99. if (isW1(m)) {
  100. // this should always be the case.
  101. return (i: number) => xx * xs[i] + yy * ys[i] + zz * zs[i] + ty;
  102. }
  103. return (i: number) => {
  104. const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
  105. return (xx * x + yy * y + zz * z + ty) / w;
  106. }
  107. }
  108. function projectZ({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
  109. const xx = m[2], yy = m[6], zz = m[10], tz = m[14];
  110. if (isW1(m)) {
  111. // this should always be the case.
  112. return (i: number) => xx * xs[i] + yy * ys[i] + zz * zs[i] + tz;
  113. }
  114. return (i: number) => {
  115. const x = xs[i], y = ys[i], z = zs[i], w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
  116. return (xx * x + yy * y + zz * z + tz) / w;
  117. }
  118. }
  119. function identityPosition<T extends number>({ x, y, z }: SymmetryOperator.Coordinates): SymmetryOperator.CoordinateMapper<T> {
  120. return (i, s) => {
  121. s[0] = x[i];
  122. s[1] = y[i];
  123. s[2] = z[i];
  124. return s;
  125. }
  126. }
  127. function generalPosition<T extends number>({ matrix: m }: SymmetryOperator, { x: xs, y: ys, z: zs }: SymmetryOperator.Coordinates) {
  128. if (isW1(m)) {
  129. // this should always be the case.
  130. return (i: T, r: Vec3): Vec3 => {
  131. const x = xs[i], y = ys[i], z = zs[i];
  132. r[0] = m[0] * x + m[4] * y + m[8] * z + m[12];
  133. r[1] = m[1] * x + m[5] * y + m[9] * z + m[13];
  134. r[2] = m[2] * x + m[6] * y + m[10] * z + m[14];
  135. return r;
  136. }
  137. }
  138. return (i: T, r: Vec3): Vec3 => {
  139. r[0] = xs[i];
  140. r[1] = ys[i];
  141. r[2] = zs[i];
  142. Vec3.transformMat4(r, r, m);
  143. return r;
  144. }
  145. }