@sachitv/safe-math-ts 0.1.0
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/esm/mod.d.ts +4 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +2 -0
- package/esm/package.json +3 -0
- package/esm/src/geometry3d/matrix4.d.ts +400 -0
- package/esm/src/geometry3d/matrix4.d.ts.map +1 -0
- package/esm/src/geometry3d/matrix4.js +975 -0
- package/esm/src/geometry3d/mod.d.ts +6 -0
- package/esm/src/geometry3d/mod.d.ts.map +1 -0
- package/esm/src/geometry3d/mod.js +4 -0
- package/esm/src/geometry3d/quaternion.d.ts +221 -0
- package/esm/src/geometry3d/quaternion.d.ts.map +1 -0
- package/esm/src/geometry3d/quaternion.js +419 -0
- package/esm/src/geometry3d/types.d.ts +151 -0
- package/esm/src/geometry3d/types.d.ts.map +1 -0
- package/esm/src/geometry3d/types.js +7 -0
- package/esm/src/geometry3d/vector3.d.ts +253 -0
- package/esm/src/geometry3d/vector3.d.ts.map +1 -0
- package/esm/src/geometry3d/vector3.js +361 -0
- package/esm/src/units.d.ts +350 -0
- package/esm/src/units.d.ts.map +1 -0
- package/esm/src/units.js +219 -0
- package/package.json +34 -0
|
@@ -0,0 +1,975 @@
|
|
|
1
|
+
import { dimensionlessUnit, } from '../units.js';
|
|
2
|
+
import { quatNormalize, quatNormalizeUnsafe } from './quaternion.js';
|
|
3
|
+
/**
|
|
4
|
+
* Casts a raw number into a branded quantity.
|
|
5
|
+
*
|
|
6
|
+
* @param value Raw numeric scalar.
|
|
7
|
+
* @returns Branded quantity.
|
|
8
|
+
*/
|
|
9
|
+
const asQuantity = (value) => value;
|
|
10
|
+
/**
|
|
11
|
+
* Casts 16 numeric values into a branded affine matrix.
|
|
12
|
+
*
|
|
13
|
+
* @param values Matrix coefficients in column-major order.
|
|
14
|
+
* @returns Branded affine matrix.
|
|
15
|
+
*/
|
|
16
|
+
const asMat4 = (values) => values;
|
|
17
|
+
/**
|
|
18
|
+
* Narrows a dimensionless affine matrix to a linear matrix type.
|
|
19
|
+
*
|
|
20
|
+
* @param value Affine matrix with dimensionless translation slot.
|
|
21
|
+
* @returns Branded linear matrix.
|
|
22
|
+
*/
|
|
23
|
+
const asLinearMat4 = (value) => value;
|
|
24
|
+
/**
|
|
25
|
+
* Casts 16 numeric values into a branded projection matrix.
|
|
26
|
+
*
|
|
27
|
+
* @param values Matrix coefficients in column-major order.
|
|
28
|
+
* @returns Branded projection matrix.
|
|
29
|
+
*/
|
|
30
|
+
const asProjectionMat4 = (values) => values;
|
|
31
|
+
/**
|
|
32
|
+
* Casts xyz components to a branded displacement tuple.
|
|
33
|
+
*
|
|
34
|
+
* @param x X component.
|
|
35
|
+
* @param y Y component.
|
|
36
|
+
* @param z Z component.
|
|
37
|
+
* @returns Branded displacement.
|
|
38
|
+
*/
|
|
39
|
+
const asDelta3 = (x, y, z) => [x, y, z];
|
|
40
|
+
/**
|
|
41
|
+
* Casts xyz components to a branded point tuple.
|
|
42
|
+
*
|
|
43
|
+
* @param x X component.
|
|
44
|
+
* @param y Y component.
|
|
45
|
+
* @param z Z component.
|
|
46
|
+
* @returns Branded point.
|
|
47
|
+
*/
|
|
48
|
+
const asPoint3 = (x, y, z) => [x, y, z];
|
|
49
|
+
/**
|
|
50
|
+
* Creates a typed 4x4 matrix from 16 column-major values.
|
|
51
|
+
*
|
|
52
|
+
* `toFrameTag`, `fromFrameTag`, and `translationUnitTag` enforce explicit
|
|
53
|
+
* frame/unit declaration at construction.
|
|
54
|
+
* Values follow column-major layout and translation lives in indices 12/13/14.
|
|
55
|
+
* Unsafe variant: slices to 16 values without length validation.
|
|
56
|
+
*
|
|
57
|
+
* @param toFrameTag Destination frame token.
|
|
58
|
+
* @param fromFrameTag Source frame token.
|
|
59
|
+
* @param translationUnitTag Translation unit token.
|
|
60
|
+
* @param values Matrix coefficients in column-major order.
|
|
61
|
+
* @returns Typed affine matrix.
|
|
62
|
+
*/
|
|
63
|
+
export const mat4Unsafe = (toFrameTag, fromFrameTag, translationUnitTag, values) => {
|
|
64
|
+
void toFrameTag;
|
|
65
|
+
void fromFrameTag;
|
|
66
|
+
void translationUnitTag;
|
|
67
|
+
return asMat4(values.slice(0, 16));
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Creates a typed 4x4 matrix from 16 column-major values.
|
|
71
|
+
*
|
|
72
|
+
* `toFrameTag`, `fromFrameTag`, and `translationUnitTag` enforce explicit
|
|
73
|
+
* frame/unit declaration at construction.
|
|
74
|
+
* Values follow column-major layout and translation lives in indices 12/13/14.
|
|
75
|
+
* Throws when `values.length !== 16`.
|
|
76
|
+
*
|
|
77
|
+
* @param toFrameTag Destination frame token.
|
|
78
|
+
* @param fromFrameTag Source frame token.
|
|
79
|
+
* @param translationUnitTag Translation unit token.
|
|
80
|
+
* @param values Matrix coefficients in column-major order.
|
|
81
|
+
* @returns Typed affine matrix.
|
|
82
|
+
* @throws {Error} When `values` does not contain exactly 16 entries.
|
|
83
|
+
*/
|
|
84
|
+
export const mat4 = (toFrameTag, fromFrameTag, translationUnitTag, values) => {
|
|
85
|
+
void toFrameTag;
|
|
86
|
+
void fromFrameTag;
|
|
87
|
+
void translationUnitTag;
|
|
88
|
+
if (values.length !== 16) {
|
|
89
|
+
throw new Error(`Mat4 expects 16 values, received ${values.length}`);
|
|
90
|
+
}
|
|
91
|
+
return mat4Unsafe(toFrameTag, fromFrameTag, translationUnitTag, values);
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Creates an identity linear transform for a frame.
|
|
95
|
+
*
|
|
96
|
+
* @param frameTag Frame token.
|
|
97
|
+
* @param dimensionlessUnitTag Dimensionless unit token.
|
|
98
|
+
* @returns Identity linear transform.
|
|
99
|
+
*/
|
|
100
|
+
export const mat4Identity = (frameTag, dimensionlessUnitTag) => {
|
|
101
|
+
void frameTag;
|
|
102
|
+
return asLinearMat4(mat4(frameTag, frameTag, dimensionlessUnitTag, [
|
|
103
|
+
1,
|
|
104
|
+
0,
|
|
105
|
+
0,
|
|
106
|
+
0,
|
|
107
|
+
0,
|
|
108
|
+
1,
|
|
109
|
+
0,
|
|
110
|
+
0,
|
|
111
|
+
0,
|
|
112
|
+
0,
|
|
113
|
+
1,
|
|
114
|
+
0,
|
|
115
|
+
0,
|
|
116
|
+
0,
|
|
117
|
+
0,
|
|
118
|
+
1,
|
|
119
|
+
]));
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Creates a pure translation matrix in a frame.
|
|
123
|
+
*
|
|
124
|
+
* @param frameTag Frame token.
|
|
125
|
+
* @param translation Translation displacement.
|
|
126
|
+
* @returns Affine translation matrix.
|
|
127
|
+
*/
|
|
128
|
+
export const mat4FromTranslation = (frameTag, translation) => (void frameTag,
|
|
129
|
+
asMat4([
|
|
130
|
+
1,
|
|
131
|
+
0,
|
|
132
|
+
0,
|
|
133
|
+
0,
|
|
134
|
+
0,
|
|
135
|
+
1,
|
|
136
|
+
0,
|
|
137
|
+
0,
|
|
138
|
+
0,
|
|
139
|
+
0,
|
|
140
|
+
1,
|
|
141
|
+
0,
|
|
142
|
+
translation[0],
|
|
143
|
+
translation[1],
|
|
144
|
+
translation[2],
|
|
145
|
+
1,
|
|
146
|
+
]));
|
|
147
|
+
/**
|
|
148
|
+
* Creates a non-uniform scale matrix in a frame.
|
|
149
|
+
*
|
|
150
|
+
* @param frameTag Frame token.
|
|
151
|
+
* @param dimensionlessUnitTag Dimensionless unit token.
|
|
152
|
+
* @param xScale X axis scale.
|
|
153
|
+
* @param yScale Y axis scale.
|
|
154
|
+
* @param zScale Z axis scale.
|
|
155
|
+
* @returns Linear scale matrix.
|
|
156
|
+
*/
|
|
157
|
+
export const mat4FromScale = (frameTag, dimensionlessUnitTag, xScale, yScale, zScale) => {
|
|
158
|
+
void frameTag;
|
|
159
|
+
void dimensionlessUnitTag;
|
|
160
|
+
return asLinearMat4(asMat4([
|
|
161
|
+
xScale,
|
|
162
|
+
0,
|
|
163
|
+
0,
|
|
164
|
+
0,
|
|
165
|
+
0,
|
|
166
|
+
yScale,
|
|
167
|
+
0,
|
|
168
|
+
0,
|
|
169
|
+
0,
|
|
170
|
+
0,
|
|
171
|
+
zScale,
|
|
172
|
+
0,
|
|
173
|
+
0,
|
|
174
|
+
0,
|
|
175
|
+
0,
|
|
176
|
+
1,
|
|
177
|
+
]));
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Creates a rotation matrix from a quaternion.
|
|
181
|
+
*
|
|
182
|
+
* @param toFrameTag Destination frame token.
|
|
183
|
+
* @param fromFrameTag Source frame token.
|
|
184
|
+
* @param dimensionlessUnitTag Dimensionless unit token.
|
|
185
|
+
* @param rotation Input quaternion.
|
|
186
|
+
* @returns Linear rotation matrix.
|
|
187
|
+
*/
|
|
188
|
+
export const mat4FromQuaternionUnsafe = (toFrameTag, fromFrameTag, dimensionlessUnitTag, rotation) => {
|
|
189
|
+
void toFrameTag;
|
|
190
|
+
void fromFrameTag;
|
|
191
|
+
void dimensionlessUnitTag;
|
|
192
|
+
const [x, y, z, w] = quatNormalizeUnsafe(rotation);
|
|
193
|
+
const xx = x * x;
|
|
194
|
+
const yy = y * y;
|
|
195
|
+
const zz = z * z;
|
|
196
|
+
const xy = x * y;
|
|
197
|
+
const xz = x * z;
|
|
198
|
+
const yz = y * z;
|
|
199
|
+
const wx = w * x;
|
|
200
|
+
const wy = w * y;
|
|
201
|
+
const wz = w * z;
|
|
202
|
+
return asLinearMat4(asMat4([
|
|
203
|
+
1 - 2 * (yy + zz),
|
|
204
|
+
2 * (xy + wz),
|
|
205
|
+
2 * (xz - wy),
|
|
206
|
+
0,
|
|
207
|
+
2 * (xy - wz),
|
|
208
|
+
1 - 2 * (xx + zz),
|
|
209
|
+
2 * (yz + wx),
|
|
210
|
+
0,
|
|
211
|
+
2 * (xz + wy),
|
|
212
|
+
2 * (yz - wx),
|
|
213
|
+
1 - 2 * (xx + yy),
|
|
214
|
+
0,
|
|
215
|
+
0,
|
|
216
|
+
0,
|
|
217
|
+
0,
|
|
218
|
+
1,
|
|
219
|
+
]));
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Creates a rotation matrix from a quaternion.
|
|
223
|
+
*
|
|
224
|
+
* @param toFrameTag Destination frame token.
|
|
225
|
+
* @param fromFrameTag Source frame token.
|
|
226
|
+
* @param dimensionlessUnitTag Dimensionless unit token.
|
|
227
|
+
* @param rotation Input quaternion.
|
|
228
|
+
* @returns Linear rotation matrix.
|
|
229
|
+
* @throws {Error} When `rotation` is near zero length.
|
|
230
|
+
*/
|
|
231
|
+
export const mat4FromQuaternion = (toFrameTag, fromFrameTag, dimensionlessUnitTag, rotation) => {
|
|
232
|
+
quatNormalize(rotation);
|
|
233
|
+
return mat4FromQuaternionUnsafe(toFrameTag, fromFrameTag, dimensionlessUnitTag, rotation);
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* Builds rigid transform matrix from rotation and translation.
|
|
237
|
+
*
|
|
238
|
+
* @param toFrameTag Destination frame token.
|
|
239
|
+
* @param fromFrameTag Source frame token.
|
|
240
|
+
* @param rotation Rotation quaternion.
|
|
241
|
+
* @param translation Translation expressed in `toFrameTag`.
|
|
242
|
+
* @returns Affine rigid transform matrix.
|
|
243
|
+
*/
|
|
244
|
+
export const mat4FromRigidTransform = (toFrameTag, fromFrameTag, rotation, translation) => {
|
|
245
|
+
void toFrameTag;
|
|
246
|
+
void fromFrameTag;
|
|
247
|
+
const rotationMatrix = mat4FromQuaternion(toFrameTag, fromFrameTag, dimensionlessUnit, rotation);
|
|
248
|
+
return asMat4([
|
|
249
|
+
rotationMatrix[0],
|
|
250
|
+
rotationMatrix[1],
|
|
251
|
+
rotationMatrix[2],
|
|
252
|
+
rotationMatrix[3],
|
|
253
|
+
rotationMatrix[4],
|
|
254
|
+
rotationMatrix[5],
|
|
255
|
+
rotationMatrix[6],
|
|
256
|
+
rotationMatrix[7],
|
|
257
|
+
rotationMatrix[8],
|
|
258
|
+
rotationMatrix[9],
|
|
259
|
+
rotationMatrix[10],
|
|
260
|
+
rotationMatrix[11],
|
|
261
|
+
translation[0],
|
|
262
|
+
translation[1],
|
|
263
|
+
translation[2],
|
|
264
|
+
1,
|
|
265
|
+
]);
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* Builds an affine transform from translation, rotation, and non-uniform scale.
|
|
269
|
+
*
|
|
270
|
+
* Order: scale in `FromFrame`, then rotate `FromFrame -> ToFrame`, then translate
|
|
271
|
+
* in `ToFrame`.
|
|
272
|
+
*
|
|
273
|
+
* @param toFrameTag Destination frame token.
|
|
274
|
+
* @param fromFrameTag Source frame token.
|
|
275
|
+
* @param translation Translation expressed in `toFrameTag`.
|
|
276
|
+
* @param rotation Rotation from `fromFrameTag` into `toFrameTag`.
|
|
277
|
+
* @param scale Non-uniform scale in `fromFrameTag`.
|
|
278
|
+
* @returns Affine TRS matrix.
|
|
279
|
+
*/
|
|
280
|
+
export const mat4FromTRSUnsafe = (toFrameTag, fromFrameTag, translation, rotation, scale) => {
|
|
281
|
+
void toFrameTag;
|
|
282
|
+
void fromFrameTag;
|
|
283
|
+
const [x, y, z, w] = quatNormalizeUnsafe(rotation);
|
|
284
|
+
const xx = x * x;
|
|
285
|
+
const yy = y * y;
|
|
286
|
+
const zz = z * z;
|
|
287
|
+
const xy = x * y;
|
|
288
|
+
const xz = x * z;
|
|
289
|
+
const yz = y * z;
|
|
290
|
+
const wx = w * x;
|
|
291
|
+
const wy = w * y;
|
|
292
|
+
const wz = w * z;
|
|
293
|
+
const sx = scale[0];
|
|
294
|
+
const sy = scale[1];
|
|
295
|
+
const sz = scale[2];
|
|
296
|
+
return asMat4([
|
|
297
|
+
(1 - 2 * (yy + zz)) * sx,
|
|
298
|
+
(2 * (xy + wz)) * sx,
|
|
299
|
+
(2 * (xz - wy)) * sx,
|
|
300
|
+
0,
|
|
301
|
+
(2 * (xy - wz)) * sy,
|
|
302
|
+
(1 - 2 * (xx + zz)) * sy,
|
|
303
|
+
(2 * (yz + wx)) * sy,
|
|
304
|
+
0,
|
|
305
|
+
(2 * (xz + wy)) * sz,
|
|
306
|
+
(2 * (yz - wx)) * sz,
|
|
307
|
+
(1 - 2 * (xx + yy)) * sz,
|
|
308
|
+
0,
|
|
309
|
+
translation[0],
|
|
310
|
+
translation[1],
|
|
311
|
+
translation[2],
|
|
312
|
+
1,
|
|
313
|
+
]);
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* Builds an affine transform from translation, rotation, and non-uniform scale.
|
|
317
|
+
*
|
|
318
|
+
* Order: scale in `FromFrame`, then rotate `FromFrame -> ToFrame`, then translate
|
|
319
|
+
* in `ToFrame`.
|
|
320
|
+
*
|
|
321
|
+
* @param toFrameTag Destination frame token.
|
|
322
|
+
* @param fromFrameTag Source frame token.
|
|
323
|
+
* @param translation Translation expressed in `toFrameTag`.
|
|
324
|
+
* @param rotation Rotation from `fromFrameTag` into `toFrameTag`.
|
|
325
|
+
* @param scale Non-uniform scale in `fromFrameTag`.
|
|
326
|
+
* @returns Affine TRS matrix.
|
|
327
|
+
* @throws {Error} When `rotation` is near zero length.
|
|
328
|
+
*/
|
|
329
|
+
export const mat4FromTRS = (toFrameTag, fromFrameTag, translation, rotation, scale) => {
|
|
330
|
+
quatNormalize(rotation);
|
|
331
|
+
return mat4FromTRSUnsafe(toFrameTag, fromFrameTag, translation, rotation, scale);
|
|
332
|
+
};
|
|
333
|
+
/**
|
|
334
|
+
* Creates a cache for TRS matrix construction.
|
|
335
|
+
*
|
|
336
|
+
* Returns a closure that reuses the previous matrix instance when all inputs are
|
|
337
|
+
* numerically unchanged.
|
|
338
|
+
*
|
|
339
|
+
* @param toFrameTag Destination frame token.
|
|
340
|
+
* @param fromFrameTag Source frame token.
|
|
341
|
+
* @param translationUnitTag Translation unit token.
|
|
342
|
+
* @returns Memoized TRS builder.
|
|
343
|
+
*/
|
|
344
|
+
export const createTrsMat4Cache = (toFrameTag, fromFrameTag, translationUnitTag) => {
|
|
345
|
+
void translationUnitTag;
|
|
346
|
+
let hasCached = false;
|
|
347
|
+
let cached = asMat4([
|
|
348
|
+
1,
|
|
349
|
+
0,
|
|
350
|
+
0,
|
|
351
|
+
0,
|
|
352
|
+
0,
|
|
353
|
+
1,
|
|
354
|
+
0,
|
|
355
|
+
0,
|
|
356
|
+
0,
|
|
357
|
+
0,
|
|
358
|
+
1,
|
|
359
|
+
0,
|
|
360
|
+
0,
|
|
361
|
+
0,
|
|
362
|
+
0,
|
|
363
|
+
1,
|
|
364
|
+
]);
|
|
365
|
+
let tx = 0;
|
|
366
|
+
let ty = 0;
|
|
367
|
+
let tz = 0;
|
|
368
|
+
let qx = 0;
|
|
369
|
+
let qy = 0;
|
|
370
|
+
let qz = 0;
|
|
371
|
+
let qw = 1;
|
|
372
|
+
let sx = 1;
|
|
373
|
+
let sy = 1;
|
|
374
|
+
let sz = 1;
|
|
375
|
+
return (translation, rotation, scale) => {
|
|
376
|
+
const nextTx = translation[0];
|
|
377
|
+
const nextTy = translation[1];
|
|
378
|
+
const nextTz = translation[2];
|
|
379
|
+
const nextQx = rotation[0];
|
|
380
|
+
const nextQy = rotation[1];
|
|
381
|
+
const nextQz = rotation[2];
|
|
382
|
+
const nextQw = rotation[3];
|
|
383
|
+
const nextSx = scale[0];
|
|
384
|
+
const nextSy = scale[1];
|
|
385
|
+
const nextSz = scale[2];
|
|
386
|
+
if (hasCached &&
|
|
387
|
+
nextTx === tx &&
|
|
388
|
+
nextTy === ty &&
|
|
389
|
+
nextTz === tz &&
|
|
390
|
+
nextQx === qx &&
|
|
391
|
+
nextQy === qy &&
|
|
392
|
+
nextQz === qz &&
|
|
393
|
+
nextQw === qw &&
|
|
394
|
+
nextSx === sx &&
|
|
395
|
+
nextSy === sy &&
|
|
396
|
+
nextSz === sz) {
|
|
397
|
+
return cached;
|
|
398
|
+
}
|
|
399
|
+
cached = mat4FromTRS(toFrameTag, fromFrameTag, translation, rotation, scale);
|
|
400
|
+
hasCached = true;
|
|
401
|
+
tx = nextTx;
|
|
402
|
+
ty = nextTy;
|
|
403
|
+
tz = nextTz;
|
|
404
|
+
qx = nextQx;
|
|
405
|
+
qy = nextQy;
|
|
406
|
+
qz = nextQz;
|
|
407
|
+
qw = nextQw;
|
|
408
|
+
sx = nextSx;
|
|
409
|
+
sy = nextSy;
|
|
410
|
+
sz = nextSz;
|
|
411
|
+
return cached;
|
|
412
|
+
};
|
|
413
|
+
};
|
|
414
|
+
/**
|
|
415
|
+
* Builds a right-handed perspective projection matrix.
|
|
416
|
+
*
|
|
417
|
+
* Returns a matrix intended for `projectPoint3` (includes perspective divide).
|
|
418
|
+
*
|
|
419
|
+
* @param toFrameTag Destination frame token.
|
|
420
|
+
* @param fromFrameTag Source frame token.
|
|
421
|
+
* @param fieldOfViewYRadians Vertical field of view in radians.
|
|
422
|
+
* @param aspect Width/height aspect ratio.
|
|
423
|
+
* @param near Near clipping plane distance.
|
|
424
|
+
* @param far Far clipping plane distance.
|
|
425
|
+
* @returns Projection matrix.
|
|
426
|
+
*/
|
|
427
|
+
export const mat4PerspectiveUnsafe = (toFrameTag, fromFrameTag, fieldOfViewYRadians, aspect, near, far) => {
|
|
428
|
+
void toFrameTag;
|
|
429
|
+
void fromFrameTag;
|
|
430
|
+
const f = 1 / Math.tan(fieldOfViewYRadians * 0.5);
|
|
431
|
+
const rangeInverse = 1 / (near - far);
|
|
432
|
+
return asProjectionMat4([
|
|
433
|
+
f / aspect,
|
|
434
|
+
0,
|
|
435
|
+
0,
|
|
436
|
+
0,
|
|
437
|
+
0,
|
|
438
|
+
f,
|
|
439
|
+
0,
|
|
440
|
+
0,
|
|
441
|
+
0,
|
|
442
|
+
0,
|
|
443
|
+
(far + near) * rangeInverse,
|
|
444
|
+
-1,
|
|
445
|
+
0,
|
|
446
|
+
0,
|
|
447
|
+
(2 * far * near) * rangeInverse,
|
|
448
|
+
0,
|
|
449
|
+
]);
|
|
450
|
+
};
|
|
451
|
+
/**
|
|
452
|
+
* Builds a right-handed perspective projection matrix.
|
|
453
|
+
*
|
|
454
|
+
* Returns a matrix intended for `projectPoint3` (includes perspective divide).
|
|
455
|
+
*
|
|
456
|
+
* @param toFrameTag Destination frame token.
|
|
457
|
+
* @param fromFrameTag Source frame token.
|
|
458
|
+
* @param fieldOfViewYRadians Vertical field of view in radians.
|
|
459
|
+
* @param aspect Width/height aspect ratio.
|
|
460
|
+
* @param near Near clipping plane distance.
|
|
461
|
+
* @param far Far clipping plane distance.
|
|
462
|
+
* @returns Projection matrix.
|
|
463
|
+
* @throws {Error} When FOV/aspect/near/far inputs are invalid.
|
|
464
|
+
*/
|
|
465
|
+
export const mat4Perspective = (toFrameTag, fromFrameTag, fieldOfViewYRadians, aspect, near, far) => {
|
|
466
|
+
void toFrameTag;
|
|
467
|
+
void fromFrameTag;
|
|
468
|
+
if (!(fieldOfViewYRadians > 0 && fieldOfViewYRadians < Math.PI)) {
|
|
469
|
+
throw new Error('fieldOfViewYRadians must be in (0, PI)');
|
|
470
|
+
}
|
|
471
|
+
if (!(aspect > 0)) {
|
|
472
|
+
throw new Error('aspect must be > 0');
|
|
473
|
+
}
|
|
474
|
+
if (!(near > 0 && far > 0 && near < far)) {
|
|
475
|
+
throw new Error('near and far must satisfy 0 < near < far');
|
|
476
|
+
}
|
|
477
|
+
return mat4PerspectiveUnsafe(toFrameTag, fromFrameTag, fieldOfViewYRadians, aspect, near, far);
|
|
478
|
+
};
|
|
479
|
+
/**
|
|
480
|
+
* Builds a right-handed orthographic projection matrix.
|
|
481
|
+
*
|
|
482
|
+
* Maps the view volume [left,right]×[bottom,top]×[-near,-far] to NDC
|
|
483
|
+
* [-1,1]×[-1,1]×[-1,1]. The w component of transformed points is always 1,
|
|
484
|
+
* so `projectPoint3` performs a trivial divide by 1.
|
|
485
|
+
*
|
|
486
|
+
* Unsafe variant: performs no input validation.
|
|
487
|
+
* Degenerate inputs (e.g. left === right) produce a singular matrix.
|
|
488
|
+
*
|
|
489
|
+
* @param toFrameTag Destination frame token.
|
|
490
|
+
* @param fromFrameTag Source frame token.
|
|
491
|
+
* @param left Left clipping plane distance.
|
|
492
|
+
* @param right Right clipping plane distance.
|
|
493
|
+
* @param bottom Bottom clipping plane distance.
|
|
494
|
+
* @param top Top clipping plane distance.
|
|
495
|
+
* @param near Near clipping plane distance.
|
|
496
|
+
* @param far Far clipping plane distance.
|
|
497
|
+
* @returns Orthographic projection matrix.
|
|
498
|
+
*/
|
|
499
|
+
export const mat4OrthoUnsafe = (toFrameTag, fromFrameTag, left, right, bottom, top, near, far) => {
|
|
500
|
+
void toFrameTag;
|
|
501
|
+
void fromFrameTag;
|
|
502
|
+
const rl = 1 / (right - left);
|
|
503
|
+
const tb = 1 / (top - bottom);
|
|
504
|
+
const rangeInverse = 1 / (near - far);
|
|
505
|
+
return asProjectionMat4([
|
|
506
|
+
2 * rl,
|
|
507
|
+
0,
|
|
508
|
+
0,
|
|
509
|
+
0,
|
|
510
|
+
0,
|
|
511
|
+
2 * tb,
|
|
512
|
+
0,
|
|
513
|
+
0,
|
|
514
|
+
0,
|
|
515
|
+
0,
|
|
516
|
+
2 * rangeInverse,
|
|
517
|
+
0,
|
|
518
|
+
-(right + left) * rl,
|
|
519
|
+
-(top + bottom) * tb,
|
|
520
|
+
(far + near) * rangeInverse,
|
|
521
|
+
1,
|
|
522
|
+
]);
|
|
523
|
+
};
|
|
524
|
+
/**
|
|
525
|
+
* Builds a right-handed orthographic projection matrix.
|
|
526
|
+
*
|
|
527
|
+
* Maps the view volume [left,right]×[bottom,top]×[-near,-far] to NDC
|
|
528
|
+
* [-1,1]×[-1,1]×[-1,1]. The w component of transformed points is always 1,
|
|
529
|
+
* so `projectPoint3` performs a trivial divide by 1.
|
|
530
|
+
*
|
|
531
|
+
* Safe variant: validates that left < right, bottom < top, and 0 < near < far.
|
|
532
|
+
*
|
|
533
|
+
* @param toFrameTag Destination frame token.
|
|
534
|
+
* @param fromFrameTag Source frame token.
|
|
535
|
+
* @param left Left clipping plane distance.
|
|
536
|
+
* @param right Right clipping plane distance.
|
|
537
|
+
* @param bottom Bottom clipping plane distance.
|
|
538
|
+
* @param top Top clipping plane distance.
|
|
539
|
+
* @param near Near clipping plane distance.
|
|
540
|
+
* @param far Far clipping plane distance.
|
|
541
|
+
* @returns Orthographic projection matrix.
|
|
542
|
+
* @throws {Error} When the clipping volume is degenerate.
|
|
543
|
+
*/
|
|
544
|
+
export const mat4Ortho = (toFrameTag, fromFrameTag, left, right, bottom, top, near, far) => {
|
|
545
|
+
void toFrameTag;
|
|
546
|
+
void fromFrameTag;
|
|
547
|
+
if (!(right > left)) {
|
|
548
|
+
throw new Error('right must be > left');
|
|
549
|
+
}
|
|
550
|
+
if (!(top > bottom)) {
|
|
551
|
+
throw new Error('top must be > bottom');
|
|
552
|
+
}
|
|
553
|
+
if (!(near > 0 && far > 0 && near < far)) {
|
|
554
|
+
throw new Error('near and far must satisfy 0 < near < far');
|
|
555
|
+
}
|
|
556
|
+
return mat4OrthoUnsafe(toFrameTag, fromFrameTag, left, right, bottom, top, near, far);
|
|
557
|
+
};
|
|
558
|
+
/**
|
|
559
|
+
* Projects a point with a perspective matrix and performs perspective divide.
|
|
560
|
+
*
|
|
561
|
+
* Unsafe variant: performs no `w === 0` guard.
|
|
562
|
+
* Degenerate inputs can yield `NaN`/`Infinity`.
|
|
563
|
+
*
|
|
564
|
+
* @param projection Projection matrix.
|
|
565
|
+
* @param point Input point in projection source frame.
|
|
566
|
+
* @returns Point in normalized device coordinates.
|
|
567
|
+
*/
|
|
568
|
+
export const projectPoint3Unsafe = (projection, point) => {
|
|
569
|
+
const x = point[0];
|
|
570
|
+
const y = point[1];
|
|
571
|
+
const z = point[2];
|
|
572
|
+
const clipX = projection[0] * x +
|
|
573
|
+
projection[4] * y +
|
|
574
|
+
projection[8] * z +
|
|
575
|
+
projection[12];
|
|
576
|
+
const clipY = projection[1] * x +
|
|
577
|
+
projection[5] * y +
|
|
578
|
+
projection[9] * z +
|
|
579
|
+
projection[13];
|
|
580
|
+
const clipZ = projection[2] * x +
|
|
581
|
+
projection[6] * y +
|
|
582
|
+
projection[10] * z +
|
|
583
|
+
projection[14];
|
|
584
|
+
const clipW = projection[3] * x +
|
|
585
|
+
projection[7] * y +
|
|
586
|
+
projection[11] * z +
|
|
587
|
+
projection[15];
|
|
588
|
+
const invW = 1 / clipW;
|
|
589
|
+
return asPoint3(asQuantity(clipX * invW), asQuantity(clipY * invW), asQuantity(clipZ * invW));
|
|
590
|
+
};
|
|
591
|
+
/**
|
|
592
|
+
* Projects a point with a perspective matrix and performs perspective divide.
|
|
593
|
+
*
|
|
594
|
+
* Throws when homogeneous `w` is zero.
|
|
595
|
+
*
|
|
596
|
+
* @param projection Projection matrix.
|
|
597
|
+
* @param point Input point in projection source frame.
|
|
598
|
+
* @returns Point in normalized device coordinates.
|
|
599
|
+
* @throws {Error} When homogeneous `w` equals zero.
|
|
600
|
+
*/
|
|
601
|
+
export const projectPoint3 = (projection, point) => {
|
|
602
|
+
const clipW = projection[3] * point[0] +
|
|
603
|
+
projection[7] * point[1] +
|
|
604
|
+
projection[11] * point[2] +
|
|
605
|
+
projection[15];
|
|
606
|
+
if (Math.abs(clipW) < 1e-10) {
|
|
607
|
+
throw new Error('Perspective divide is undefined for w ≈ 0');
|
|
608
|
+
}
|
|
609
|
+
return projectPoint3Unsafe(projection, point);
|
|
610
|
+
};
|
|
611
|
+
/**
|
|
612
|
+
* Builds a world-to-view pose matrix from eye, target, and up direction.
|
|
613
|
+
*
|
|
614
|
+
* Unsafe variant: performs no degeneracy checks on eye/target/up.
|
|
615
|
+
*
|
|
616
|
+
* @param toFrameTag Destination frame token.
|
|
617
|
+
* @param fromFrameTag Source frame token.
|
|
618
|
+
* @param point_eye_from Camera eye position.
|
|
619
|
+
* @param point_target_from Camera target position.
|
|
620
|
+
* @param dir_up_from Up direction.
|
|
621
|
+
* @returns View matrix.
|
|
622
|
+
*/
|
|
623
|
+
export const mat4LookAtUnsafe = (toFrameTag, fromFrameTag, point_eye_from, point_target_from, dir_up_from) => {
|
|
624
|
+
void toFrameTag;
|
|
625
|
+
void fromFrameTag;
|
|
626
|
+
const forwardX = point_target_from[0] - point_eye_from[0];
|
|
627
|
+
const forwardY = point_target_from[1] - point_eye_from[1];
|
|
628
|
+
const forwardZ = point_target_from[2] - point_eye_from[2];
|
|
629
|
+
const forwardLength = Math.hypot(forwardX, forwardY, forwardZ);
|
|
630
|
+
const dir_forward_x = forwardX / forwardLength;
|
|
631
|
+
const dir_forward_y = forwardY / forwardLength;
|
|
632
|
+
const dir_forward_z = forwardZ / forwardLength;
|
|
633
|
+
const upLength = Math.hypot(dir_up_from[0], dir_up_from[1], dir_up_from[2]);
|
|
634
|
+
const upX = dir_up_from[0] / upLength;
|
|
635
|
+
const upY = dir_up_from[1] / upLength;
|
|
636
|
+
const upZ = dir_up_from[2] / upLength;
|
|
637
|
+
const rightX = dir_forward_y * upZ - dir_forward_z * upY;
|
|
638
|
+
const rightY = dir_forward_z * upX - dir_forward_x * upZ;
|
|
639
|
+
const rightZ = dir_forward_x * upY - dir_forward_y * upX;
|
|
640
|
+
const rightLength = Math.hypot(rightX, rightY, rightZ);
|
|
641
|
+
const dir_right_x = rightX / rightLength;
|
|
642
|
+
const dir_right_y = rightY / rightLength;
|
|
643
|
+
const dir_right_z = rightZ / rightLength;
|
|
644
|
+
const dir_up_orthogonal_x = dir_right_y * dir_forward_z -
|
|
645
|
+
dir_right_z * dir_forward_y;
|
|
646
|
+
const dir_up_orthogonal_y = dir_right_z * dir_forward_x -
|
|
647
|
+
dir_right_x * dir_forward_z;
|
|
648
|
+
const dir_up_orthogonal_z = dir_right_x * dir_forward_y -
|
|
649
|
+
dir_right_y * dir_forward_x;
|
|
650
|
+
const tx = -(dir_right_x * point_eye_from[0] +
|
|
651
|
+
dir_right_y * point_eye_from[1] +
|
|
652
|
+
dir_right_z * point_eye_from[2]);
|
|
653
|
+
const ty = -(dir_up_orthogonal_x * point_eye_from[0] +
|
|
654
|
+
dir_up_orthogonal_y * point_eye_from[1] +
|
|
655
|
+
dir_up_orthogonal_z * point_eye_from[2]);
|
|
656
|
+
const tz = dir_forward_x * point_eye_from[0] +
|
|
657
|
+
dir_forward_y * point_eye_from[1] +
|
|
658
|
+
dir_forward_z * point_eye_from[2];
|
|
659
|
+
return asMat4([
|
|
660
|
+
dir_right_x,
|
|
661
|
+
dir_up_orthogonal_x,
|
|
662
|
+
-dir_forward_x,
|
|
663
|
+
0,
|
|
664
|
+
dir_right_y,
|
|
665
|
+
dir_up_orthogonal_y,
|
|
666
|
+
-dir_forward_y,
|
|
667
|
+
0,
|
|
668
|
+
dir_right_z,
|
|
669
|
+
dir_up_orthogonal_z,
|
|
670
|
+
-dir_forward_z,
|
|
671
|
+
0,
|
|
672
|
+
asQuantity(tx),
|
|
673
|
+
asQuantity(ty),
|
|
674
|
+
asQuantity(tz),
|
|
675
|
+
1,
|
|
676
|
+
]);
|
|
677
|
+
};
|
|
678
|
+
/**
|
|
679
|
+
* Builds a world-to-view pose matrix from eye, target, and up direction.
|
|
680
|
+
*
|
|
681
|
+
* `upDirection` must be dimensionless and non-zero.
|
|
682
|
+
*
|
|
683
|
+
* @param toFrameTag Destination frame token.
|
|
684
|
+
* @param fromFrameTag Source frame token.
|
|
685
|
+
* @param point_eye_from Camera eye position.
|
|
686
|
+
* @param point_target_from Camera target position.
|
|
687
|
+
* @param dir_up_from Up direction.
|
|
688
|
+
* @returns View matrix.
|
|
689
|
+
* @throws {Error} When eye/target/up vectors form a degenerate basis.
|
|
690
|
+
*/
|
|
691
|
+
export const mat4LookAt = (toFrameTag, fromFrameTag, point_eye_from, point_target_from, dir_up_from) => {
|
|
692
|
+
void toFrameTag;
|
|
693
|
+
void fromFrameTag;
|
|
694
|
+
const forwardX = point_target_from[0] - point_eye_from[0];
|
|
695
|
+
const forwardY = point_target_from[1] - point_eye_from[1];
|
|
696
|
+
const forwardZ = point_target_from[2] - point_eye_from[2];
|
|
697
|
+
const forwardLength = Math.hypot(forwardX, forwardY, forwardZ);
|
|
698
|
+
if (forwardLength === 0) {
|
|
699
|
+
throw new Error('LookAt requires eye and target to be distinct');
|
|
700
|
+
}
|
|
701
|
+
const dir_forward_x = forwardX / forwardLength;
|
|
702
|
+
const dir_forward_y = forwardY / forwardLength;
|
|
703
|
+
const dir_forward_z = forwardZ / forwardLength;
|
|
704
|
+
const upLength = Math.hypot(dir_up_from[0], dir_up_from[1], dir_up_from[2]);
|
|
705
|
+
if (upLength === 0) {
|
|
706
|
+
throw new Error('LookAt requires a non-zero up direction');
|
|
707
|
+
}
|
|
708
|
+
const upX = dir_up_from[0] / upLength;
|
|
709
|
+
const upY = dir_up_from[1] / upLength;
|
|
710
|
+
const upZ = dir_up_from[2] / upLength;
|
|
711
|
+
const rightX = dir_forward_y * upZ - dir_forward_z * upY;
|
|
712
|
+
const rightY = dir_forward_z * upX - dir_forward_x * upZ;
|
|
713
|
+
const rightZ = dir_forward_x * upY - dir_forward_y * upX;
|
|
714
|
+
const rightLength = Math.hypot(rightX, rightY, rightZ);
|
|
715
|
+
if (rightLength === 0) {
|
|
716
|
+
throw new Error('LookAt up direction cannot be parallel to forward');
|
|
717
|
+
}
|
|
718
|
+
return mat4LookAtUnsafe(toFrameTag, fromFrameTag, point_eye_from, point_target_from, dir_up_from);
|
|
719
|
+
};
|
|
720
|
+
export function transposeMat4(value) {
|
|
721
|
+
return asMat4([
|
|
722
|
+
value[0],
|
|
723
|
+
value[4],
|
|
724
|
+
value[8],
|
|
725
|
+
value[12],
|
|
726
|
+
value[1],
|
|
727
|
+
value[5],
|
|
728
|
+
value[9],
|
|
729
|
+
value[13],
|
|
730
|
+
value[2],
|
|
731
|
+
value[6],
|
|
732
|
+
value[10],
|
|
733
|
+
value[14],
|
|
734
|
+
value[3],
|
|
735
|
+
value[7],
|
|
736
|
+
value[11],
|
|
737
|
+
value[15],
|
|
738
|
+
]);
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Multiplies two raw 4x4 matrices in column-major order.
|
|
742
|
+
*
|
|
743
|
+
* @param left Left matrix values.
|
|
744
|
+
* @param right Right matrix values.
|
|
745
|
+
* @returns Product matrix values.
|
|
746
|
+
*/
|
|
747
|
+
const multiplyRaw = (left, right) => {
|
|
748
|
+
const output = new Array(16);
|
|
749
|
+
for (let column = 0; column < 4; column += 1) {
|
|
750
|
+
const columnOffset = column * 4;
|
|
751
|
+
for (let row = 0; row < 4; row += 1) {
|
|
752
|
+
output[columnOffset + row] = left[row] * right[columnOffset] +
|
|
753
|
+
left[row + 4] * right[columnOffset + 1] +
|
|
754
|
+
left[row + 8] * right[columnOffset + 2] +
|
|
755
|
+
left[row + 12] * right[columnOffset + 3];
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return output;
|
|
759
|
+
};
|
|
760
|
+
/**
|
|
761
|
+
* Composes affine transforms with different translation units in chain order.
|
|
762
|
+
*
|
|
763
|
+
* Result translation unit is widened to `UnitExpr`.
|
|
764
|
+
* `composeMat4(outer, inner)` returns `outer * inner`, so `inner` is applied
|
|
765
|
+
* first.
|
|
766
|
+
*
|
|
767
|
+
* @param outer Outer transform.
|
|
768
|
+
* @param inner Inner transform.
|
|
769
|
+
* @returns Composed transform with widened translation unit.
|
|
770
|
+
*/
|
|
771
|
+
export function composeMat4(outer, inner) {
|
|
772
|
+
return asMat4(multiplyRaw(outer, inner));
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Checks whether two scalar values are within epsilon tolerance.
|
|
776
|
+
*
|
|
777
|
+
* @param actual Computed value.
|
|
778
|
+
* @param expected Reference value.
|
|
779
|
+
* @param epsilon Absolute tolerance.
|
|
780
|
+
* @returns `true` when values are close enough.
|
|
781
|
+
*/
|
|
782
|
+
const isApproximately = (actual, expected, epsilon) => Math.abs(actual - expected) <= epsilon;
|
|
783
|
+
/**
|
|
784
|
+
* Validates that a matrix represents a rigid transform.
|
|
785
|
+
*
|
|
786
|
+
* @param value Raw matrix values.
|
|
787
|
+
* @param epsilon Absolute tolerance for orthonormal checks.
|
|
788
|
+
* @throws {Error} When the matrix is not rigid.
|
|
789
|
+
*/
|
|
790
|
+
const assertRigidTransform = (value, epsilon) => {
|
|
791
|
+
const col0x = value[0];
|
|
792
|
+
const col0y = value[1];
|
|
793
|
+
const col0z = value[2];
|
|
794
|
+
const col1x = value[4];
|
|
795
|
+
const col1y = value[5];
|
|
796
|
+
const col1z = value[6];
|
|
797
|
+
const col2x = value[8];
|
|
798
|
+
const col2y = value[9];
|
|
799
|
+
const col2z = value[10];
|
|
800
|
+
const norm0 = col0x * col0x + col0y * col0y + col0z * col0z;
|
|
801
|
+
const norm1 = col1x * col1x + col1y * col1y + col1z * col1z;
|
|
802
|
+
const norm2 = col2x * col2x + col2y * col2y + col2z * col2z;
|
|
803
|
+
const dot01 = col0x * col1x + col0y * col1y + col0z * col1z;
|
|
804
|
+
const dot02 = col0x * col2x + col0y * col2y + col0z * col2z;
|
|
805
|
+
const dot12 = col1x * col2x + col1y * col2y + col1z * col2z;
|
|
806
|
+
const hasFiniteNorms = Number.isFinite(norm0) &&
|
|
807
|
+
Number.isFinite(norm1) &&
|
|
808
|
+
Number.isFinite(norm2);
|
|
809
|
+
const hasUnitLengthRows = isApproximately(norm0, 1, epsilon) &&
|
|
810
|
+
isApproximately(norm1, 1, epsilon) &&
|
|
811
|
+
isApproximately(norm2, 1, epsilon);
|
|
812
|
+
const hasOrthogonalRows = isApproximately(dot01, 0, epsilon) &&
|
|
813
|
+
isApproximately(dot02, 0, epsilon) &&
|
|
814
|
+
isApproximately(dot12, 0, epsilon);
|
|
815
|
+
const hasAffineBottomRow = isApproximately(value[3], 0, epsilon) &&
|
|
816
|
+
isApproximately(value[7], 0, epsilon) &&
|
|
817
|
+
isApproximately(value[11], 0, epsilon) &&
|
|
818
|
+
isApproximately(value[15], 1, epsilon);
|
|
819
|
+
const isRigid = hasFiniteNorms &&
|
|
820
|
+
hasUnitLengthRows &&
|
|
821
|
+
hasOrthogonalRows &&
|
|
822
|
+
hasAffineBottomRow;
|
|
823
|
+
if (!isRigid) {
|
|
824
|
+
throw new Error('Matrix is not a rigid transform');
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
export function invertRigidMat4Unsafe(value) {
|
|
828
|
+
const r00 = value[0];
|
|
829
|
+
const r10 = value[1];
|
|
830
|
+
const r20 = value[2];
|
|
831
|
+
const r01 = value[4];
|
|
832
|
+
const r11 = value[5];
|
|
833
|
+
const r21 = value[6];
|
|
834
|
+
const r02 = value[8];
|
|
835
|
+
const r12 = value[9];
|
|
836
|
+
const r22 = value[10];
|
|
837
|
+
const tx = value[12];
|
|
838
|
+
const ty = value[13];
|
|
839
|
+
const tz = value[14];
|
|
840
|
+
const inverseTx = -(r00 * tx + r10 * ty + r20 * tz);
|
|
841
|
+
const inverseTy = -(r01 * tx + r11 * ty + r21 * tz);
|
|
842
|
+
const inverseTz = -(r02 * tx + r12 * ty + r22 * tz);
|
|
843
|
+
return asMat4([
|
|
844
|
+
r00,
|
|
845
|
+
r01,
|
|
846
|
+
r02,
|
|
847
|
+
0,
|
|
848
|
+
r10,
|
|
849
|
+
r11,
|
|
850
|
+
r12,
|
|
851
|
+
0,
|
|
852
|
+
r20,
|
|
853
|
+
r21,
|
|
854
|
+
r22,
|
|
855
|
+
0,
|
|
856
|
+
inverseTx,
|
|
857
|
+
inverseTy,
|
|
858
|
+
inverseTz,
|
|
859
|
+
1,
|
|
860
|
+
]);
|
|
861
|
+
}
|
|
862
|
+
export function invertRigidMat4(value) {
|
|
863
|
+
assertRigidTransform(value, 1e-10);
|
|
864
|
+
return invertRigidMat4Unsafe(value);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Builds a normal matrix (inverse-transpose of upper-left 3x3 linear part).
|
|
868
|
+
*
|
|
869
|
+
* Unsafe variant: performs no singularity guard.
|
|
870
|
+
* Degenerate inputs can yield `NaN`/`Infinity`.
|
|
871
|
+
*
|
|
872
|
+
* @param value Input affine transform.
|
|
873
|
+
* @returns Normal matrix as a linear transform.
|
|
874
|
+
*/
|
|
875
|
+
export const normalMatrixFromMat4Unsafe = (value) => {
|
|
876
|
+
const a = value[0];
|
|
877
|
+
const b = value[4];
|
|
878
|
+
const c = value[8];
|
|
879
|
+
const d = value[1];
|
|
880
|
+
const e = value[5];
|
|
881
|
+
const f = value[9];
|
|
882
|
+
const g = value[2];
|
|
883
|
+
const h = value[6];
|
|
884
|
+
const i = value[10];
|
|
885
|
+
const co00 = e * i - f * h;
|
|
886
|
+
const co01 = c * h - b * i;
|
|
887
|
+
const co02 = b * f - c * e;
|
|
888
|
+
const co10 = f * g - d * i;
|
|
889
|
+
const co11 = a * i - c * g;
|
|
890
|
+
const co12 = c * d - a * f;
|
|
891
|
+
const co20 = d * h - e * g;
|
|
892
|
+
const co21 = b * g - a * h;
|
|
893
|
+
const co22 = a * e - b * d;
|
|
894
|
+
const determinant = a * co00 + b * co10 + c * co20;
|
|
895
|
+
const inverseDeterminant = 1 / determinant;
|
|
896
|
+
return asLinearMat4(asMat4([
|
|
897
|
+
co00 * inverseDeterminant,
|
|
898
|
+
co01 * inverseDeterminant,
|
|
899
|
+
co02 * inverseDeterminant,
|
|
900
|
+
0,
|
|
901
|
+
co10 * inverseDeterminant,
|
|
902
|
+
co11 * inverseDeterminant,
|
|
903
|
+
co12 * inverseDeterminant,
|
|
904
|
+
0,
|
|
905
|
+
co20 * inverseDeterminant,
|
|
906
|
+
co21 * inverseDeterminant,
|
|
907
|
+
co22 * inverseDeterminant,
|
|
908
|
+
0,
|
|
909
|
+
0,
|
|
910
|
+
0,
|
|
911
|
+
0,
|
|
912
|
+
1,
|
|
913
|
+
]));
|
|
914
|
+
};
|
|
915
|
+
/**
|
|
916
|
+
* Builds a normal matrix (inverse-transpose of upper-left 3x3 linear part).
|
|
917
|
+
*
|
|
918
|
+
* Throws when the linear part is singular.
|
|
919
|
+
*
|
|
920
|
+
* @param value Input affine transform.
|
|
921
|
+
* @returns Normal matrix as a linear transform.
|
|
922
|
+
* @throws {Error} When the upper-left 3x3 block is singular.
|
|
923
|
+
*/
|
|
924
|
+
export const normalMatrixFromMat4 = (value) => {
|
|
925
|
+
const a = value[0];
|
|
926
|
+
const b = value[4];
|
|
927
|
+
const c = value[8];
|
|
928
|
+
const d = value[1];
|
|
929
|
+
const e = value[5];
|
|
930
|
+
const f = value[9];
|
|
931
|
+
const g = value[2];
|
|
932
|
+
const h = value[6];
|
|
933
|
+
const i = value[10];
|
|
934
|
+
const co00 = e * i - f * h;
|
|
935
|
+
const co10 = f * g - d * i;
|
|
936
|
+
const co20 = d * h - e * g;
|
|
937
|
+
const determinant = a * co00 + b * co10 + c * co20;
|
|
938
|
+
if (Math.abs(determinant) < 1e-10) {
|
|
939
|
+
throw new Error('Cannot build a normal matrix from a singular transform');
|
|
940
|
+
}
|
|
941
|
+
return normalMatrixFromMat4Unsafe(value);
|
|
942
|
+
};
|
|
943
|
+
export function transformPoint3(matrix, point) {
|
|
944
|
+
const x = point[0];
|
|
945
|
+
const y = point[1];
|
|
946
|
+
const z = point[2];
|
|
947
|
+
const transformedX = matrix[0] * x +
|
|
948
|
+
matrix[4] * y +
|
|
949
|
+
matrix[8] * z +
|
|
950
|
+
matrix[12];
|
|
951
|
+
const transformedY = matrix[1] * x +
|
|
952
|
+
matrix[5] * y +
|
|
953
|
+
matrix[9] * z +
|
|
954
|
+
matrix[13];
|
|
955
|
+
const transformedZ = matrix[2] * x +
|
|
956
|
+
matrix[6] * y +
|
|
957
|
+
matrix[10] * z +
|
|
958
|
+
matrix[14];
|
|
959
|
+
return asPoint3(asQuantity(transformedX), asQuantity(transformedY), asQuantity(transformedZ));
|
|
960
|
+
}
|
|
961
|
+
export function transformDirection3(matrix, direction) {
|
|
962
|
+
const x = direction[0];
|
|
963
|
+
const y = direction[1];
|
|
964
|
+
const z = direction[2];
|
|
965
|
+
const transformedX = matrix[0] * x +
|
|
966
|
+
matrix[4] * y +
|
|
967
|
+
matrix[8] * z;
|
|
968
|
+
const transformedY = matrix[1] * x +
|
|
969
|
+
matrix[5] * y +
|
|
970
|
+
matrix[9] * z;
|
|
971
|
+
const transformedZ = matrix[2] * x +
|
|
972
|
+
matrix[6] * y +
|
|
973
|
+
matrix[10] * z;
|
|
974
|
+
return asDelta3(asQuantity(transformedX), asQuantity(transformedY), asQuantity(transformedZ));
|
|
975
|
+
}
|