@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.
- package/lib/cjs/Geometry.d.ts +7 -6
- package/lib/cjs/Geometry.d.ts.map +1 -1
- package/lib/cjs/Geometry.js +7 -6
- package/lib/cjs/Geometry.js.map +1 -1
- package/lib/cjs/geometry3d/Matrix3d.d.ts +86 -38
- package/lib/cjs/geometry3d/Matrix3d.d.ts.map +1 -1
- package/lib/cjs/geometry3d/Matrix3d.js +183 -130
- package/lib/cjs/geometry3d/Matrix3d.js.map +1 -1
- package/lib/cjs/geometry3d/OrderedRotationAngles.d.ts +1 -0
- package/lib/cjs/geometry3d/OrderedRotationAngles.d.ts.map +1 -1
- package/lib/cjs/geometry3d/OrderedRotationAngles.js +1 -0
- package/lib/cjs/geometry3d/OrderedRotationAngles.js.map +1 -1
- package/lib/cjs/geometry3d/Point3dVector3d.d.ts.map +1 -1
- package/lib/cjs/geometry3d/Point3dVector3d.js.map +1 -1
- package/lib/esm/Geometry.d.ts +7 -6
- package/lib/esm/Geometry.d.ts.map +1 -1
- package/lib/esm/Geometry.js +7 -6
- package/lib/esm/Geometry.js.map +1 -1
- package/lib/esm/geometry3d/Matrix3d.d.ts +86 -38
- package/lib/esm/geometry3d/Matrix3d.d.ts.map +1 -1
- package/lib/esm/geometry3d/Matrix3d.js +183 -130
- package/lib/esm/geometry3d/Matrix3d.js.map +1 -1
- package/lib/esm/geometry3d/OrderedRotationAngles.d.ts +1 -0
- package/lib/esm/geometry3d/OrderedRotationAngles.d.ts.map +1 -1
- package/lib/esm/geometry3d/OrderedRotationAngles.js +1 -0
- package/lib/esm/geometry3d/OrderedRotationAngles.js.map +1 -1
- package/lib/esm/geometry3d/Point3dVector3d.d.ts.map +1 -1
- package/lib/esm/geometry3d/Point3dVector3d.js.map +1 -1
- package/package.json +3 -3
|
@@ -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
|
-
/**
|
|
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
|
|
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
|
|
892
|
-
*
|
|
893
|
-
*
|
|
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
|
-
|
|
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
|
|
902
|
-
const
|
|
903
|
-
const
|
|
904
|
-
const
|
|
905
|
-
if (Math.abs(
|
|
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 *
|
|
913
|
-
this.coffs[indexJJ] = ss *
|
|
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] =
|
|
1002
|
+
this.coffs[indexIK] = c * a + s * b;
|
|
918
1003
|
this.coffs[indexJK] = -s * a + c * b;
|
|
919
|
-
this.coffs[
|
|
920
|
-
this.coffs[
|
|
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(
|
|
1007
|
+
return Math.abs(sij);
|
|
924
1008
|
}
|
|
925
1009
|
/**
|
|
926
|
-
* Factor this
|
|
927
|
-
*
|
|
928
|
-
*
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
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
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
+ matrix.
|
|
941
|
-
|
|
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
|
|
953
|
-
* *
|
|
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
|
-
/**
|
|
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]
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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,
|
|
1037
|
-
matrixU.applyGivensRowOp(i, j,
|
|
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
|
|
1044
|
-
*
|
|
1045
|
-
*
|
|
1046
|
-
* @param matrixU
|
|
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(
|
|
1049
|
-
|
|
1145
|
+
factorPerpendicularColumns(matrixVD, matrixU) {
|
|
1146
|
+
matrixVD.setFrom(this);
|
|
1050
1147
|
matrixU.setIdentity();
|
|
1051
1148
|
const tolerance = 1.0e-12 * this.sumSquares();
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
+
|
|
1056
|
-
|
|
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
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1068
|
-
* @param
|
|
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
|
-
//
|
|
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
|
-
*
|
|
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[
|
|
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).
|