@pawells/math-extended 1.0.1
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/LICENSE +21 -0
- package/README.md +319 -0
- package/build/angles.d.ts +31 -0
- package/build/angles.d.ts.map +1 -0
- package/build/angles.js +85 -0
- package/build/angles.js.map +1 -0
- package/build/angles.spec.d.ts +2 -0
- package/build/angles.spec.d.ts.map +1 -0
- package/build/angles.spec.js +147 -0
- package/build/angles.spec.js.map +1 -0
- package/build/clamp.d.ts +17 -0
- package/build/clamp.d.ts.map +1 -0
- package/build/clamp.js +19 -0
- package/build/clamp.js.map +1 -0
- package/build/clamp.spec.d.ts +2 -0
- package/build/clamp.spec.d.ts.map +1 -0
- package/build/clamp.spec.js +19 -0
- package/build/clamp.spec.js.map +1 -0
- package/build/documentation-validation.spec.d.ts +11 -0
- package/build/documentation-validation.spec.d.ts.map +1 -0
- package/build/documentation-validation.spec.js +401 -0
- package/build/documentation-validation.spec.js.map +1 -0
- package/build/index.d.ts +8 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +8 -0
- package/build/index.js.map +1 -0
- package/build/interpolation.d.ts +175 -0
- package/build/interpolation.d.ts.map +1 -0
- package/build/interpolation.js +369 -0
- package/build/interpolation.js.map +1 -0
- package/build/interpolation.spec.d.ts +2 -0
- package/build/interpolation.spec.d.ts.map +1 -0
- package/build/interpolation.spec.js +480 -0
- package/build/interpolation.spec.js.map +1 -0
- package/build/matrices/arithmetic.d.ts +411 -0
- package/build/matrices/arithmetic.d.ts.map +1 -0
- package/build/matrices/arithmetic.js +954 -0
- package/build/matrices/arithmetic.js.map +1 -0
- package/build/matrices/arithmetic.spec.d.ts +2 -0
- package/build/matrices/arithmetic.spec.d.ts.map +1 -0
- package/build/matrices/arithmetic.spec.js +915 -0
- package/build/matrices/arithmetic.spec.js.map +1 -0
- package/build/matrices/asserts.d.ts +306 -0
- package/build/matrices/asserts.d.ts.map +1 -0
- package/build/matrices/asserts.js +396 -0
- package/build/matrices/asserts.js.map +1 -0
- package/build/matrices/asserts.spec.d.ts +2 -0
- package/build/matrices/asserts.spec.d.ts.map +1 -0
- package/build/matrices/asserts.spec.js +565 -0
- package/build/matrices/asserts.spec.js.map +1 -0
- package/build/matrices/core.d.ts +168 -0
- package/build/matrices/core.d.ts.map +1 -0
- package/build/matrices/core.js +457 -0
- package/build/matrices/core.js.map +1 -0
- package/build/matrices/core.spec.d.ts +2 -0
- package/build/matrices/core.spec.d.ts.map +1 -0
- package/build/matrices/core.spec.js +634 -0
- package/build/matrices/core.spec.js.map +1 -0
- package/build/matrices/decompositions.d.ts +326 -0
- package/build/matrices/decompositions.d.ts.map +1 -0
- package/build/matrices/decompositions.js +816 -0
- package/build/matrices/decompositions.js.map +1 -0
- package/build/matrices/decompositions.spec.d.ts +2 -0
- package/build/matrices/decompositions.spec.d.ts.map +1 -0
- package/build/matrices/decompositions.spec.js +195 -0
- package/build/matrices/decompositions.spec.js.map +1 -0
- package/build/matrices/index.d.ts +9 -0
- package/build/matrices/index.d.ts.map +1 -0
- package/build/matrices/index.js +9 -0
- package/build/matrices/index.js.map +1 -0
- package/build/matrices/linear-algebra.d.ts +64 -0
- package/build/matrices/linear-algebra.d.ts.map +1 -0
- package/build/matrices/linear-algebra.js +253 -0
- package/build/matrices/linear-algebra.js.map +1 -0
- package/build/matrices/linear-algebra.spec.d.ts +2 -0
- package/build/matrices/linear-algebra.spec.d.ts.map +1 -0
- package/build/matrices/linear-algebra.spec.js +355 -0
- package/build/matrices/linear-algebra.spec.js.map +1 -0
- package/build/matrices/normalization.d.ts +62 -0
- package/build/matrices/normalization.d.ts.map +1 -0
- package/build/matrices/normalization.js +167 -0
- package/build/matrices/normalization.js.map +1 -0
- package/build/matrices/normalization.spec.d.ts +2 -0
- package/build/matrices/normalization.spec.d.ts.map +1 -0
- package/build/matrices/normalization.spec.js +335 -0
- package/build/matrices/normalization.spec.js.map +1 -0
- package/build/matrices/transformations.d.ts +484 -0
- package/build/matrices/transformations.d.ts.map +1 -0
- package/build/matrices/transformations.js +592 -0
- package/build/matrices/transformations.js.map +1 -0
- package/build/matrices/transformations.spec.d.ts +2 -0
- package/build/matrices/transformations.spec.d.ts.map +1 -0
- package/build/matrices/transformations.spec.js +755 -0
- package/build/matrices/transformations.spec.js.map +1 -0
- package/build/matrices/types.d.ts +134 -0
- package/build/matrices/types.d.ts.map +1 -0
- package/build/matrices/types.js +6 -0
- package/build/matrices/types.js.map +1 -0
- package/build/quaternions/asserts.d.ts +77 -0
- package/build/quaternions/asserts.d.ts.map +1 -0
- package/build/quaternions/asserts.js +175 -0
- package/build/quaternions/asserts.js.map +1 -0
- package/build/quaternions/asserts.spec.d.ts +2 -0
- package/build/quaternions/asserts.spec.d.ts.map +1 -0
- package/build/quaternions/asserts.spec.js +320 -0
- package/build/quaternions/asserts.spec.js.map +1 -0
- package/build/quaternions/conversions.d.ts +73 -0
- package/build/quaternions/conversions.d.ts.map +1 -0
- package/build/quaternions/conversions.js +179 -0
- package/build/quaternions/conversions.js.map +1 -0
- package/build/quaternions/conversions.spec.d.ts +2 -0
- package/build/quaternions/conversions.spec.d.ts.map +1 -0
- package/build/quaternions/conversions.spec.js +344 -0
- package/build/quaternions/conversions.spec.js.map +1 -0
- package/build/quaternions/core.d.ts +203 -0
- package/build/quaternions/core.d.ts.map +1 -0
- package/build/quaternions/core.js +374 -0
- package/build/quaternions/core.js.map +1 -0
- package/build/quaternions/core.spec.d.ts +2 -0
- package/build/quaternions/core.spec.d.ts.map +1 -0
- package/build/quaternions/core.spec.js +294 -0
- package/build/quaternions/core.spec.js.map +1 -0
- package/build/quaternions/index.d.ts +7 -0
- package/build/quaternions/index.d.ts.map +1 -0
- package/build/quaternions/index.js +7 -0
- package/build/quaternions/index.js.map +1 -0
- package/build/quaternions/interpolation.d.ts +54 -0
- package/build/quaternions/interpolation.d.ts.map +1 -0
- package/build/quaternions/interpolation.js +201 -0
- package/build/quaternions/interpolation.js.map +1 -0
- package/build/quaternions/interpolation.spec.d.ts +2 -0
- package/build/quaternions/interpolation.spec.d.ts.map +1 -0
- package/build/quaternions/interpolation.spec.js +64 -0
- package/build/quaternions/interpolation.spec.js.map +1 -0
- package/build/quaternions/predefined.d.ts +36 -0
- package/build/quaternions/predefined.d.ts.map +1 -0
- package/build/quaternions/predefined.js +42 -0
- package/build/quaternions/predefined.js.map +1 -0
- package/build/quaternions/predefined.spec.d.ts +2 -0
- package/build/quaternions/predefined.spec.d.ts.map +1 -0
- package/build/quaternions/predefined.spec.js +35 -0
- package/build/quaternions/predefined.spec.js.map +1 -0
- package/build/quaternions/types.d.ts +55 -0
- package/build/quaternions/types.d.ts.map +1 -0
- package/build/quaternions/types.js +7 -0
- package/build/quaternions/types.js.map +1 -0
- package/build/random.d.ts +66 -0
- package/build/random.d.ts.map +1 -0
- package/build/random.js +115 -0
- package/build/random.js.map +1 -0
- package/build/random.spec.d.ts +2 -0
- package/build/random.spec.d.ts.map +1 -0
- package/build/random.spec.js +267 -0
- package/build/random.spec.js.map +1 -0
- package/build/vectors/asserts.d.ts +182 -0
- package/build/vectors/asserts.d.ts.map +1 -0
- package/build/vectors/asserts.js +285 -0
- package/build/vectors/asserts.js.map +1 -0
- package/build/vectors/asserts.spec.d.ts +2 -0
- package/build/vectors/asserts.spec.d.ts.map +1 -0
- package/build/vectors/asserts.spec.js +260 -0
- package/build/vectors/asserts.spec.js.map +1 -0
- package/build/vectors/core.d.ts +507 -0
- package/build/vectors/core.d.ts.map +1 -0
- package/build/vectors/core.js +825 -0
- package/build/vectors/core.js.map +1 -0
- package/build/vectors/core.spec.d.ts +2 -0
- package/build/vectors/core.spec.d.ts.map +1 -0
- package/build/vectors/core.spec.js +343 -0
- package/build/vectors/core.spec.js.map +1 -0
- package/build/vectors/index.d.ts +6 -0
- package/build/vectors/index.d.ts.map +1 -0
- package/build/vectors/index.js +6 -0
- package/build/vectors/index.js.map +1 -0
- package/build/vectors/interpolation.d.ts +404 -0
- package/build/vectors/interpolation.d.ts.map +1 -0
- package/build/vectors/interpolation.js +585 -0
- package/build/vectors/interpolation.js.map +1 -0
- package/build/vectors/interpolation.spec.d.ts +2 -0
- package/build/vectors/interpolation.spec.d.ts.map +1 -0
- package/build/vectors/interpolation.spec.js +378 -0
- package/build/vectors/interpolation.spec.js.map +1 -0
- package/build/vectors/predefined.d.ts +191 -0
- package/build/vectors/predefined.d.ts.map +1 -0
- package/build/vectors/predefined.js +191 -0
- package/build/vectors/predefined.js.map +1 -0
- package/build/vectors/predefined.spec.d.ts +2 -0
- package/build/vectors/predefined.spec.d.ts.map +1 -0
- package/build/vectors/predefined.spec.js +333 -0
- package/build/vectors/predefined.spec.js.map +1 -0
- package/build/vectors/types.d.ts +62 -0
- package/build/vectors/types.d.ts.map +1 -0
- package/build/vectors/types.js +6 -0
- package/build/vectors/types.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
import { MatrixRotation2D, MatrixRotation3DRoll, MatrixRotation3DPitch, MatrixRotation3DYaw, MatrixRotation3D, MatrixRotation3DEulerAngles, MatrixScale2D, MatrixScale3D, MatrixTranslation2D, MatrixTranslation3D, MatrixTransform2D, MatrixTransform3D, MatrixDirection3D, MatrixView, MatrixPerspective, MatrixOrthographic, } from './transformations.js';
|
|
2
|
+
import { DegreesToRadians } from '../angles.ts';
|
|
3
|
+
import { AssertMatrixRow, AssertMatrixValue } from './asserts.js';
|
|
4
|
+
import { MatrixIdentity } from './core.js';
|
|
5
|
+
import { VectorMagnitude } from '../vectors/core.ts';
|
|
6
|
+
describe('Matrix Transformations', () => {
|
|
7
|
+
// Helper function to check if matrices are approximately equal
|
|
8
|
+
function expectMatrixToBeCloseTo(actual, expected, precision = 5) {
|
|
9
|
+
expect(actual.length).toBe(expected.length);
|
|
10
|
+
for (let i = 0; i < actual.length; i++) {
|
|
11
|
+
const actualRow = actual[i];
|
|
12
|
+
expect(actualRow).toBeDefined();
|
|
13
|
+
AssertMatrixRow(actualRow);
|
|
14
|
+
const expectedRow = expected[i];
|
|
15
|
+
expect(expectedRow).toBeDefined();
|
|
16
|
+
AssertMatrixRow(expectedRow);
|
|
17
|
+
const actualRowLength = actualRow.length;
|
|
18
|
+
const expectedRowLength = expectedRow.length;
|
|
19
|
+
expect(actualRowLength).toBe(expectedRowLength);
|
|
20
|
+
for (let j = 0; j < actualRow.length; j++) {
|
|
21
|
+
const actualValue = actualRow[j];
|
|
22
|
+
AssertMatrixValue(actualValue);
|
|
23
|
+
const expectedValue = expectedRow[j];
|
|
24
|
+
AssertMatrixValue(expectedValue);
|
|
25
|
+
expect(actualValue).toBeCloseTo(expectedValue, precision);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
describe('2D Rotation', () => {
|
|
30
|
+
describe('MatrixRotation2D', () => {
|
|
31
|
+
test('should create identity matrix for 0 radians', () => {
|
|
32
|
+
const matrix = MatrixRotation2D(0);
|
|
33
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
34
|
+
[1, 0, 0],
|
|
35
|
+
[0, 1, 0],
|
|
36
|
+
[0, 0, 1],
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
test('should create 90-degree rotation matrix', () => {
|
|
40
|
+
const matrix = MatrixRotation2D(Math.PI / 2);
|
|
41
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
42
|
+
[0, -1, 0],
|
|
43
|
+
[1, 0, 0],
|
|
44
|
+
[0, 0, 1],
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
test('should create 180-degree rotation matrix', () => {
|
|
48
|
+
const matrix = MatrixRotation2D(Math.PI);
|
|
49
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
50
|
+
[-1, 0, 0],
|
|
51
|
+
[0, -1, 0],
|
|
52
|
+
[0, 0, 1],
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
test('should create 270-degree rotation matrix', () => {
|
|
56
|
+
const matrix = MatrixRotation2D(3 * Math.PI / 2);
|
|
57
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
58
|
+
[0, 1, 0],
|
|
59
|
+
[-1, 0, 0],
|
|
60
|
+
[0, 0, 1],
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
63
|
+
test('should handle negative angles', () => {
|
|
64
|
+
const matrix = MatrixRotation2D(-Math.PI / 2);
|
|
65
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
66
|
+
[0, 1, 0],
|
|
67
|
+
[-1, 0, 0],
|
|
68
|
+
[0, 0, 1],
|
|
69
|
+
]);
|
|
70
|
+
});
|
|
71
|
+
test('should throw for invalid input', () => {
|
|
72
|
+
// @ts-expect-error Argument of type 'string' is not assignable to parameter of type 'number'. This should throw at runtime.
|
|
73
|
+
expect(() => MatrixRotation2D('invalid')).toThrow('Rotation angle must be a number');
|
|
74
|
+
expect(() => MatrixRotation2D(NaN)).toThrow('Rotation angle must be a number');
|
|
75
|
+
expect(() => MatrixRotation2D(Infinity)).toThrow('Rotation angle must be a number');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('3D Rotations', () => {
|
|
80
|
+
describe('MatrixRotation3DRoll', () => {
|
|
81
|
+
test('should create identity matrix for 0 radians', () => {
|
|
82
|
+
const matrix = MatrixRotation3DRoll(0);
|
|
83
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
84
|
+
[1, 0, 0, 0],
|
|
85
|
+
[0, 1, 0, 0],
|
|
86
|
+
[0, 0, 1, 0],
|
|
87
|
+
[0, 0, 0, 1],
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
test('should create 90-degree roll rotation', () => {
|
|
91
|
+
const matrix = MatrixRotation3DRoll(Math.PI / 2);
|
|
92
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
93
|
+
[1, 0, 0, 0],
|
|
94
|
+
[0, 0, -1, 0],
|
|
95
|
+
[0, 1, 0, 0],
|
|
96
|
+
[0, 0, 0, 1],
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
test('should throw for invalid input', () => {
|
|
100
|
+
expect(() => MatrixRotation3DRoll(NaN)).toThrow('Rotation angle must be a number');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('MatrixRotation3DPitch', () => {
|
|
104
|
+
test('should create identity matrix for 0 radians', () => {
|
|
105
|
+
const matrix = MatrixRotation3DPitch(0);
|
|
106
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
107
|
+
[1, 0, 0, 0],
|
|
108
|
+
[0, 1, 0, 0],
|
|
109
|
+
[0, 0, 1, 0],
|
|
110
|
+
[0, 0, 0, 1],
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
test('should create 90-degree pitch rotation', () => {
|
|
114
|
+
const matrix = MatrixRotation3DPitch(Math.PI / 2);
|
|
115
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
116
|
+
[0, 0, 1, 0],
|
|
117
|
+
[0, 1, 0, 0],
|
|
118
|
+
[-1, 0, 0, 0],
|
|
119
|
+
[0, 0, 0, 1],
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
test('should throw for invalid input', () => {
|
|
123
|
+
expect(() => MatrixRotation3DPitch(NaN)).toThrow('Rotation angle must be a number');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('MatrixRotation3DYaw', () => {
|
|
127
|
+
test('should create identity matrix for 0 radians', () => {
|
|
128
|
+
const matrix = MatrixRotation3DYaw(0);
|
|
129
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
130
|
+
[1, 0, 0, 0],
|
|
131
|
+
[0, 1, 0, 0],
|
|
132
|
+
[0, 0, 1, 0],
|
|
133
|
+
[0, 0, 0, 1],
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
136
|
+
test('should create 90-degree yaw rotation', () => {
|
|
137
|
+
const matrix = MatrixRotation3DYaw(Math.PI / 2);
|
|
138
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
139
|
+
[0, -1, 0, 0],
|
|
140
|
+
[1, 0, 0, 0],
|
|
141
|
+
[0, 0, 1, 0],
|
|
142
|
+
[0, 0, 0, 1],
|
|
143
|
+
]);
|
|
144
|
+
});
|
|
145
|
+
test('should throw for invalid input', () => {
|
|
146
|
+
expect(() => MatrixRotation3DYaw(NaN)).toThrow('Rotation angle must be a number');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('MatrixRotation3D', () => {
|
|
150
|
+
test('should create identity matrix for zero rotation', () => {
|
|
151
|
+
const matrix = MatrixRotation3D(0, 0, 0);
|
|
152
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
153
|
+
[1, 0, 0, 0],
|
|
154
|
+
[0, 1, 0, 0],
|
|
155
|
+
[0, 0, 1, 0],
|
|
156
|
+
[0, 0, 0, 1],
|
|
157
|
+
]);
|
|
158
|
+
});
|
|
159
|
+
test('should handle single axis rotations', () => {
|
|
160
|
+
const rollMatrix = MatrixRotation3D(Math.PI / 2, 0, 0);
|
|
161
|
+
const expectedRoll = MatrixRotation3DRoll(Math.PI / 2);
|
|
162
|
+
expectMatrixToBeCloseTo(rollMatrix, expectedRoll);
|
|
163
|
+
const pitchMatrix = MatrixRotation3D(0, Math.PI / 2, 0);
|
|
164
|
+
const expectedPitch = MatrixRotation3DPitch(Math.PI / 2);
|
|
165
|
+
expectMatrixToBeCloseTo(pitchMatrix, expectedPitch);
|
|
166
|
+
const yawMatrix = MatrixRotation3D(0, 0, Math.PI / 2);
|
|
167
|
+
const expectedYaw = MatrixRotation3DYaw(Math.PI / 2);
|
|
168
|
+
expectMatrixToBeCloseTo(yawMatrix, expectedYaw);
|
|
169
|
+
});
|
|
170
|
+
test('should create complex rotation with all axes', () => {
|
|
171
|
+
const matrix = MatrixRotation3D(DegreesToRadians(60), DegreesToRadians(30), DegreesToRadians(45));
|
|
172
|
+
// Matrix should not be identity
|
|
173
|
+
expect(matrix).not.toEqual([
|
|
174
|
+
[1, 0, 0, 0],
|
|
175
|
+
[0, 1, 0, 0],
|
|
176
|
+
[0, 0, 1, 0],
|
|
177
|
+
[0, 0, 0, 1],
|
|
178
|
+
]);
|
|
179
|
+
// Should be a proper rotation matrix (orthogonal with determinant 1)
|
|
180
|
+
// Basic check: bottom row should be [0, 0, 0, 1]
|
|
181
|
+
expectMatrixToBeCloseTo([matrix[3]], [[0, 0, 0, 1]]);
|
|
182
|
+
});
|
|
183
|
+
test('should handle vector input', () => {
|
|
184
|
+
const eulerAngles = [Math.PI / 4, Math.PI / 6, Math.PI / 3];
|
|
185
|
+
const matrix = MatrixRotation3D(eulerAngles);
|
|
186
|
+
const expected = MatrixRotation3D(Math.PI / 4, Math.PI / 6, Math.PI / 3);
|
|
187
|
+
expectMatrixToBeCloseTo(matrix, expected);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
describe('MatrixRotation3DEulerAngles', () => {
|
|
191
|
+
test('should convert degrees to radians and create rotation matrix', () => {
|
|
192
|
+
const matrix = MatrixRotation3DEulerAngles(60, 30, 45);
|
|
193
|
+
const expected = MatrixRotation3D(DegreesToRadians(60), DegreesToRadians(30), DegreesToRadians(45));
|
|
194
|
+
expectMatrixToBeCloseTo(matrix, expected);
|
|
195
|
+
});
|
|
196
|
+
test('should handle zero degrees', () => {
|
|
197
|
+
const matrix = MatrixRotation3DEulerAngles(0, 0, 0);
|
|
198
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
199
|
+
[1, 0, 0, 0],
|
|
200
|
+
[0, 1, 0, 0],
|
|
201
|
+
[0, 0, 1, 0],
|
|
202
|
+
[0, 0, 0, 1],
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe('Scaling Transformations', () => {
|
|
208
|
+
describe('Matrix_Transformation_Scale2D', () => {
|
|
209
|
+
test('should create identity scale for (1, 1)', () => {
|
|
210
|
+
const matrix = MatrixScale2D(1, 1);
|
|
211
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
212
|
+
[1, 0, 0],
|
|
213
|
+
[0, 1, 0],
|
|
214
|
+
[0, 0, 1],
|
|
215
|
+
]);
|
|
216
|
+
});
|
|
217
|
+
test('should create proper scale matrix', () => {
|
|
218
|
+
const matrix = MatrixScale2D(2, 3);
|
|
219
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
220
|
+
[2, 0, 0],
|
|
221
|
+
[0, 3, 0],
|
|
222
|
+
[0, 0, 1],
|
|
223
|
+
]);
|
|
224
|
+
});
|
|
225
|
+
test('should handle negative scaling (flipping)', () => {
|
|
226
|
+
const matrix = MatrixScale2D(-1, 1);
|
|
227
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
228
|
+
[-1, 0, 0],
|
|
229
|
+
[0, 1, 0],
|
|
230
|
+
[0, 0, 1],
|
|
231
|
+
]);
|
|
232
|
+
});
|
|
233
|
+
test('should handle zero scaling', () => {
|
|
234
|
+
const matrix = MatrixScale2D(0, 1);
|
|
235
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
236
|
+
[0, 0, 0],
|
|
237
|
+
[0, 1, 0],
|
|
238
|
+
[0, 0, 1],
|
|
239
|
+
]);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
describe('Matrix_Transformation_Scale3D', () => {
|
|
243
|
+
test('should create identity scale for (1, 1, 1)', () => {
|
|
244
|
+
const matrix = MatrixScale3D(1, 1, 1);
|
|
245
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
246
|
+
[1, 0, 0, 0],
|
|
247
|
+
[0, 1, 0, 0],
|
|
248
|
+
[0, 0, 1, 0],
|
|
249
|
+
[0, 0, 0, 1],
|
|
250
|
+
]);
|
|
251
|
+
});
|
|
252
|
+
test('should create proper 3D scale matrix', () => {
|
|
253
|
+
const matrix = MatrixScale3D(2, 3, 4);
|
|
254
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
255
|
+
[2, 0, 0, 0],
|
|
256
|
+
[0, 3, 0, 0],
|
|
257
|
+
[0, 0, 4, 0],
|
|
258
|
+
[0, 0, 0, 1],
|
|
259
|
+
]);
|
|
260
|
+
});
|
|
261
|
+
test('should handle negative scaling', () => {
|
|
262
|
+
const matrix = MatrixScale3D(-1, -1, -1);
|
|
263
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
264
|
+
[-1, 0, 0, 0],
|
|
265
|
+
[0, -1, 0, 0],
|
|
266
|
+
[0, 0, -1, 0],
|
|
267
|
+
[0, 0, 0, 1],
|
|
268
|
+
]);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
describe('Scale Transformations', () => {
|
|
273
|
+
describe('MatrixScale2D', () => {
|
|
274
|
+
test('should create uniform scale matrix', () => {
|
|
275
|
+
const matrix = MatrixScale2D(2);
|
|
276
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
277
|
+
[2, 0, 0],
|
|
278
|
+
[0, 2, 0],
|
|
279
|
+
[0, 0, 1],
|
|
280
|
+
]);
|
|
281
|
+
});
|
|
282
|
+
test('should create identity for scale 1', () => {
|
|
283
|
+
const matrix = MatrixScale2D(1);
|
|
284
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
285
|
+
[1, 0, 0],
|
|
286
|
+
[0, 1, 0],
|
|
287
|
+
[0, 0, 1],
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
test('should create independent scale matrix', () => {
|
|
291
|
+
const matrix = MatrixScale2D(2, 3);
|
|
292
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
293
|
+
[2, 0, 0],
|
|
294
|
+
[0, 3, 0],
|
|
295
|
+
[0, 0, 1],
|
|
296
|
+
]);
|
|
297
|
+
});
|
|
298
|
+
test('should handle vector input', () => {
|
|
299
|
+
const scaleVector = [2, 3];
|
|
300
|
+
const matrix = MatrixScale2D(scaleVector);
|
|
301
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
302
|
+
[2, 0, 0],
|
|
303
|
+
[0, 3, 0],
|
|
304
|
+
[0, 0, 1],
|
|
305
|
+
]);
|
|
306
|
+
});
|
|
307
|
+
test('should handle negative scaling (flipping)', () => {
|
|
308
|
+
const matrix = MatrixScale2D(-1, 1);
|
|
309
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
310
|
+
[-1, 0, 0],
|
|
311
|
+
[0, 1, 0],
|
|
312
|
+
[0, 0, 1],
|
|
313
|
+
]);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
describe('MatrixScale3D', () => {
|
|
317
|
+
test('should create uniform scale matrix', () => {
|
|
318
|
+
const matrix = MatrixScale3D(2);
|
|
319
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
320
|
+
[2, 0, 0, 0],
|
|
321
|
+
[0, 2, 0, 0],
|
|
322
|
+
[0, 0, 2, 0],
|
|
323
|
+
[0, 0, 0, 1],
|
|
324
|
+
]);
|
|
325
|
+
});
|
|
326
|
+
test('should create identity for scale 1', () => {
|
|
327
|
+
const matrix = MatrixScale3D(1);
|
|
328
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
329
|
+
[1, 0, 0, 0],
|
|
330
|
+
[0, 1, 0, 0],
|
|
331
|
+
[0, 0, 1, 0],
|
|
332
|
+
[0, 0, 0, 1],
|
|
333
|
+
]);
|
|
334
|
+
});
|
|
335
|
+
test('should create independent scale matrix', () => {
|
|
336
|
+
const matrix = MatrixScale3D(2, 3, 4);
|
|
337
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
338
|
+
[2, 0, 0, 0],
|
|
339
|
+
[0, 3, 0, 0],
|
|
340
|
+
[0, 0, 4, 0],
|
|
341
|
+
[0, 0, 0, 1],
|
|
342
|
+
]);
|
|
343
|
+
});
|
|
344
|
+
test('should handle vector input', () => {
|
|
345
|
+
const scaleVector = [2, 3, 4];
|
|
346
|
+
const matrix = MatrixScale3D(scaleVector);
|
|
347
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
348
|
+
[2, 0, 0, 0],
|
|
349
|
+
[0, 3, 0, 0],
|
|
350
|
+
[0, 0, 4, 0],
|
|
351
|
+
[0, 0, 0, 1],
|
|
352
|
+
]);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
describe('Translation Transformations', () => {
|
|
357
|
+
describe('MatrixTranslation2D', () => {
|
|
358
|
+
test('should create identity for zero translation', () => {
|
|
359
|
+
const matrix = MatrixTranslation2D(0, 0);
|
|
360
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
361
|
+
[1, 0, 0],
|
|
362
|
+
[0, 1, 0],
|
|
363
|
+
[0, 0, 1],
|
|
364
|
+
]);
|
|
365
|
+
});
|
|
366
|
+
test('should create proper translation matrix', () => {
|
|
367
|
+
const matrix = MatrixTranslation2D(5, 7);
|
|
368
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
369
|
+
[1, 0, 5],
|
|
370
|
+
[0, 1, 7],
|
|
371
|
+
[0, 0, 1],
|
|
372
|
+
]);
|
|
373
|
+
});
|
|
374
|
+
test('should handle negative translation', () => {
|
|
375
|
+
const matrix = MatrixTranslation2D(-3, -4);
|
|
376
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
377
|
+
[1, 0, -3],
|
|
378
|
+
[0, 1, -4],
|
|
379
|
+
[0, 0, 1],
|
|
380
|
+
]);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
describe('MatrixTranslation3D', () => {
|
|
384
|
+
test('should create identity for zero translation', () => {
|
|
385
|
+
const matrix = MatrixTranslation3D(0, 0, 0);
|
|
386
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
387
|
+
[1, 0, 0, 0],
|
|
388
|
+
[0, 1, 0, 0],
|
|
389
|
+
[0, 0, 1, 0],
|
|
390
|
+
[0, 0, 0, 1],
|
|
391
|
+
]);
|
|
392
|
+
});
|
|
393
|
+
test('should create proper 3D translation matrix', () => {
|
|
394
|
+
const matrix = MatrixTranslation3D(1, 2, 3);
|
|
395
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
396
|
+
[1, 0, 0, 1],
|
|
397
|
+
[0, 1, 0, 2],
|
|
398
|
+
[0, 0, 1, 3],
|
|
399
|
+
[0, 0, 0, 1],
|
|
400
|
+
]);
|
|
401
|
+
});
|
|
402
|
+
test('should handle uniform translation', () => {
|
|
403
|
+
const matrix = MatrixTranslation3D(5);
|
|
404
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
405
|
+
[1, 0, 0, 5],
|
|
406
|
+
[0, 1, 0, 5],
|
|
407
|
+
[0, 0, 1, 5],
|
|
408
|
+
[0, 0, 0, 1],
|
|
409
|
+
]);
|
|
410
|
+
});
|
|
411
|
+
test('should handle vector input', () => {
|
|
412
|
+
const translationVector = [1, 2, 3];
|
|
413
|
+
const matrix = MatrixTranslation3D(translationVector[0], translationVector[1], translationVector[2]);
|
|
414
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
415
|
+
[1, 0, 0, 1],
|
|
416
|
+
[0, 1, 0, 2],
|
|
417
|
+
[0, 0, 1, 3],
|
|
418
|
+
[0, 0, 0, 1],
|
|
419
|
+
]);
|
|
420
|
+
});
|
|
421
|
+
test('should handle negative translation', () => {
|
|
422
|
+
const matrix = MatrixTranslation3D(-5, -10, -15);
|
|
423
|
+
expectMatrixToBeCloseTo(matrix, [
|
|
424
|
+
[1, 0, 0, -5],
|
|
425
|
+
[0, 1, 0, -10],
|
|
426
|
+
[0, 0, 1, -15],
|
|
427
|
+
[0, 0, 0, 1],
|
|
428
|
+
]);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
describe('Vector Transformations', () => {
|
|
433
|
+
describe('MatrixTransform2D', () => {
|
|
434
|
+
test('should transform vector with identity matrix', () => {
|
|
435
|
+
const identity = MatrixIdentity(3);
|
|
436
|
+
const vector = [3, 4];
|
|
437
|
+
const result = MatrixTransform2D(vector, identity);
|
|
438
|
+
expect(result[0]).toBeCloseTo(3);
|
|
439
|
+
expect(result[1]).toBeCloseTo(4);
|
|
440
|
+
});
|
|
441
|
+
test('should transform vector with translation', () => {
|
|
442
|
+
const translation = MatrixTranslation2D(2, 3);
|
|
443
|
+
const vector = [1, 1];
|
|
444
|
+
const result = MatrixTransform2D(vector, translation);
|
|
445
|
+
expect(result[0]).toBeCloseTo(3);
|
|
446
|
+
expect(result[1]).toBeCloseTo(4);
|
|
447
|
+
});
|
|
448
|
+
test('should transform vector with rotation', () => {
|
|
449
|
+
const rotation = MatrixRotation2D(Math.PI / 2);
|
|
450
|
+
const vector = [1, 0];
|
|
451
|
+
const result = MatrixTransform2D(vector, rotation);
|
|
452
|
+
expect(result[0]).toBeCloseTo(0, 5);
|
|
453
|
+
expect(result[1]).toBeCloseTo(1, 5);
|
|
454
|
+
});
|
|
455
|
+
test('should transform vector with scaling', () => {
|
|
456
|
+
const scale = MatrixScale2D(2, 3);
|
|
457
|
+
const vector = [4, 5];
|
|
458
|
+
const result = MatrixTransform2D(vector, scale);
|
|
459
|
+
expect(result[0]).toBeCloseTo(8);
|
|
460
|
+
expect(result[1]).toBeCloseTo(15);
|
|
461
|
+
});
|
|
462
|
+
test('should throw for invalid matrix', () => {
|
|
463
|
+
const invalidMatrix = [[1, 0], [0, 1]]; // Wrong size
|
|
464
|
+
// @ts-expect-error Invalid matrix size should cause a type error: MatrixTransform2D expects a 3x3 matrix
|
|
465
|
+
expect(() => MatrixTransform2D([1, 1], invalidMatrix)).toThrow();
|
|
466
|
+
});
|
|
467
|
+
test('should throw for invalid vector', () => {
|
|
468
|
+
const identity = MatrixIdentity(3);
|
|
469
|
+
expect(() => MatrixTransform2D([1], identity)).toThrow();
|
|
470
|
+
});
|
|
471
|
+
test('should throw for degenerate transformation', () => {
|
|
472
|
+
const degenerate = [
|
|
473
|
+
[1, 0, 0],
|
|
474
|
+
[0, 1, 0],
|
|
475
|
+
[0, 0, 0], // w = 0 causes division by zero
|
|
476
|
+
];
|
|
477
|
+
expect(() => MatrixTransform2D([1, 1], degenerate)).toThrow('2D transformation w component near zero');
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
describe('MatrixTransform3D', () => {
|
|
481
|
+
test('should transform vector with identity matrix', () => {
|
|
482
|
+
const identity = MatrixIdentity(4);
|
|
483
|
+
const vector = [3, 4, 5];
|
|
484
|
+
const result = MatrixTransform3D(vector, identity);
|
|
485
|
+
expect(result[0]).toBeCloseTo(3);
|
|
486
|
+
expect(result[1]).toBeCloseTo(4);
|
|
487
|
+
expect(result[2]).toBeCloseTo(5);
|
|
488
|
+
});
|
|
489
|
+
test('should transform vector with translation', () => {
|
|
490
|
+
const translation = MatrixTranslation3D(1, 2, 3);
|
|
491
|
+
const vector = [4, 5, 6];
|
|
492
|
+
const result = MatrixTransform3D(vector, translation);
|
|
493
|
+
expect(result[0]).toBeCloseTo(5);
|
|
494
|
+
expect(result[1]).toBeCloseTo(7);
|
|
495
|
+
expect(result[2]).toBeCloseTo(9);
|
|
496
|
+
});
|
|
497
|
+
test('should transform vector with rotation', () => {
|
|
498
|
+
const rotation = MatrixRotation3DYaw(Math.PI / 2);
|
|
499
|
+
const vector = [1, 0, 0];
|
|
500
|
+
const result = MatrixTransform3D(vector, rotation);
|
|
501
|
+
expect(result[0]).toBeCloseTo(0, 5);
|
|
502
|
+
expect(result[1]).toBeCloseTo(1, 5);
|
|
503
|
+
expect(result[2]).toBeCloseTo(0, 5);
|
|
504
|
+
});
|
|
505
|
+
test('should transform vector with scaling', () => {
|
|
506
|
+
const scale = MatrixScale3D(2, 3, 4);
|
|
507
|
+
const vector = [1, 2, 3];
|
|
508
|
+
const result = MatrixTransform3D(vector, scale);
|
|
509
|
+
expect(result[0]).toBeCloseTo(2);
|
|
510
|
+
expect(result[1]).toBeCloseTo(6);
|
|
511
|
+
expect(result[2]).toBeCloseTo(12);
|
|
512
|
+
});
|
|
513
|
+
test('should throw for invalid matrix', () => {
|
|
514
|
+
const invalidMatrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; // Wrong size
|
|
515
|
+
// @ts-expect-error Invalid matrix size should cause a type error: MatrixTransform3D expects a 4x4 matrix
|
|
516
|
+
expect(() => MatrixTransform3D([1, 1, 1], invalidMatrix)).toThrow();
|
|
517
|
+
});
|
|
518
|
+
test('should throw for invalid vector', () => {
|
|
519
|
+
const identity = MatrixIdentity(4);
|
|
520
|
+
expect(() => MatrixTransform3D([1, 1], identity)).toThrow();
|
|
521
|
+
});
|
|
522
|
+
test('should throw for degenerate transformation', () => {
|
|
523
|
+
const degenerate = [
|
|
524
|
+
[1, 0, 0, 0],
|
|
525
|
+
[0, 1, 0, 0],
|
|
526
|
+
[0, 0, 1, 0],
|
|
527
|
+
[0, 0, 0, 0], // w = 0 causes division by zero
|
|
528
|
+
];
|
|
529
|
+
expect(() => MatrixTransform3D([1, 1, 1], degenerate)).toThrow('3D transformation w component near zero');
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
describe('MatrixDirection3D', () => {
|
|
533
|
+
test('should transform direction with identity matrix', () => {
|
|
534
|
+
const identity = MatrixIdentity(3);
|
|
535
|
+
const direction = [1, 0, 0];
|
|
536
|
+
const result = MatrixDirection3D(direction, identity);
|
|
537
|
+
expect(result[0]).toBeCloseTo(1);
|
|
538
|
+
expect(result[1]).toBeCloseTo(0);
|
|
539
|
+
expect(result[2]).toBeCloseTo(0);
|
|
540
|
+
});
|
|
541
|
+
test('should transform direction with rotation', () => {
|
|
542
|
+
// Extract 3x3 rotation part from 4x4 matrix
|
|
543
|
+
const yaw4x4 = MatrixRotation3DYaw(Math.PI / 2);
|
|
544
|
+
const rotation3x3 = [
|
|
545
|
+
[yaw4x4[0][0], yaw4x4[0][1], yaw4x4[0][2]],
|
|
546
|
+
[yaw4x4[1][0], yaw4x4[1][1], yaw4x4[1][2]],
|
|
547
|
+
[yaw4x4[2][0], yaw4x4[2][1], yaw4x4[2][2]],
|
|
548
|
+
];
|
|
549
|
+
const direction = [1, 0, 0];
|
|
550
|
+
const result = MatrixDirection3D(direction, rotation3x3);
|
|
551
|
+
expect(result[0]).toBeCloseTo(0, 5);
|
|
552
|
+
expect(result[1]).toBeCloseTo(1, 5);
|
|
553
|
+
expect(result[2]).toBeCloseTo(0, 5);
|
|
554
|
+
});
|
|
555
|
+
test('should ignore translation (direction vectors should not be translated)', () => {
|
|
556
|
+
// Create a 3x3 matrix that would translate if it were 4x4
|
|
557
|
+
const transform = [
|
|
558
|
+
[1, 0, 5], // Translation component in 3x3 context
|
|
559
|
+
[0, 1, 10],
|
|
560
|
+
[0, 0, 1],
|
|
561
|
+
];
|
|
562
|
+
const direction = [1, 1, 0];
|
|
563
|
+
const result = MatrixDirection3D(direction, transform);
|
|
564
|
+
// Should apply transformation but not translation effects
|
|
565
|
+
expect(result[0]).toBeCloseTo(1);
|
|
566
|
+
expect(result[1]).toBeCloseTo(1);
|
|
567
|
+
expect(result[2]).toBeCloseTo(0);
|
|
568
|
+
});
|
|
569
|
+
test('should throw for invalid matrix', () => {
|
|
570
|
+
const invalidMatrix = [[1, 0], [0, 1]]; // Wrong size
|
|
571
|
+
// @ts-expect-error Invalid matrix size should cause a type error: MatrixDirection3D expects a 3x3 matrix
|
|
572
|
+
expect(() => MatrixDirection3D([1, 1, 1], invalidMatrix)).toThrow();
|
|
573
|
+
});
|
|
574
|
+
test('should throw for invalid direction', () => {
|
|
575
|
+
const identity = [
|
|
576
|
+
[1, 0, 0],
|
|
577
|
+
[0, 1, 0],
|
|
578
|
+
[0, 0, 1],
|
|
579
|
+
];
|
|
580
|
+
expect(() => MatrixDirection3D([1, 1], identity)).toThrow();
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
describe('View and Projection Matrices', () => {
|
|
585
|
+
describe('MatrixView', () => {
|
|
586
|
+
test('should create view matrix looking down negative Z', () => {
|
|
587
|
+
const eye = [0, 0, 0];
|
|
588
|
+
const target = [0, 0, -1];
|
|
589
|
+
const up = [0, 1, 0];
|
|
590
|
+
const viewMatrix = MatrixView(eye, target, up);
|
|
591
|
+
// Should be approximately identity since we're at origin looking down -Z
|
|
592
|
+
expectMatrixToBeCloseTo(viewMatrix, [
|
|
593
|
+
[1, 0, 0, 0],
|
|
594
|
+
[0, 1, 0, 0],
|
|
595
|
+
[0, 0, 1, 0],
|
|
596
|
+
[0, 0, 0, 1],
|
|
597
|
+
]);
|
|
598
|
+
});
|
|
599
|
+
test('should create view matrix with translation', () => {
|
|
600
|
+
const eye = [0, 0, 5];
|
|
601
|
+
const target = [0, 0, 0];
|
|
602
|
+
const up = [0, 1, 0];
|
|
603
|
+
const viewMatrix = MatrixView(eye, target, up);
|
|
604
|
+
// Should translate camera position
|
|
605
|
+
expect(viewMatrix[0][3]).toBeCloseTo(0);
|
|
606
|
+
expect(viewMatrix[1][3]).toBeCloseTo(0);
|
|
607
|
+
expect(viewMatrix[2][3]).toBeCloseTo(-5);
|
|
608
|
+
expect(viewMatrix[3][3]).toBeCloseTo(1);
|
|
609
|
+
});
|
|
610
|
+
test('should create view matrix with rotation', () => {
|
|
611
|
+
const eye = [1, 0, 0];
|
|
612
|
+
const target = [0, 0, 0];
|
|
613
|
+
const up = [0, 1, 0];
|
|
614
|
+
const viewMatrix = MatrixView(eye, target, up);
|
|
615
|
+
// Camera is to the right of origin looking towards it
|
|
616
|
+
// This should create a rotation
|
|
617
|
+
expect(viewMatrix).not.toEqual([
|
|
618
|
+
[1, 0, 0, 0],
|
|
619
|
+
[0, 1, 0, 0],
|
|
620
|
+
[0, 0, 1, 0],
|
|
621
|
+
[0, 0, 0, 1],
|
|
622
|
+
]);
|
|
623
|
+
// Bottom row should always be [0, 0, 0, 1]
|
|
624
|
+
expectMatrixToBeCloseTo([viewMatrix[3]], [[0, 0, 0, 1]]);
|
|
625
|
+
});
|
|
626
|
+
test('should throw for invalid vectors', () => {
|
|
627
|
+
expect(() => MatrixView([1, 2], [0, 0, 0], [0, 1, 0])).toThrow();
|
|
628
|
+
expect(() => MatrixView([0, 0, 0], [1, 2], [0, 1, 0])).toThrow();
|
|
629
|
+
expect(() => MatrixView([0, 0, 0], [0, 0, 0], [1, 2])).toThrow();
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
describe('Matrix_Transformation_Perspective', () => {
|
|
633
|
+
test('should create perspective matrix with valid parameters', () => {
|
|
634
|
+
const fovY = Math.PI / 4; // 45 degrees
|
|
635
|
+
const aspect = 16 / 9;
|
|
636
|
+
const near = 0.1;
|
|
637
|
+
const far = 100;
|
|
638
|
+
const perspective = MatrixPerspective(fovY, aspect, near, far);
|
|
639
|
+
// Should have specific structure for perspective matrix
|
|
640
|
+
expect(perspective[0][0]).toBeGreaterThan(0); // X scaling
|
|
641
|
+
expect(perspective[1][1]).toBeGreaterThan(0); // Y scaling
|
|
642
|
+
expect(perspective[2][2]).toBeLessThan(0); // Z mapping (negative)
|
|
643
|
+
expect(perspective[2][3]).toBeLessThan(0); // Z translation (negative)
|
|
644
|
+
expect(perspective[3][2]).toBe(-1); // Perspective trigger
|
|
645
|
+
expect(perspective[3][3]).toBe(0); // Clear diagonal
|
|
646
|
+
});
|
|
647
|
+
test('should throw for invalid near plane', () => {
|
|
648
|
+
expect(() => MatrixPerspective(Math.PI / 4, 1, 0, 100)).toThrow('Near clipping plane must be greater than 0');
|
|
649
|
+
expect(() => MatrixPerspective(Math.PI / 4, 1, -1, 100)).toThrow('Near clipping plane must be greater than 0');
|
|
650
|
+
});
|
|
651
|
+
test('should throw for invalid far plane', () => {
|
|
652
|
+
expect(() => MatrixPerspective(Math.PI / 4, 1, 1, 0)).toThrow('Far clipping plane must be greater than 0');
|
|
653
|
+
expect(() => MatrixPerspective(Math.PI / 4, 1, 1, -1)).toThrow('Far clipping plane must be greater than 0');
|
|
654
|
+
});
|
|
655
|
+
test('should throw when near >= far', () => {
|
|
656
|
+
expect(() => MatrixPerspective(Math.PI / 4, 1, 100, 100)).toThrow('Near clipping plane must be less than far clipping plane');
|
|
657
|
+
expect(() => MatrixPerspective(Math.PI / 4, 1, 100, 50)).toThrow('Near clipping plane must be less than far clipping plane');
|
|
658
|
+
});
|
|
659
|
+
test('should throw for invalid aspect ratio', () => {
|
|
660
|
+
expect(() => MatrixPerspective(Math.PI / 4, 0, 0.1, 100)).toThrow('Aspect ratio must be greater than 0');
|
|
661
|
+
expect(() => MatrixPerspective(Math.PI / 4, -1, 0.1, 100)).toThrow('Aspect ratio must be greater than 0');
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
describe('MatrixOrthographic', () => {
|
|
665
|
+
test('should create orthographic matrix with valid parameters', () => {
|
|
666
|
+
const left = -10;
|
|
667
|
+
const right = 10;
|
|
668
|
+
const bottom = -5;
|
|
669
|
+
const top = 5;
|
|
670
|
+
const near = -100;
|
|
671
|
+
const far = 100;
|
|
672
|
+
const ortho = MatrixOrthographic(left, right, bottom, top, near, far);
|
|
673
|
+
// Should have specific structure for orthographic matrix
|
|
674
|
+
expect(ortho[0][0]).toBeCloseTo(2 / (right - left)); // X scaling
|
|
675
|
+
expect(ortho[1][1]).toBeCloseTo(2 / (top - bottom)); // Y scaling
|
|
676
|
+
expect(ortho[2][2]).toBeCloseTo(-2 / (far - near)); // Z scaling (negative)
|
|
677
|
+
expect(ortho[0][3]).toBeCloseTo(-(right + left) / (right - left)); // X translation
|
|
678
|
+
expect(ortho[1][3]).toBeCloseTo(-(top + bottom) / (top - bottom)); // Y translation
|
|
679
|
+
expect(ortho[2][3]).toBeCloseTo(-(far + near) / (far - near)); // Z translation
|
|
680
|
+
expect(ortho[3][3]).toBe(1); // Homogeneous coordinate
|
|
681
|
+
});
|
|
682
|
+
test('should throw for equal boundaries', () => {
|
|
683
|
+
expect(() => MatrixOrthographic(5, 5, -5, 5, -100, 100)).toThrow('Left and right bounds must not be equal');
|
|
684
|
+
expect(() => MatrixOrthographic(-5, 5, 3, 3, -100, 100)).toThrow('Bottom and top bounds must not be equal');
|
|
685
|
+
expect(() => MatrixOrthographic(-5, 5, -5, 5, 50, 50)).toThrow('Near and far clipping planes must not be equal');
|
|
686
|
+
});
|
|
687
|
+
test('should handle negative coordinate spaces', () => {
|
|
688
|
+
const ortho = MatrixOrthographic(-100, -50, -25, -10, 10, 20);
|
|
689
|
+
// Should still create valid matrix
|
|
690
|
+
expect(ortho[3][3]).toBe(1);
|
|
691
|
+
expect(ortho[0][0]).toBeCloseTo(2 / (-50 - -100));
|
|
692
|
+
expect(ortho[1][1]).toBeCloseTo(2 / (-10 - -25));
|
|
693
|
+
expect(ortho[2][2]).toBeCloseTo(-2 / (20 - 10));
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
describe('Integration Tests', () => {
|
|
698
|
+
test('should compose transformations correctly', () => {
|
|
699
|
+
// Create a composite transformation: translate, then rotate, then scale
|
|
700
|
+
const translation = MatrixTranslation2D(5, 5);
|
|
701
|
+
const rotation = MatrixRotation2D(Math.PI / 4); // 45 degrees
|
|
702
|
+
const scale = MatrixScale2D(2, 2);
|
|
703
|
+
// Apply transformations in sequence
|
|
704
|
+
const point = [1, 0];
|
|
705
|
+
// First translate
|
|
706
|
+
const translated = MatrixTransform2D(point, translation);
|
|
707
|
+
expect(translated[0]).toBeCloseTo(6);
|
|
708
|
+
expect(translated[1]).toBeCloseTo(5);
|
|
709
|
+
// Then rotate
|
|
710
|
+
const rotated = MatrixTransform2D(translated, rotation);
|
|
711
|
+
// Then scale
|
|
712
|
+
const final = MatrixTransform2D(rotated, scale);
|
|
713
|
+
// Final result should be properly transformed
|
|
714
|
+
expect(final).toBeDefined();
|
|
715
|
+
expect(Array.isArray(final)).toBe(true);
|
|
716
|
+
expect(final.length).toBe(2);
|
|
717
|
+
});
|
|
718
|
+
test('should maintain vector magnitude for rotation only', () => {
|
|
719
|
+
const vector = [3, 4]; // magnitude = 5
|
|
720
|
+
const rotation = MatrixRotation2D(Math.PI / 3); // 60 degrees
|
|
721
|
+
const rotated = MatrixTransform2D(vector, rotation);
|
|
722
|
+
const originalMagnitude = VectorMagnitude(vector);
|
|
723
|
+
const rotatedMagnitude = VectorMagnitude(rotated);
|
|
724
|
+
expect(rotatedMagnitude).toBeCloseTo(originalMagnitude, 5);
|
|
725
|
+
});
|
|
726
|
+
test('should transform 3D vectors correctly with combined rotations', () => {
|
|
727
|
+
const vector = [1, 0, 0];
|
|
728
|
+
// First rotate around Y (pitch), then around Z (yaw)
|
|
729
|
+
const pitch90 = MatrixRotation3DPitch(Math.PI / 2);
|
|
730
|
+
const yaw90 = MatrixRotation3DYaw(Math.PI / 2);
|
|
731
|
+
const afterPitch = MatrixTransform3D(vector, pitch90);
|
|
732
|
+
expect(afterPitch[0]).toBeCloseTo(0, 5);
|
|
733
|
+
expect(afterPitch[1]).toBeCloseTo(0, 5);
|
|
734
|
+
expect(afterPitch[2]).toBeCloseTo(-1, 5);
|
|
735
|
+
const afterYaw = MatrixTransform3D(afterPitch, yaw90);
|
|
736
|
+
expect(afterYaw[0]).toBeCloseTo(0, 5);
|
|
737
|
+
expect(afterYaw[1]).toBeCloseTo(0, 5);
|
|
738
|
+
expect(afterYaw[2]).toBeCloseTo(-1, 5);
|
|
739
|
+
});
|
|
740
|
+
test('should preserve direction vector properties', () => {
|
|
741
|
+
const direction = [1, 1, 1];
|
|
742
|
+
const rotation3x3 = [
|
|
743
|
+
[1, 0, 0],
|
|
744
|
+
[0, 0, -1],
|
|
745
|
+
[0, 1, 0],
|
|
746
|
+
];
|
|
747
|
+
const rotatedDirection = MatrixDirection3D(direction, rotation3x3);
|
|
748
|
+
const originalMagnitude = VectorMagnitude(direction);
|
|
749
|
+
const rotatedMagnitude = VectorMagnitude(rotatedDirection);
|
|
750
|
+
// Direction transformations should preserve magnitude
|
|
751
|
+
expect(rotatedMagnitude).toBeCloseTo(originalMagnitude, 5);
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
//# sourceMappingURL=transformations.spec.js.map
|