@emasoft/svg-matrix 1.0.5 → 1.0.6
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/README.md +317 -396
- package/package.json +19 -1
- package/src/browser-verify.js +463 -0
- package/src/clip-path-resolver.js +759 -0
- package/src/geometry-to-path.js +348 -0
- package/src/index.js +413 -6
- package/src/marker-resolver.js +1006 -0
- package/src/mask-resolver.js +1407 -0
- package/src/mesh-gradient.js +1215 -0
- package/src/pattern-resolver.js +844 -0
- package/src/polygon-clip.js +1491 -0
- package/src/svg-flatten.js +1264 -105
- package/src/text-to-path.js +820 -0
- package/src/transforms2d.js +493 -37
- package/src/transforms3d.js +418 -47
- package/src/use-symbol-resolver.js +1126 -0
- package/samples/preserveAspectRatio_SVG.svg +0 -63
- package/samples/test.svg +0 -39
package/src/transforms3d.js
CHANGED
|
@@ -11,21 +11,78 @@ const D = x => (x instanceof Decimal ? x : new Decimal(x));
|
|
|
11
11
|
/**
|
|
12
12
|
* 3D Affine Transforms using 4x4 homogeneous matrices.
|
|
13
13
|
*
|
|
14
|
+
* ## Mathematical Foundation
|
|
15
|
+
*
|
|
16
|
+
* In 3D computer graphics, affine transformations (translation, rotation, scaling, reflection)
|
|
17
|
+
* are represented using 4x4 matrices in homogeneous coordinates. A 3D point (x, y, z) is
|
|
18
|
+
* represented as a 4D vector [x, y, z, 1]ᵀ, where the extra 1 enables translation via
|
|
19
|
+
* matrix multiplication.
|
|
20
|
+
*
|
|
21
|
+
* The general form of a 4x4 affine transformation matrix is:
|
|
22
|
+
*
|
|
23
|
+
* ```
|
|
24
|
+
* | a b c tx |
|
|
25
|
+
* | d e f ty |
|
|
26
|
+
* | g h i tz |
|
|
27
|
+
* | 0 0 0 1 |
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Where the 3x3 upper-left submatrix handles rotation/scaling/shear, and the rightmost
|
|
31
|
+
* column (tx, ty, tz) handles translation.
|
|
32
|
+
*
|
|
33
|
+
* ## Transform Composition
|
|
34
|
+
*
|
|
14
35
|
* All transforms return 4x4 Matrix objects that can be composed via multiplication.
|
|
15
|
-
* Transform composition is right-to-left: T.mul(R).mul(S) applies S first,
|
|
36
|
+
* **IMPORTANT**: Transform composition is right-to-left: `T.mul(R).mul(S)` applies S first,
|
|
37
|
+
* then R, then T. This follows standard matrix multiplication order.
|
|
38
|
+
*
|
|
39
|
+
* Example: To rotate then translate:
|
|
40
|
+
* ```js
|
|
41
|
+
* const M = translation(10, 0, 0).mul(rotateZ(Math.PI/4));
|
|
42
|
+
* // Applies rotation first, then translation
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* ## Rotation Convention
|
|
16
46
|
*
|
|
17
47
|
* Rotation matrices use the right-hand rule: positive angles rotate counterclockwise
|
|
18
|
-
* when looking down the axis toward the origin.
|
|
48
|
+
* when looking down the axis toward the origin. This means:
|
|
49
|
+
* - rotateX: Y→Z (thumb points +X, fingers curl Y toward Z)
|
|
50
|
+
* - rotateY: Z→X (thumb points +Y, fingers curl Z toward X)
|
|
51
|
+
* - rotateZ: X→Y (thumb points +Z, fingers curl X toward Y)
|
|
19
52
|
*
|
|
20
53
|
* @module Transforms3D
|
|
21
54
|
*/
|
|
22
55
|
|
|
23
56
|
/**
|
|
24
57
|
* Create a 3D translation matrix.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
58
|
+
*
|
|
59
|
+
* Produces a 4x4 matrix that translates points by the vector (tx, ty, tz).
|
|
60
|
+
* Translation matrices have the form:
|
|
61
|
+
*
|
|
62
|
+
* ```
|
|
63
|
+
* | 1 0 0 tx |
|
|
64
|
+
* | 0 1 0 ty |
|
|
65
|
+
* | 0 0 1 tz |
|
|
66
|
+
* | 0 0 0 1 |
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* When applied to a point P = [x, y, z, 1]ᵀ, the result is [x+tx, y+ty, z+tz, 1]ᵀ.
|
|
70
|
+
*
|
|
71
|
+
* @param {number|string|Decimal} tx - Translation distance in X direction (right/left)
|
|
72
|
+
* @param {number|string|Decimal} ty - Translation distance in Y direction (up/down)
|
|
73
|
+
* @param {number|string|Decimal} tz - Translation distance in Z direction (forward/back)
|
|
74
|
+
* @returns {Matrix} 4x4 translation matrix with arbitrary precision
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Move point 5 units right, 3 units up, 2 units forward
|
|
78
|
+
* const T = translation(5, 3, 2);
|
|
79
|
+
* const [x, y, z] = applyTransform(T, 0, 0, 0);
|
|
80
|
+
* // Result: x=5, y=3, z=2
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // Compose with rotation: translate then rotate
|
|
84
|
+
* const M = rotateZ(Math.PI/4).mul(translation(10, 0, 0));
|
|
85
|
+
* // First translates by (10,0,0), then rotates around Z
|
|
29
86
|
*/
|
|
30
87
|
export function translation(tx, ty, tz) {
|
|
31
88
|
return Matrix.from([
|
|
@@ -38,10 +95,40 @@ export function translation(tx, ty, tz) {
|
|
|
38
95
|
|
|
39
96
|
/**
|
|
40
97
|
* Create a 3D scaling matrix.
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
98
|
+
*
|
|
99
|
+
* Produces a 4x4 matrix that scales points by factors (sx, sy, sz) along each axis.
|
|
100
|
+
* Scaling matrices have the form:
|
|
101
|
+
*
|
|
102
|
+
* ```
|
|
103
|
+
* | sx 0 0 0 |
|
|
104
|
+
* | 0 sy 0 0 |
|
|
105
|
+
* | 0 0 sz 0 |
|
|
106
|
+
* | 0 0 0 1 |
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* When applied to a point P = [x, y, z, 1]ᵀ, the result is [sx·x, sy·y, sz·z, 1]ᵀ.
|
|
110
|
+
* Uniform scaling occurs when sx = sy = sz. Non-uniform scaling stretches/compresses
|
|
111
|
+
* along individual axes.
|
|
112
|
+
*
|
|
113
|
+
* @param {number|string|Decimal} sx - Scale factor in X direction (width multiplier)
|
|
114
|
+
* @param {number|string|Decimal} [sy=sx] - Scale factor in Y direction (height multiplier, defaults to sx for uniform scaling)
|
|
115
|
+
* @param {number|string|Decimal} [sz=sx] - Scale factor in Z direction (depth multiplier, defaults to sx for uniform scaling)
|
|
116
|
+
* @returns {Matrix} 4x4 scaling matrix with arbitrary precision
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* // Uniform scaling: double size in all dimensions
|
|
120
|
+
* const S1 = scale(2);
|
|
121
|
+
* // Equivalent to scale(2, 2, 2)
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* // Non-uniform scaling: stretch X by 2, compress Y by 0.5, keep Z unchanged
|
|
125
|
+
* const S2 = scale(2, 0.5, 1);
|
|
126
|
+
* const [x, y, z] = applyTransform(S2, 1, 1, 1);
|
|
127
|
+
* // Result: x=2, y=0.5, z=1
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* // Negative scale factors create reflections
|
|
131
|
+
* const mirror = scale(-1, 1, 1); // Flip X axis
|
|
45
132
|
*/
|
|
46
133
|
export function scale(sx, sy = null, sz = null) {
|
|
47
134
|
if (sy === null) sy = sx;
|
|
@@ -57,13 +144,33 @@ export function scale(sx, sy = null, sz = null) {
|
|
|
57
144
|
/**
|
|
58
145
|
* Create a rotation matrix around the X axis.
|
|
59
146
|
*
|
|
147
|
+
* Rotates points counterclockwise around the X axis (right-hand rule: thumb points +X,
|
|
148
|
+
* fingers curl from +Y toward +Z). This rotates the YZ plane while leaving X coordinates
|
|
149
|
+
* unchanged.
|
|
150
|
+
*
|
|
151
|
+
* Matrix form:
|
|
152
|
+
* ```
|
|
60
153
|
* | 1 0 0 0 |
|
|
61
154
|
* | 0 cos(θ) -sin(θ) 0 |
|
|
62
155
|
* | 0 sin(θ) cos(θ) 0 |
|
|
63
156
|
* | 0 0 0 1 |
|
|
157
|
+
* ```
|
|
64
158
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
159
|
+
* The rotation is applied in the YZ plane: Y' = Y·cos(θ) - Z·sin(θ), Z' = Y·sin(θ) + Z·cos(θ)
|
|
160
|
+
*
|
|
161
|
+
* @param {number|string|Decimal} theta - Rotation angle in radians. Positive angles rotate
|
|
162
|
+
* counterclockwise when looking down +X toward origin.
|
|
163
|
+
* @returns {Matrix} 4x4 rotation matrix with arbitrary precision
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* // Rotate 90° around X axis: Y→Z, Z→-Y
|
|
167
|
+
* const R = rotateX(Math.PI / 2);
|
|
168
|
+
* const [x, y, z] = applyTransform(R, 0, 1, 0);
|
|
169
|
+
* // Result: x≈0, y≈0, z≈1 (point on +Y axis moves to +Z axis)
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* // Pitch rotation in 3D graphics (nodding head up/down)
|
|
173
|
+
* const pitch = rotateX(-0.1); // Slight downward tilt
|
|
67
174
|
*/
|
|
68
175
|
export function rotateX(theta) {
|
|
69
176
|
const t = D(theta);
|
|
@@ -80,13 +187,34 @@ export function rotateX(theta) {
|
|
|
80
187
|
/**
|
|
81
188
|
* Create a rotation matrix around the Y axis.
|
|
82
189
|
*
|
|
190
|
+
* Rotates points counterclockwise around the Y axis (right-hand rule: thumb points +Y,
|
|
191
|
+
* fingers curl from +Z toward +X). This rotates the XZ plane while leaving Y coordinates
|
|
192
|
+
* unchanged.
|
|
193
|
+
*
|
|
194
|
+
* Matrix form:
|
|
195
|
+
* ```
|
|
83
196
|
* | cos(θ) 0 sin(θ) 0 |
|
|
84
197
|
* | 0 1 0 0 |
|
|
85
198
|
* | -sin(θ) 0 cos(θ) 0 |
|
|
86
199
|
* | 0 0 0 1 |
|
|
200
|
+
* ```
|
|
87
201
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
202
|
+
* The rotation is applied in the XZ plane: X' = X·cos(θ) + Z·sin(θ), Z' = -X·sin(θ) + Z·cos(θ)
|
|
203
|
+
* Note the sign pattern differs from rotateX/rotateZ due to maintaining right-hand rule consistency.
|
|
204
|
+
*
|
|
205
|
+
* @param {number|string|Decimal} theta - Rotation angle in radians. Positive angles rotate
|
|
206
|
+
* counterclockwise when looking down +Y toward origin.
|
|
207
|
+
* @returns {Matrix} 4x4 rotation matrix with arbitrary precision
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* // Rotate 90° around Y axis: Z→X, X→-Z
|
|
211
|
+
* const R = rotateY(Math.PI / 2);
|
|
212
|
+
* const [x, y, z] = applyTransform(R, 0, 0, 1);
|
|
213
|
+
* // Result: x≈1, y≈0, z≈0 (point on +Z axis moves to +X axis)
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* // Yaw rotation in 3D graphics (turning head left/right)
|
|
217
|
+
* const yaw = rotateY(0.5); // Turn right by ~28.6°
|
|
90
218
|
*/
|
|
91
219
|
export function rotateY(theta) {
|
|
92
220
|
const t = D(theta);
|
|
@@ -103,13 +231,35 @@ export function rotateY(theta) {
|
|
|
103
231
|
/**
|
|
104
232
|
* Create a rotation matrix around the Z axis.
|
|
105
233
|
*
|
|
234
|
+
* Rotates points counterclockwise around the Z axis (right-hand rule: thumb points +Z,
|
|
235
|
+
* fingers curl from +X toward +Y). This rotates the XY plane while leaving Z coordinates
|
|
236
|
+
* unchanged. This is the most common rotation in 2D graphics extended to 3D.
|
|
237
|
+
*
|
|
238
|
+
* Matrix form:
|
|
239
|
+
* ```
|
|
106
240
|
* | cos(θ) -sin(θ) 0 0 |
|
|
107
241
|
* | sin(θ) cos(θ) 0 0 |
|
|
108
242
|
* | 0 0 1 0 |
|
|
109
243
|
* | 0 0 0 1 |
|
|
244
|
+
* ```
|
|
110
245
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
246
|
+
* The rotation is applied in the XY plane: X' = X·cos(θ) - Y·sin(θ), Y' = X·sin(θ) + Y·cos(θ)
|
|
247
|
+
* This is identical to the standard 2D rotation extended with Z and W components.
|
|
248
|
+
*
|
|
249
|
+
* @param {number|string|Decimal} theta - Rotation angle in radians. Positive angles rotate
|
|
250
|
+
* counterclockwise when looking down +Z toward origin
|
|
251
|
+
* (standard 2D counterclockwise from above).
|
|
252
|
+
* @returns {Matrix} 4x4 rotation matrix with arbitrary precision
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* // Rotate 90° around Z axis: X→Y, Y→-X
|
|
256
|
+
* const R = rotateZ(Math.PI / 2);
|
|
257
|
+
* const [x, y, z] = applyTransform(R, 1, 0, 0);
|
|
258
|
+
* // Result: x≈0, y≈1, z≈0 (point on +X axis moves to +Y axis)
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* // Roll rotation in 3D graphics (tilting head left/right)
|
|
262
|
+
* const roll = rotateZ(0.2); // Slight clockwise tilt from viewer perspective
|
|
113
263
|
*/
|
|
114
264
|
export function rotateZ(theta) {
|
|
115
265
|
const t = D(theta);
|
|
@@ -125,16 +275,57 @@ export function rotateZ(theta) {
|
|
|
125
275
|
|
|
126
276
|
/**
|
|
127
277
|
* Create a rotation matrix around an arbitrary axis through the origin.
|
|
128
|
-
* Uses Rodrigues' rotation formula.
|
|
129
278
|
*
|
|
130
|
-
*
|
|
279
|
+
* Uses Rodrigues' rotation formula to construct a rotation matrix for any axis direction.
|
|
280
|
+
* This is the general form of 3D rotation - rotateX, rotateY, and rotateZ are special cases
|
|
281
|
+
* where the axis is aligned with a coordinate axis.
|
|
282
|
+
*
|
|
283
|
+
* ## Rodrigues' Rotation Formula
|
|
284
|
+
*
|
|
285
|
+
* For a unit axis vector **u** = (ux, uy, uz) and angle θ, the rotation matrix is:
|
|
286
|
+
*
|
|
287
|
+
* **R** = **I** + sin(θ)**K** + (1 - cos(θ))**K**²
|
|
131
288
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
289
|
+
* where **K** is the cross-product matrix of **u**:
|
|
290
|
+
* ```
|
|
291
|
+
* K = | 0 -uz uy |
|
|
292
|
+
* | uz 0 -ux |
|
|
293
|
+
* | -uy ux 0 |
|
|
294
|
+
* ```
|
|
295
|
+
*
|
|
296
|
+
* Alternatively, in component form:
|
|
297
|
+
* ```
|
|
298
|
+
* R_ij = cos(θ)δ_ij + (1-cos(θ))u_i·u_j + sin(θ)ε_ijk·u_k
|
|
299
|
+
* ```
|
|
300
|
+
*
|
|
301
|
+
* where δ_ij is Kronecker delta and ε_ijk is Levi-Civita symbol.
|
|
302
|
+
*
|
|
303
|
+
* The axis vector (ux, uy, uz) is automatically normalized to unit length before use.
|
|
304
|
+
* The rotation follows the right-hand rule: positive angles rotate counterclockwise when
|
|
305
|
+
* looking down the axis toward the origin.
|
|
306
|
+
*
|
|
307
|
+
* @param {number|string|Decimal} ux - X component of rotation axis (need not be normalized)
|
|
308
|
+
* @param {number|string|Decimal} uy - Y component of rotation axis (need not be normalized)
|
|
309
|
+
* @param {number|string|Decimal} uz - Z component of rotation axis (need not be normalized)
|
|
135
310
|
* @param {number|string|Decimal} theta - Rotation angle in radians
|
|
136
|
-
* @returns {Matrix} 4x4 rotation matrix
|
|
137
|
-
* @throws {Error} If axis is zero vector
|
|
311
|
+
* @returns {Matrix} 4x4 rotation matrix with arbitrary precision
|
|
312
|
+
* @throws {Error} If axis is zero vector (undefined rotation)
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* // Rotate 45° around the diagonal axis (1,1,1)
|
|
316
|
+
* const R = rotateAroundAxis(1, 1, 1, Math.PI / 4);
|
|
317
|
+
* // The axis is automatically normalized to (1/√3, 1/√3, 1/√3)
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* // Rotate around arbitrary axis pointing northeast and up
|
|
321
|
+
* const axis = [1, 1, 2]; // Not normalized
|
|
322
|
+
* const angle = Math.PI / 3; // 60 degrees
|
|
323
|
+
* const R = rotateAroundAxis(...axis, angle);
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* // Reproducing rotateX using arbitrary axis
|
|
327
|
+
* const Rx = rotateAroundAxis(1, 0, 0, Math.PI / 2);
|
|
328
|
+
* // Equivalent to rotateX(Math.PI / 2)
|
|
138
329
|
*/
|
|
139
330
|
export function rotateAroundAxis(ux, uy, uz, theta) {
|
|
140
331
|
const u = [D(ux), D(uy), D(uz)];
|
|
@@ -172,16 +363,45 @@ export function rotateAroundAxis(ux, uy, uz, theta) {
|
|
|
172
363
|
|
|
173
364
|
/**
|
|
174
365
|
* Create a rotation matrix around an arbitrary axis through a specific point.
|
|
175
|
-
* Equivalent to: translate(px, py, pz) × rotateAroundAxis(...) × translate(-px, -py, -pz)
|
|
176
366
|
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
367
|
+
* By default, rotateAroundAxis rotates around an axis passing through the origin.
|
|
368
|
+
* This function rotates around an axis passing through an arbitrary point (px, py, pz).
|
|
369
|
+
*
|
|
370
|
+
* The transformation is mathematically equivalent to:
|
|
371
|
+
* 1. Translate the pivot point to origin: T(-px, -py, -pz)
|
|
372
|
+
* 2. Perform rotation around axis through origin: R(axis, θ)
|
|
373
|
+
* 3. Translate back: T(px, py, pz)
|
|
374
|
+
*
|
|
375
|
+
* Matrix composition: **M** = T(p) · R(u,θ) · T(-p)
|
|
376
|
+
*
|
|
377
|
+
* This is essential for rotating objects around their center or any specific point
|
|
378
|
+
* rather than around the world origin.
|
|
379
|
+
*
|
|
380
|
+
* @param {number|string|Decimal} ux - X component of rotation axis (need not be normalized)
|
|
381
|
+
* @param {number|string|Decimal} uy - Y component of rotation axis (need not be normalized)
|
|
382
|
+
* @param {number|string|Decimal} uz - Z component of rotation axis (need not be normalized)
|
|
180
383
|
* @param {number|string|Decimal} theta - Rotation angle in radians
|
|
181
|
-
* @param {number|string|Decimal} px - X coordinate of rotation
|
|
182
|
-
* @param {number|string|Decimal} py - Y coordinate of rotation
|
|
183
|
-
* @param {number|string|Decimal} pz - Z coordinate of rotation
|
|
184
|
-
* @returns {Matrix} 4x4 rotation matrix around point (px, py, pz)
|
|
384
|
+
* @param {number|string|Decimal} px - X coordinate of pivot point on rotation axis
|
|
385
|
+
* @param {number|string|Decimal} py - Y coordinate of pivot point on rotation axis
|
|
386
|
+
* @param {number|string|Decimal} pz - Z coordinate of pivot point on rotation axis
|
|
387
|
+
* @returns {Matrix} 4x4 rotation matrix around axis through point (px, py, pz)
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* // Rotate a cube around its center at (5, 5, 5) instead of world origin
|
|
391
|
+
* const center = [5, 5, 5];
|
|
392
|
+
* const R = rotateAroundPoint(0, 0, 1, Math.PI/4, ...center);
|
|
393
|
+
* // Rotates 45° around Z axis passing through (5,5,5)
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* // Swing a pendulum: rotate around pivot point at top
|
|
397
|
+
* const pivot = [0, 10, 0]; // Pivot 10 units above origin
|
|
398
|
+
* const swing = rotateAroundPoint(0, 0, 1, 0.3, ...pivot);
|
|
399
|
+
* // Rotates around Z axis through (0,10,0)
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* // Rotate around diagonal axis through a specific point
|
|
403
|
+
* const R = rotateAroundPoint(1, 1, 1, Math.PI/2, 10, 20, 30);
|
|
404
|
+
* // Complex rotation around axis (1,1,1) passing through (10,20,30)
|
|
185
405
|
*/
|
|
186
406
|
export function rotateAroundPoint(ux, uy, uz, theta, px, py, pz) {
|
|
187
407
|
const pxD = D(px), pyD = D(py), pzD = D(pz);
|
|
@@ -191,14 +411,49 @@ export function rotateAroundPoint(ux, uy, uz, theta, px, py, pz) {
|
|
|
191
411
|
}
|
|
192
412
|
|
|
193
413
|
/**
|
|
194
|
-
* Apply a 3D
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
414
|
+
* Apply a 3D transformation matrix to a point.
|
|
415
|
+
*
|
|
416
|
+
* Converts the 3D point (x, y, z) to homogeneous coordinates [x, y, z, 1]ᵀ,
|
|
417
|
+
* multiplies by the transformation matrix M, then converts back to 3D via
|
|
418
|
+
* perspective division.
|
|
419
|
+
*
|
|
420
|
+
* ## Homogeneous Coordinates
|
|
421
|
+
*
|
|
422
|
+
* A 3D point P = (x, y, z) is represented as a 4D vector [x, y, z, 1]ᵀ.
|
|
423
|
+
* After transformation: P' = M · P = [x', y', z', w']ᵀ
|
|
424
|
+
*
|
|
425
|
+
* The final 3D point is obtained by perspective division:
|
|
426
|
+
* ```
|
|
427
|
+
* result = (x'/w', y'/w', z'/w')
|
|
428
|
+
* ```
|
|
429
|
+
*
|
|
430
|
+
* For affine transformations (translation, rotation, scaling), w' is always 1,
|
|
431
|
+
* so division has no effect. For perspective projections, w' varies and creates
|
|
432
|
+
* the perspective effect.
|
|
433
|
+
*
|
|
434
|
+
* @param {Matrix} M - 4x4 transformation matrix to apply
|
|
435
|
+
* @param {number|string|Decimal} x - X coordinate of input point
|
|
436
|
+
* @param {number|string|Decimal} y - Y coordinate of input point
|
|
437
|
+
* @param {number|string|Decimal} z - Z coordinate of input point
|
|
438
|
+
* @returns {Decimal[]} Transformed point as [x', y', z'] array of Decimal values
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* // Translate a point
|
|
442
|
+
* const T = translation(10, 5, 3);
|
|
443
|
+
* const [x, y, z] = applyTransform(T, 0, 0, 0);
|
|
444
|
+
* // Result: x=10, y=5, z=3
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* // Apply composed transformation
|
|
448
|
+
* const M = translation(5, 0, 0).mul(rotateZ(Math.PI/4)).mul(scale(2));
|
|
449
|
+
* const [x, y, z] = applyTransform(M, 1, 0, 0);
|
|
450
|
+
* // First scales (2,0,0), then rotates, then translates
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* // Transform multiple points in a loop
|
|
454
|
+
* const R = rotateY(Math.PI / 6);
|
|
455
|
+
* const vertices = [[1,0,0], [0,1,0], [0,0,1]];
|
|
456
|
+
* const transformed = vertices.map(([x,y,z]) => applyTransform(R, x, y, z));
|
|
202
457
|
*/
|
|
203
458
|
export function applyTransform(M, x, y, z) {
|
|
204
459
|
const P = Matrix.from([[D(x)], [D(y)], [D(z)], [new Decimal(1)]]);
|
|
@@ -209,8 +464,35 @@ export function applyTransform(M, x, y, z) {
|
|
|
209
464
|
}
|
|
210
465
|
|
|
211
466
|
/**
|
|
212
|
-
* Create a reflection matrix across the XY plane
|
|
213
|
-
*
|
|
467
|
+
* Create a reflection matrix across the XY plane.
|
|
468
|
+
*
|
|
469
|
+
* Reflects points across the XY plane (the plane where z=0), effectively flipping
|
|
470
|
+
* the Z coordinate while leaving X and Y unchanged. This is equivalent to scaling
|
|
471
|
+
* by (1, 1, -1).
|
|
472
|
+
*
|
|
473
|
+
* Matrix form:
|
|
474
|
+
* ```
|
|
475
|
+
* | 1 0 0 0 |
|
|
476
|
+
* | 0 1 0 0 |
|
|
477
|
+
* | 0 0 -1 0 |
|
|
478
|
+
* | 0 0 0 1 |
|
|
479
|
+
* ```
|
|
480
|
+
*
|
|
481
|
+
* Transformation: (x, y, z) → (x, y, -z)
|
|
482
|
+
*
|
|
483
|
+
* @returns {Matrix} 4x4 reflection matrix with determinant -1
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* // Mirror a point across XY plane
|
|
487
|
+
* const M = reflectXY();
|
|
488
|
+
* const [x, y, z] = applyTransform(M, 1, 2, 3);
|
|
489
|
+
* // Result: x=1, y=2, z=-3
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* // Create symmetric geometry above and below XY plane
|
|
493
|
+
* const original = [2, 3, 5];
|
|
494
|
+
* const mirrored = applyTransform(reflectXY(), ...original);
|
|
495
|
+
* // mirrored = [2, 3, -5]
|
|
214
496
|
*/
|
|
215
497
|
export function reflectXY() {
|
|
216
498
|
return Matrix.from([
|
|
@@ -222,8 +504,34 @@ export function reflectXY() {
|
|
|
222
504
|
}
|
|
223
505
|
|
|
224
506
|
/**
|
|
225
|
-
* Create a reflection matrix across the XZ plane
|
|
226
|
-
*
|
|
507
|
+
* Create a reflection matrix across the XZ plane.
|
|
508
|
+
*
|
|
509
|
+
* Reflects points across the XZ plane (the plane where y=0), effectively flipping
|
|
510
|
+
* the Y coordinate while leaving X and Z unchanged. This is equivalent to scaling
|
|
511
|
+
* by (1, -1, 1).
|
|
512
|
+
*
|
|
513
|
+
* Matrix form:
|
|
514
|
+
* ```
|
|
515
|
+
* | 1 0 0 0 |
|
|
516
|
+
* | 0 -1 0 0 |
|
|
517
|
+
* | 0 0 1 0 |
|
|
518
|
+
* | 0 0 0 1 |
|
|
519
|
+
* ```
|
|
520
|
+
*
|
|
521
|
+
* Transformation: (x, y, z) → (x, -y, z)
|
|
522
|
+
*
|
|
523
|
+
* @returns {Matrix} 4x4 reflection matrix with determinant -1
|
|
524
|
+
*
|
|
525
|
+
* @example
|
|
526
|
+
* // Mirror a point across XZ plane
|
|
527
|
+
* const M = reflectXZ();
|
|
528
|
+
* const [x, y, z] = applyTransform(M, 1, 2, 3);
|
|
529
|
+
* // Result: x=1, y=-2, z=3
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* // Create left-right symmetry in horizontal plane
|
|
533
|
+
* const M = reflectXZ();
|
|
534
|
+
* // Useful for mirroring floor plans or terrain
|
|
227
535
|
*/
|
|
228
536
|
export function reflectXZ() {
|
|
229
537
|
return Matrix.from([
|
|
@@ -235,8 +543,34 @@ export function reflectXZ() {
|
|
|
235
543
|
}
|
|
236
544
|
|
|
237
545
|
/**
|
|
238
|
-
* Create a reflection matrix across the YZ plane
|
|
239
|
-
*
|
|
546
|
+
* Create a reflection matrix across the YZ plane.
|
|
547
|
+
*
|
|
548
|
+
* Reflects points across the YZ plane (the plane where x=0), effectively flipping
|
|
549
|
+
* the X coordinate while leaving Y and Z unchanged. This is equivalent to scaling
|
|
550
|
+
* by (-1, 1, 1).
|
|
551
|
+
*
|
|
552
|
+
* Matrix form:
|
|
553
|
+
* ```
|
|
554
|
+
* | -1 0 0 0 |
|
|
555
|
+
* | 0 1 0 0 |
|
|
556
|
+
* | 0 0 1 0 |
|
|
557
|
+
* | 0 0 0 1 |
|
|
558
|
+
* ```
|
|
559
|
+
*
|
|
560
|
+
* Transformation: (x, y, z) → (-x, y, z)
|
|
561
|
+
*
|
|
562
|
+
* @returns {Matrix} 4x4 reflection matrix with determinant -1
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* // Mirror a point across YZ plane
|
|
566
|
+
* const M = reflectYZ();
|
|
567
|
+
* const [x, y, z] = applyTransform(M, 1, 2, 3);
|
|
568
|
+
* // Result: x=-1, y=2, z=3
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* // Create front-back symmetry
|
|
572
|
+
* const M = reflectYZ();
|
|
573
|
+
* // Useful for mirroring left/right halves of models
|
|
240
574
|
*/
|
|
241
575
|
export function reflectYZ() {
|
|
242
576
|
return Matrix.from([
|
|
@@ -248,8 +582,45 @@ export function reflectYZ() {
|
|
|
248
582
|
}
|
|
249
583
|
|
|
250
584
|
/**
|
|
251
|
-
* Create a reflection matrix
|
|
252
|
-
*
|
|
585
|
+
* Create a reflection matrix through the origin (point inversion).
|
|
586
|
+
*
|
|
587
|
+
* Reflects all points through the origin, effectively flipping all three coordinates.
|
|
588
|
+
* This is equivalent to scaling by (-1, -1, -1), or a 180° rotation around any axis
|
|
589
|
+
* through the origin. Also known as central inversion or point reflection.
|
|
590
|
+
*
|
|
591
|
+
* Matrix form:
|
|
592
|
+
* ```
|
|
593
|
+
* | -1 0 0 0 |
|
|
594
|
+
* | 0 -1 0 0 |
|
|
595
|
+
* | 0 0 -1 0 |
|
|
596
|
+
* | 0 0 0 1 |
|
|
597
|
+
* ```
|
|
598
|
+
*
|
|
599
|
+
* Transformation: (x, y, z) → (-x, -y, -z)
|
|
600
|
+
*
|
|
601
|
+
* This transformation has determinant -1 and is its own inverse (applying it twice
|
|
602
|
+
* returns to the original position).
|
|
603
|
+
*
|
|
604
|
+
* @returns {Matrix} 4x4 reflection matrix with determinant -1
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* // Invert a point through origin
|
|
608
|
+
* const M = reflectOrigin();
|
|
609
|
+
* const [x, y, z] = applyTransform(M, 1, 2, 3);
|
|
610
|
+
* // Result: x=-1, y=-2, z=-3
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* // Create antipodal symmetry (opposite sides)
|
|
614
|
+
* const M = reflectOrigin();
|
|
615
|
+
* const point = [5, 3, -2];
|
|
616
|
+
* const opposite = applyTransform(M, ...point);
|
|
617
|
+
* // opposite = [-5, -3, 2]
|
|
618
|
+
*
|
|
619
|
+
* @example
|
|
620
|
+
* // Inversion is self-inverse: applying twice returns original
|
|
621
|
+
* const M = reflectOrigin();
|
|
622
|
+
* const M2 = M.mul(M);
|
|
623
|
+
* // M2 equals identity matrix
|
|
253
624
|
*/
|
|
254
625
|
export function reflectOrigin() {
|
|
255
626
|
return Matrix.from([
|