@itwin/core-geometry 4.0.0-dev.39 → 4.0.0-dev.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,7 +15,7 @@ const Point2dVector2d_1 = require("./Point2dVector2d");
15
15
  const Point3dVector3d_1 = require("./Point3dVector3d");
16
16
  const Transform_1 = require("./Transform");
17
17
  /* eslint-disable @itwin/prefer-get */
18
- // cSpell:words XXYZ YXYZ ZXYZ SaeedTorabi arctan newcommand
18
+ // cSpell:words XXYZ YXYZ ZXYZ SaeedTorabi arctan newcommand diagonalization
19
19
  /**
20
20
  * PackedMatrix3dOps contains static methods for matrix operations where the matrix is a Float64Array.
21
21
  * * The Float64Array contains the matrix entries in row-major order
@@ -669,7 +669,14 @@ class Matrix3d {
669
669
  }
670
670
  return Matrix3d.createIdentity(result);
671
671
  }
672
- /** Return the matrix for rotation of `angle` around desired `axis` */
672
+ /**
673
+ * Return the matrix for rotation of `angle` around desired `axis`
674
+ * * Visualization can be found at https://www.itwinjs.org/sandbox/SaeedTorabi/CubeRotationAroundAnAxis
675
+ * @param axis the axis of rotation
676
+ * @param angle the angle of rotation
677
+ * @param result caller-allocated matrix (optional)
678
+ * @returns the `rotation matrix` or `undefined` (if axis magnitude is near zero).
679
+ */
673
680
  static createRotationAroundVector(axis, angle, result) {
674
681
  // Rodriguez formula (matrix form), https://mathworld.wolfram.com/RodriguesRotationFormula.html
675
682
  const c = angle.cos();
@@ -885,63 +892,143 @@ class Matrix3d {
885
892
  return result;
886
893
  }
887
894
  /**
888
- * Apply (in place) a jacobi update that zeros out this.at(i,j).
895
+ * Apply (in place) a jacobi eigenvalue algorithm.
889
896
  * @param i row index of zeroed member
890
897
  * @param j column index of zeroed member
891
- * @param k other row/column index (different from i and j)
892
- * @param leftEigenVectors a matrix that its columns will be filled by eigenvectors of this Matrix3d
893
- * (allocated by caller, computed and filled by this function)
898
+ * @param leftEigenvectors a matrix that its columns will be filled by the left eigenvectors of `this` Matrix3d
899
+ * (allocated by caller, computed and filled by this function). Note that columns of leftEigenVectors will be
900
+ * mutually perpendicular because `this` matrix is symmetric.
901
+ * @param lambda a matrix that its diagonal entries will be filled by eigenvalues and its non-diagonal elements
902
+ * converge to 0 (allocated by caller, computed and filled by this function).
903
+ */
904
+ applySymmetricJacobi(i, j, leftEigenvectors, lambda) {
905
+ const sii = lambda.at(i, i);
906
+ const sjj = lambda.at(j, j);
907
+ const sij = lambda.at(i, j);
908
+ if (Math.abs(sij) < 1.0e-15 * (sii + sjj))
909
+ return 0.0;
910
+ const jacobi = Angle_1.Angle.trigValuesToHalfAngleTrigValues(sii - sjj, 2.0 * sij);
911
+ const c = jacobi.c;
912
+ const s = jacobi.s;
913
+ /**
914
+ * The following check does not exist in applyFastSymmetricJacobi because here if we don't return
915
+ * early, the matrix remains untouched. However, applyFastSymmetricJacobi zeroes-out elements ij
916
+ * and ji. Therefore, if we return early in applyFastSymmetricJacobi, zeroing-out wont happen.
917
+ */
918
+ if (Math.abs(s) < 2.0e-15)
919
+ return 0.0;
920
+ /**
921
+ * If you apply the following 2 lines to a symmetric matrix, you get same lines used in
922
+ * applyFastSymmetricJacobi. There are 2 differences which make applyFastSymmetricJacobi
923
+ * more efficient. First, we directly set elements ij and ji equal to zero rather than
924
+ * calculation them. Second, we copy symmetric elements from upper triangle to lower
925
+ * instead of calculating them.
926
+ */
927
+ lambda.applyGivensRowOp(i, j, c, s);
928
+ lambda.applyGivensColumnOp(i, j, c, s);
929
+ leftEigenvectors.applyGivensColumnOp(i, j, c, s);
930
+ return Math.abs(sij);
931
+ }
932
+ /**
933
+ * Factor `this` matrix as a product `U * lambda * UT` where `U` is an orthogonal matrix and `lambda`
934
+ * is a diagonal matrix.
935
+ *
936
+ * * **Note 1:** You must apply this function to a `symmetric` matrix. Otherwise, the lower triangle is ignored
937
+ * and the upper triangle is mirrored to the lower triangle to enforce symmetry.
938
+ * * **Note 2:** This function is replaced by a faster method called `fastSymmetricEigenvalues` so consider
939
+ * using the fast version instead.
940
+ * @param leftEigenvectors a matrix that its columns will be filled by the left eigenvectors of `this` Matrix3d
941
+ * (allocated by caller, computed and filled by this function). Note that columns of leftEigenVectors will be
942
+ * mutually perpendicular because `this` matrix is symmetric.
943
+ * @param lambda a vector that its entries will be filled by eigenvalues of `this` Matrix3d (allocated by
944
+ * caller, computed and filled by this function).
894
945
  */
895
- applyFastSymmetricJacobiUpdate(i, j, k, leftEigenVectors) {
946
+ symmetricEigenvalues(leftEigenvectors, lambda) {
947
+ const matrix = this.clone();
948
+ leftEigenvectors.setIdentity();
949
+ matrix.coffs[3] = matrix.coffs[1];
950
+ matrix.coffs[6] = matrix.coffs[2];
951
+ matrix.coffs[7] = matrix.coffs[5];
952
+ const tolerance = 1.0e-12 * this.sumSquares();
953
+ const numberOfIterations = 7;
954
+ for (let iteration = 0; iteration < numberOfIterations; iteration++) {
955
+ const sum = this.applySymmetricJacobi(0, 1, leftEigenvectors, matrix)
956
+ + this.applySymmetricJacobi(0, 2, leftEigenvectors, matrix)
957
+ + this.applySymmetricJacobi(1, 2, leftEigenvectors, matrix);
958
+ if (sum < tolerance) {
959
+ lambda.set(matrix.at(0, 0), matrix.at(1, 1), matrix.at(2, 2));
960
+ return true;
961
+ }
962
+ }
963
+ return false;
964
+ }
965
+ /**
966
+ * Apply (in place) a jacobi eigenvalue algorithm that diagonalize `this` matrix, i.e., zeros out this.at(i,j).
967
+ * * During diagonalization, the upper triangle is mirrored to lower triangle to enforce symmetry.
968
+ * * Math details can be found at docs/learning/geometry/Matrix.md
969
+ * @param i row index of zeroed member.
970
+ * @param j column index of zeroed member.
971
+ * @param k other row/column index (different from i and j).
972
+ * @param leftEigenVectors a matrix that its columns will be filled by the left eigenvectors of `this` Matrix3d
973
+ * (allocated by caller, computed and filled by this function). Note that columns of leftEigenVectors will be
974
+ * mutually perpendicular because `this` matrix is symmetric.
975
+ */
976
+ applyFastSymmetricJacobi(i, j, k, leftEigenVectors) {
896
977
  const indexII = 4 * i;
897
978
  const indexJJ = 4 * j;
898
979
  const indexIJ = 3 * i + j;
980
+ const indexJI = 3 * j + i;
899
981
  const indexIK = 3 * i + k;
982
+ const indexKI = 3 * k + i;
900
983
  const indexJK = 3 * j + k;
901
- const dotUU = this.coffs[indexII];
902
- const dotVV = this.coffs[indexJJ];
903
- const dotUV = this.coffs[indexIJ];
904
- const jacobi = Angle_1.Angle.trigValuesToHalfAngleTrigValues(dotUU - dotVV, 2.0 * dotUV);
905
- if (Math.abs(dotUV) < 1.0e-15 * (dotUU + dotVV))
984
+ const indexKJ = 3 * k + j;
985
+ const sii = this.coffs[indexII];
986
+ const sjj = this.coffs[indexJJ];
987
+ const sij = this.coffs[indexIJ];
988
+ if (Math.abs(sij) < 1.0e-15 * (sii + sjj))
906
989
  return 0.0;
990
+ const jacobi = Angle_1.Angle.trigValuesToHalfAngleTrigValues(sii - sjj, 2.0 * sij);
907
991
  const c = jacobi.c;
908
992
  const s = jacobi.s;
909
993
  const cc = c * c;
910
994
  const ss = s * s;
911
995
  const sc2 = 2.0 * c * s;
912
- this.coffs[indexII] = cc * dotUU + sc2 * dotUV + ss * dotVV;
913
- this.coffs[indexJJ] = ss * dotUU - sc2 * dotUV + cc * dotVV;
996
+ this.coffs[indexII] = cc * sii + sc2 * sij + ss * sjj;
997
+ this.coffs[indexJJ] = ss * sii - sc2 * sij + cc * sjj;
914
998
  this.coffs[indexIJ] = 0.0;
999
+ this.coffs[indexJI] = 0.0;
915
1000
  const a = this.coffs[indexIK];
916
1001
  const b = this.coffs[indexJK];
917
- this.coffs[indexIK] = a * c + b * s;
1002
+ this.coffs[indexIK] = c * a + s * b;
918
1003
  this.coffs[indexJK] = -s * a + c * b;
919
- this.coffs[3 * j + i] = 0.0;
920
- this.coffs[3 * k + i] = this.coffs[indexIK];
921
- this.coffs[3 * k + j] = this.coffs[indexJK];
1004
+ this.coffs[indexKI] = this.coffs[indexIK];
1005
+ this.coffs[indexKJ] = this.coffs[indexJK];
922
1006
  leftEigenVectors.applyGivensColumnOp(i, j, c, s);
923
- return Math.abs(dotUV);
1007
+ return Math.abs(sij);
924
1008
  }
925
1009
  /**
926
- * Factor this (symmetrized) as a product U * lambda * UT where U is orthogonal, lambda is diagonal.
927
- * The upper triangle is mirrored to lower triangle to enforce symmetry.
928
- * @param leftEigenvectors a matrix that its columns will be filled by eigenvectors of this Matrix3d
929
- * (allocated by caller, computed and filled by this function)
930
- * @param lambda a vector that its entries will be filled by eigenvalues of this Matrix3d
931
- * (allocated by caller, computed and filled by this function)
1010
+ * Factor `this` matrix as a product `U * lambda * UT` where `U` is an orthogonal matrix and `lambda`
1011
+ * is a diagonal matrix.
1012
+ *
1013
+ * * **Note:** You must apply this function to a `symmetric` matrix. Otherwise, the lower triangle is ignored
1014
+ * and the upper triangle is mirrored to the lower triangle to enforce symmetry.
1015
+ * * Math details can be found at docs/learning/geometry/Matrix.md
1016
+ * @param leftEigenvectors a matrix that its columns will be filled by the left eigenvectors of `this` Matrix3d
1017
+ * (allocated by caller, computed and filled by this function). Note that columns of leftEigenVectors will be
1018
+ * mutually perpendicular because `this` matrix is symmetric.
1019
+ * @param lambda a vector that its entries will be filled by eigenvalues of `this` Matrix3d (allocated by
1020
+ * caller, computed and filled by this function).
932
1021
  */
933
1022
  fastSymmetricEigenvalues(leftEigenvectors, lambda) {
934
1023
  const matrix = this.clone();
935
1024
  leftEigenvectors.setIdentity();
936
1025
  const tolerance = 1.0e-12 * this.sumSquares();
937
- for (let iteration = 0; iteration < 7; iteration++) {
938
- const sum = matrix.applyFastSymmetricJacobiUpdate(0, 1, 2, leftEigenvectors)
939
- + matrix.applyFastSymmetricJacobiUpdate(0, 2, 1, leftEigenvectors)
940
- + matrix.applyFastSymmetricJacobiUpdate(1, 2, 0, leftEigenvectors);
941
- // console.log("symmetric sum", sum);
942
- // console.log ("sum", sum);
1026
+ const numberOfIterations = 7;
1027
+ for (let iteration = 0; iteration < numberOfIterations; iteration++) {
1028
+ const sum = matrix.applyFastSymmetricJacobi(0, 1, 2, leftEigenvectors)
1029
+ + matrix.applyFastSymmetricJacobi(0, 2, 1, leftEigenvectors)
1030
+ + matrix.applyFastSymmetricJacobi(1, 2, 0, leftEigenvectors);
943
1031
  if (sum < tolerance) {
944
- // console.log("symmetric iterations", iteration);
945
1032
  lambda.set(matrix.at(0, 0), matrix.at(1, 1), matrix.at(2, 2));
946
1033
  return true;
947
1034
  }
@@ -949,8 +1036,8 @@ class Matrix3d {
949
1036
  return false;
950
1037
  }
951
1038
  /**
952
- * Compute the (unit vector) axis and angle of rotation.
953
- * * math details can be found at docs/learning/geometry/Angle.md
1039
+ * Compute the (unit vector) axis and angle for the rotation generated by `this` Matrix3d.
1040
+ * * Math details can be found at docs/learning/geometry/Angle.md
954
1041
  * @returns Returns axis and angle of rotation with result.ok === true when the conversion succeeded.
955
1042
  */
956
1043
  getAxisAndAngleOfRotation() {
@@ -976,7 +1063,7 @@ class Matrix3d {
976
1063
  * 2x^2-1 2xy 2xz
977
1064
  * 2xy 2y^2-1 2yz
978
1065
  * 2xz 2yz 2z^2-1
979
- * Note that the matrix is symmetric.
1066
+ * Note that the matrix is "symmetric".
980
1067
  * If rotation is around one the standard basis then non-diagonal entries become 0 and we
981
1068
  * have one 1 and two -1s on the diagonal.
982
1069
  * If rotation is around an axis other than standard basis, then the axis is the eigenvector
@@ -998,7 +1085,7 @@ class Matrix3d {
998
1085
  // Look for eigenvector with eigenvalue = 1
999
1086
  const eigenvectors = Matrix3d.createIdentity();
1000
1087
  const eigenvalues = Point3dVector3d_1.Vector3d.create(0, 0, 0);
1001
- if (this.fastSymmetricEigenvalues(eigenvectors, eigenvalues)) {
1088
+ if (this.fastSymmetricEigenvalues(eigenvectors, eigenvalues)) { // note: this matrix is "symmetric"
1002
1089
  for (let axisIndex = 0; axisIndex < 2; axisIndex++) {
1003
1090
  const lambda = eigenvalues.at(axisIndex);
1004
1091
  if (Geometry_1.Geometry.isAlmostEqualNumber(1, lambda))
@@ -1019,53 +1106,67 @@ class Matrix3d {
1019
1106
  };
1020
1107
  return result;
1021
1108
  }
1022
- /** Rotate so columns i and j become perpendicular */
1109
+ /**
1110
+ * Rotate columns i and j of `this` matrix to make them perpendicular using the angle that zero-out
1111
+ * `thisTranspose * this`.
1112
+ * @param i row index of zeroed member.
1113
+ * @param j column index of zeroed member.
1114
+ * @param matrixU a matrix that its columns will be filled by the right eigenvectors of `thisTranspose * this`
1115
+ * (allocated by caller, computed and filled by this function). Note that columns of matrixU will be mutually
1116
+ * perpendicular because `thisTranspose * this` matrix is symmetric.
1117
+ */
1023
1118
  applyJacobiColumnRotation(i, j, matrixU) {
1024
- const uDotU = this.coffs[i] * this.coffs[i] + this.coffs[i + 3] * this.coffs[i + 3] + this.coffs[i + 6] * this.coffs[i + 6];
1025
- const vDotV = this.coffs[j] * this.coffs[j] + this.coffs[j + 3] * this.coffs[j + 3] + this.coffs[j + 6] * this.coffs[j + 6];
1026
- const uDotV = this.coffs[i] * this.coffs[j] + this.coffs[i + 3] * this.coffs[j + 3] + this.coffs[i + 6] * this.coffs[j + 6];
1027
- // const c2 = uDotU - vDotV;
1028
- // const s2 = 2.0 * uDotV;
1119
+ const uDotU = this.coffs[i] * this.coffs[i]
1120
+ + this.coffs[i + 3] * this.coffs[i + 3]
1121
+ + this.coffs[i + 6] * this.coffs[i + 6];
1122
+ const vDotV = this.coffs[j] * this.coffs[j]
1123
+ + this.coffs[j + 3] * this.coffs[j + 3]
1124
+ + this.coffs[j + 6] * this.coffs[j + 6];
1125
+ const uDotV = this.coffs[i] * this.coffs[j]
1126
+ + this.coffs[i + 3] * this.coffs[j + 3]
1127
+ + this.coffs[i + 6] * this.coffs[j + 6];
1029
1128
  const jacobi = Angle_1.Angle.trigValuesToHalfAngleTrigValues(uDotU - vDotV, 2.0 * uDotV);
1030
- // const h = Math.hypot(c2, s2);
1031
- // console.log(" c2 s2", c2 / h, s2 / h);
1032
- // console.log(" C S ", Math.cos(2 * jacobi.radians), Math.sin(2 * jacobi.radians));
1033
- // console.log("i j uDotV", i, j, uDotV);
1034
- if (Math.abs(jacobi.s) < 2.0e-15)
1129
+ const c = jacobi.c;
1130
+ const s = jacobi.s;
1131
+ if (Math.abs(s) < 2.0e-15)
1035
1132
  return 0.0;
1036
- this.applyGivensColumnOp(i, j, jacobi.c, jacobi.s);
1037
- matrixU.applyGivensRowOp(i, j, jacobi.c, jacobi.s);
1038
- // const BTB = this.multiplyMatrixTransposeMatrix(this);
1039
- // console.log("BTB", BTB.at(0, 0), BTB.at(1, 1), BTB.at(2, 2), " off", BTB.at(0, 1), BTB.at(0, 2), BTB.at(1, 2), " at(i,j)", BTB.at(i, j));
1133
+ this.applyGivensColumnOp(i, j, c, s); // make columns i and j of `this` matrix perpendicular
1134
+ matrixU.applyGivensRowOp(i, j, c, s); // right eigenvalues of `thisTranspose * this`
1040
1135
  return Math.abs(uDotV);
1041
1136
  }
1042
1137
  /**
1043
- * Factor this as a product C * U where C has mutually perpendicular columns and
1044
- * U is orthogonal.
1045
- * @param matrixC (allocate by caller, computed here)
1046
- * @param matrixU (allocate by caller, computed here)
1138
+ * Factor `this` matrix as a product `VD * U` where `VD` has mutually perpendicular columns and `U` is orthogonal.
1139
+ * @param matrixVD a matrix that its columns will be filled by rotating columns of `this` to make them mutually
1140
+ * perpendicular (allocated by caller, computed and filled by this function).
1141
+ * @param matrixU a matrix that its columns will be filled by the right eigenvectors of `thisTranspose * this`
1142
+ * (allocated by caller, computed and filled by this function). Note that columns of matrixU will be mutually
1143
+ * perpendicular because `thisTranspose * this` matrix is symmetric.
1047
1144
  */
1048
- factorPerpendicularColumns(matrixC, matrixU) {
1049
- matrixC.setFrom(this);
1145
+ factorPerpendicularColumns(matrixVD, matrixU) {
1146
+ matrixVD.setFrom(this);
1050
1147
  matrixU.setIdentity();
1051
1148
  const tolerance = 1.0e-12 * this.sumSquares();
1052
- for (let iteration = 0; iteration < 7; iteration++) {
1053
- const sum = matrixC.applyJacobiColumnRotation(0, 1, matrixU)
1054
- + matrixC.applyJacobiColumnRotation(0, 2, matrixU)
1055
- + matrixC.applyJacobiColumnRotation(1, 2, matrixU);
1056
- // console.log (" sum", sum);
1149
+ const numberOfIterations = 7;
1150
+ for (let iteration = 0; iteration < numberOfIterations; iteration++) {
1151
+ const sum = matrixVD.applyJacobiColumnRotation(0, 1, matrixU)
1152
+ + matrixVD.applyJacobiColumnRotation(0, 2, matrixU)
1153
+ + matrixVD.applyJacobiColumnRotation(1, 2, matrixU);
1057
1154
  if (sum < tolerance) {
1058
- // console.log("jacobi iterations", iteration);
1059
1155
  return true;
1060
1156
  }
1061
1157
  }
1062
1158
  return false;
1063
1159
  }
1064
1160
  /**
1065
- * Factor this matrix M as a product M = V * D * U where V and U are orthogonal, and D is diagonal (scale matrix).
1066
- * @param matrixV left orthogonal factor (allocate by caller, computed here)
1067
- * @param scale diagonal entries of D (allocate by caller, computed here)
1068
- * @param matrixU right orthogonal factor (allocate by caller, computed here)
1161
+ * Factor `this` matrix as a product `V * D * U` where `V` and `U` are orthogonal and `D` is diagonal with
1162
+ * positive entries.
1163
+ * * This is formally known as the `Singular Value Decomposition` or `SVD`.
1164
+ * @param matrixV an orthogonal matrix that its columns will be filled by the left eigenvectors of
1165
+ * `thisTranspose * this` (allocated by caller, computed and filled by this function).
1166
+ * @param scale singular values of `this` (allocated by caller, computed and filled by this function).
1167
+ * The singular values in the `scale` are non-negative and decreasing.
1168
+ * @param matrixU an orthogonal matrix that its columns will be filled by the right eigenvectors of
1169
+ * `thisTranspose * this` (allocated by caller, computed and filled by this function).
1069
1170
  */
1070
1171
  factorOrthogonalScaleOrthogonal(matrixV, scale, matrixU) {
1071
1172
  const matrixVD = Matrix3d.createZero();
@@ -1075,7 +1176,7 @@ class Matrix3d {
1075
1176
  column.push(matrixVD.getColumn(0));
1076
1177
  column.push(matrixVD.getColumn(1));
1077
1178
  column.push(matrixVD.getColumn(2));
1078
- scale.set(column[0].magnitude(), column[1].magnitude(), column[2].magnitude());
1179
+ scale.set(column[0].magnitude(), column[1].magnitude(), column[2].magnitude()); // singular values of `this`
1079
1180
  const det = matrixVD.determinant();
1080
1181
  if (det < 0)
1081
1182
  scale.z = -scale.z;
@@ -1083,7 +1184,7 @@ class Matrix3d {
1083
1184
  const scaleXIsZero = Math.abs(scale.x) < almostZero;
1084
1185
  const scaleYIsZero = Math.abs(scale.y) < almostZero;
1085
1186
  const scaleZIsZero = Math.abs(scale.z) < almostZero;
1086
- // ASSUME: any zero-magnitude column(s) of matrixVD are last
1187
+ // NOTE: We assume any zero-magnitude column(s) of matrixVD are last
1087
1188
  if (!scaleXIsZero && !scaleYIsZero && !scaleZIsZero) { // full rank
1088
1189
  matrixV = matrixVD.scaleColumns(1 / scale.x, 1 / scale.y, 1 / scale.z, matrixV);
1089
1190
  }
@@ -1101,59 +1202,6 @@ class Matrix3d {
1101
1202
  }
1102
1203
  return true;
1103
1204
  }
1104
- /** Apply a jacobi step to lambda which evolves towards diagonal. */
1105
- applySymmetricJacobi(i, j, lambda) {
1106
- const uDotU = lambda.at(i, i);
1107
- const vDotV = lambda.at(j, j);
1108
- const uDotV = lambda.at(i, j);
1109
- if (Math.abs(uDotV) < 1.0e-15 * (uDotU + vDotV))
1110
- return 0.0;
1111
- // const c2 = uDotU - vDotV;
1112
- // const s2 = 2.0 * uDotV;
1113
- const jacobi = Angle_1.Angle.trigValuesToHalfAngleTrigValues(uDotU - vDotV, 2.0 * uDotV);
1114
- // const h = Math.hypot(c2, s2);
1115
- // console.log(" c2 s2", c2 / h, s2 / h);
1116
- // console.log(" C S ", Math.cos(2 * jacobi.radians), Math.sin(2 * jacobi.radians));
1117
- // console.log("i j uDotV", i, j, uDotV);
1118
- if (Math.abs(jacobi.s) < 2.0e-15)
1119
- return 0.0;
1120
- // Factored form is this *lambda * thisTranspose
1121
- // Let Q be the rotation matrix. Q*QT is inserted, viz
1122
- // this*Q * QT * lambda * Q*thisTranspose
1123
- this.applyGivensColumnOp(i, j, jacobi.c, jacobi.s);
1124
- lambda.applyGivensRowOp(i, j, jacobi.c, jacobi.s);
1125
- lambda.applyGivensColumnOp(i, j, jacobi.c, jacobi.s);
1126
- // const BTB = this.multiplyMatrixTransposeMatrix(this);
1127
- // console.log("BTB", BTB.at(0, 0), BTB.at(1, 1), BTB.at(2, 2), " off", BTB.at(0, 1), BTB.at(0, 2), BTB.at(1, 2), " at(i,j)", BTB.at(i, j));
1128
- return Math.abs(uDotV);
1129
- }
1130
- /**
1131
- * Factor this (symmetrized) as a product U * lambda * UT where U is orthogonal, lambda is diagonal.
1132
- * The upper triangle is mirrored to lower triangle to enforce symmetry.
1133
- * @param matrixC (allocate by caller, computed here)
1134
- * @param factor (allocate by caller, computed here)
1135
- */
1136
- symmetricEigenvalues(leftEigenvectors, lambda) {
1137
- const matrix = this.clone();
1138
- leftEigenvectors.setIdentity();
1139
- matrix.coffs[3] = matrix.coffs[1];
1140
- matrix.coffs[6] = matrix.coffs[2];
1141
- matrix.coffs[7] = matrix.coffs[5];
1142
- const tolerance = 1.0e-12 * this.sumSquares();
1143
- for (let iteration = 0; iteration < 7; iteration++) {
1144
- const sum = leftEigenvectors.applySymmetricJacobi(0, 1, matrix)
1145
- + leftEigenvectors.applySymmetricJacobi(0, 2, matrix)
1146
- + leftEigenvectors.applySymmetricJacobi(1, 2, matrix);
1147
- // console.log("symmetric sum", sum);
1148
- // console.log (" sum", sum);
1149
- if (sum < tolerance) {
1150
- // console.log("symmetric iterations", iteration);
1151
- lambda.set(matrix.at(0, 0), matrix.at(1, 1), matrix.at(2, 2));
1152
- return true;
1153
- }
1154
- }
1155
- return false;
1156
- }
1157
1205
  /**
1158
1206
  * Return a matrix that rotates a fraction of the angular sweep from vectorA to vectorB.
1159
1207
  * @param vectorA initial vector position
@@ -2251,8 +2299,7 @@ class Matrix3d {
2251
2299
  this.inverseState = InverseMatrixState.unknown;
2252
2300
  }
2253
2301
  /**
2254
- * Create a rigid matrix (columns and rows are unit length and pairwise perpendicular) for
2255
- * the given eye coordinate.
2302
+ * Create a rigid matrix (columns and rows are unit length and pairwise perpendicular) for the given eye coordinate.
2256
2303
  * * column 2 is parallel to (x,y,z).
2257
2304
  * * column 0 is perpendicular to column 2 and is in the xy plane.
2258
2305
  * * column 1 is perpendicular to both. It is the "up" vector on the view plane.
@@ -2285,7 +2332,8 @@ class Matrix3d {
2285
2332
  *
2286
2333
  * This is an orthogonal matrix meaning it rotates the standard XYZ axis to ABC axis system
2287
2334
  * (if matrix is [A B C]). The matrix rotates (0,0,1), i.e., the default Top view or Z axis,
2288
- * to the eye point (x/r,y/r,z/r). The matrix also rotates (1,0,0) to a point on XY plane.
2335
+ * to the eye point (x/r,y/r,z/r) where r = sqrt(x*x + y*y + z*z). The matrix also rotates
2336
+ * (1,0,0) to a point on XY plane.
2289
2337
  */
2290
2338
  const c = x / rxy;
2291
2339
  const s = y / rxy;
@@ -2365,6 +2413,15 @@ class Matrix3d {
2365
2413
  const sumOff = Math.abs(sumAll - sumDiagonal);
2366
2414
  return Math.sqrt(sumOff) <= Geometry_1.Geometry.smallAngleRadians * (1.0 + Math.sqrt(sumAll));
2367
2415
  }
2416
+ /** Sum of squared differences between symmetric pairs (symmetric pairs have indices (1,3), (2,6), and (5,7).) */
2417
+ sumSkewSquares() {
2418
+ return Geometry_1.Geometry.hypotenuseSquaredXYZ(this.coffs[1] - this.coffs[3], this.coffs[2] - this.coffs[6], this.coffs[5] - this.coffs[7]);
2419
+ }
2420
+ /** Test if the matrix is (very near to) symmetric */
2421
+ isSymmetric() {
2422
+ const offDiagonal = this.sumSkewSquares();
2423
+ return Math.sqrt(offDiagonal) <= Geometry_1.Geometry.smallAngleRadians * (1.0 + Math.sqrt(this.sumSquares()));
2424
+ }
2368
2425
  /** Test if the stored inverse is present and marked valid */
2369
2426
  get hasCachedInverse() {
2370
2427
  return this.inverseState === InverseMatrixState.inverseStored && this.inverseCoffs !== undefined;
@@ -2378,7 +2435,7 @@ class Matrix3d {
2378
2435
  /** Test if the above diagonal entries (1,2,5) are all nearly zero */
2379
2436
  get isLowerTriangular() {
2380
2437
  const sumAll = this.sumSquares();
2381
- const sumLow = Geometry_1.Geometry.hypotenuseSquaredXYZ(this.coffs[1], this.coffs[2], this.coffs[75]);
2438
+ const sumLow = Geometry_1.Geometry.hypotenuseSquaredXYZ(this.coffs[1], this.coffs[2], this.coffs[5]);
2382
2439
  return Math.sqrt(sumLow) <= Geometry_1.Geometry.smallAngleRadians * (1.0 + Math.sqrt(sumAll));
2383
2440
  }
2384
2441
  /**
@@ -2395,10 +2452,6 @@ class Matrix3d {
2395
2452
  return this.coffs[0];
2396
2453
  return undefined;
2397
2454
  }
2398
- /** Sum of squared differences between symmetric pairs (entry 1 and 3 - 2 and 6 - 5 and 7) */
2399
- sumSkewSquares() {
2400
- return Geometry_1.Geometry.hypotenuseSquaredXYZ(this.coffs[1] - this.coffs[3], this.coffs[2] - this.coffs[6], this.coffs[5] - this.coffs[7]);
2401
- }
2402
2455
  /**
2403
2456
  * Test if all rows and columns are unit length and are perpendicular to each other, i.e., the matrix is either
2404
2457
  * a `pure rotation` (determinant is +1) or is a `mirror` (determinant is -1).