@vib3code/sdk 2.0.3-canary.89c05e0 → 2.0.3-canary.8d2fdcd
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/DOCS/AGENT_HARNESS_ARCHITECTURE.md +2 -0
- package/DOCS/ANDROID_DEPLOYMENT.md +59 -0
- package/DOCS/ARCHITECTURE.md +1 -0
- package/DOCS/CI_TESTING.md +2 -0
- package/DOCS/CLI_ONBOARDING.md +2 -0
- package/DOCS/CONTROL_REFERENCE.md +2 -0
- package/DOCS/CROSS_SITE_DESIGN_PATTERNS.md +2 -0
- package/DOCS/ENV_SETUP.md +2 -0
- package/DOCS/EPIC_SCROLL_EVENTS.md +2 -0
- package/DOCS/EXPANSION_DESIGN.md +2 -0
- package/DOCS/EXPANSION_DESIGN_ULTRA.md +2 -0
- package/DOCS/EXPORT_FORMATS.md +2 -0
- package/DOCS/GPU_DISPOSAL_GUIDE.md +2 -0
- package/DOCS/HANDOFF_LANDING_PAGE.md +2 -0
- package/DOCS/HANDOFF_SDK_DEVELOPMENT.md +2 -0
- package/DOCS/LICENSING_TIERS.md +2 -0
- package/DOCS/MASTER_PLAN_2026-01-31.md +2 -0
- package/DOCS/MULTIVIZ_CHOREOGRAPHY_PATTERNS.md +3 -1
- package/DOCS/OBS_SETUP_GUIDE.md +2 -0
- package/DOCS/OPTIMIZATION_PLAN_MATH.md +1 -0
- package/DOCS/PRODUCT_STRATEGY.md +2 -0
- package/DOCS/PROJECT_SETUP.md +2 -0
- package/DOCS/README.md +5 -3
- package/DOCS/REFERENCE_SCROLL_ANALYSIS.md +2 -0
- package/DOCS/RENDERER_LIFECYCLE.md +2 -0
- package/DOCS/REPO_MANIFEST.md +2 -0
- package/DOCS/ROADMAP.md +2 -0
- package/DOCS/SCROLL_TIMELINE_v3.md +2 -0
- package/DOCS/SITE_REFACTOR_PLAN.md +2 -0
- package/DOCS/STATUS.md +2 -0
- package/DOCS/SYSTEM_INVENTORY.md +2 -0
- package/DOCS/TELEMETRY_EXPORTS.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_CLICKERSS.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_FACETAD.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_SIMONE.md +2 -0
- package/DOCS/VISUAL_ANALYSIS_TABLESIDE.md +2 -0
- package/DOCS/WEBGPU_STATUS.md +2 -0
- package/DOCS/XR_BENCHMARKS.md +2 -0
- package/DOCS/archive/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +1 -34
- package/DOCS/archive/DEV_TRACK_ANALYSIS.md +1 -80
- package/DOCS/archive/DEV_TRACK_PLAN_2026-01-07.md +1 -42
- package/DOCS/archive/SESSION_014_PLAN.md +1 -195
- package/DOCS/archive/SESSION_LOG_2026-01-07.md +1 -56
- package/DOCS/archive/STRATEGIC_BLUEPRINT_2026-01-07.md +1 -72
- package/DOCS/archive/SYSTEM_AUDIT_2026-01-30.md +1 -741
- package/DOCS/archive/WEBGPU_STATUS_2026-02-15_STALE.md +1 -38
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-01-31.md +2 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-06.md +2 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-13.md +2 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-15.md +2 -0
- package/DOCS/dev-tracks/DEV_TRACK_SESSION_2026-02-16.md +2 -0
- package/DOCS/dev-tracks/PERF_UPGRADE_2026-02-16.md +2 -0
- package/DOCS/dev-tracks/README.md +2 -0
- package/package.json +2 -4
- package/src/cli/index.js +59 -5
- package/src/export/SVGExporter.js +9 -5
- package/src/geometry/warp/HypersphereCore.js +53 -24
- package/src/math/Mat4x4.js +116 -86
- package/src/math/Projection.js +39 -4
- package/src/math/Rotor4D.js +31 -1
- package/src/math/Vec4.js +127 -30
- package/src/scene/Node4D.js +74 -24
- package/src/testing/ProjectionClass.test.js +38 -0
- package/tools/update_projection.py +109 -0
package/src/math/Vec4.js
CHANGED
|
@@ -128,11 +128,19 @@ export class Vec4 {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
* Add another vector (immutable)
|
|
131
|
+
* Add another vector (immutable unless target provided)
|
|
132
132
|
* @param {Vec4} v
|
|
133
|
-
* @
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
* @param {Vec4} [target=null] - Optional target vector
|
|
134
|
+
* @returns {Vec4} New vector or target
|
|
135
|
+
*/
|
|
136
|
+
add(v, target = null) {
|
|
137
|
+
if (target) {
|
|
138
|
+
target._x = this._x + v._x;
|
|
139
|
+
target._y = this._y + v._y;
|
|
140
|
+
target._z = this._z + v._z;
|
|
141
|
+
target._w = this._w + v._w;
|
|
142
|
+
return target;
|
|
143
|
+
}
|
|
136
144
|
return new Vec4(
|
|
137
145
|
this._x + v._x,
|
|
138
146
|
this._y + v._y,
|
|
@@ -155,11 +163,19 @@ export class Vec4 {
|
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
/**
|
|
158
|
-
* Subtract another vector (immutable)
|
|
166
|
+
* Subtract another vector (immutable unless target provided)
|
|
159
167
|
* @param {Vec4} v
|
|
160
|
-
* @
|
|
161
|
-
|
|
162
|
-
|
|
168
|
+
* @param {Vec4} [target=null] - Optional target vector
|
|
169
|
+
* @returns {Vec4} New vector or target
|
|
170
|
+
*/
|
|
171
|
+
sub(v, target = null) {
|
|
172
|
+
if (target) {
|
|
173
|
+
target._x = this._x - v._x;
|
|
174
|
+
target._y = this._y - v._y;
|
|
175
|
+
target._z = this._z - v._z;
|
|
176
|
+
target._w = this._w - v._w;
|
|
177
|
+
return target;
|
|
178
|
+
}
|
|
163
179
|
return new Vec4(
|
|
164
180
|
this._x - v._x,
|
|
165
181
|
this._y - v._y,
|
|
@@ -182,11 +198,19 @@ export class Vec4 {
|
|
|
182
198
|
}
|
|
183
199
|
|
|
184
200
|
/**
|
|
185
|
-
* Multiply by scalar (immutable)
|
|
201
|
+
* Multiply by scalar (immutable unless target provided)
|
|
186
202
|
* @param {number} s
|
|
187
|
-
* @
|
|
188
|
-
|
|
189
|
-
|
|
203
|
+
* @param {Vec4} [target=null] - Optional target vector
|
|
204
|
+
* @returns {Vec4} New vector or target
|
|
205
|
+
*/
|
|
206
|
+
scale(s, target = null) {
|
|
207
|
+
if (target) {
|
|
208
|
+
target._x = this._x * s;
|
|
209
|
+
target._y = this._y * s;
|
|
210
|
+
target._z = this._z * s;
|
|
211
|
+
target._w = this._w * s;
|
|
212
|
+
return target;
|
|
213
|
+
}
|
|
190
214
|
return new Vec4(
|
|
191
215
|
this._x * s,
|
|
192
216
|
this._y * s,
|
|
@@ -211,9 +235,17 @@ export class Vec4 {
|
|
|
211
235
|
/**
|
|
212
236
|
* Component-wise multiply (Hadamard product)
|
|
213
237
|
* @param {Vec4} v
|
|
214
|
-
* @
|
|
215
|
-
|
|
216
|
-
|
|
238
|
+
* @param {Vec4} [target=null] - Optional target vector
|
|
239
|
+
* @returns {Vec4} New vector or target
|
|
240
|
+
*/
|
|
241
|
+
multiply(v, target = null) {
|
|
242
|
+
if (target) {
|
|
243
|
+
target._x = this._x * v._x;
|
|
244
|
+
target._y = this._y * v._y;
|
|
245
|
+
target._z = this._z * v._z;
|
|
246
|
+
target._w = this._w * v._w;
|
|
247
|
+
return target;
|
|
248
|
+
}
|
|
217
249
|
return new Vec4(
|
|
218
250
|
this._x * v._x,
|
|
219
251
|
this._y * v._y,
|
|
@@ -223,10 +255,18 @@ export class Vec4 {
|
|
|
223
255
|
}
|
|
224
256
|
|
|
225
257
|
/**
|
|
226
|
-
* Negate vector (immutable)
|
|
227
|
-
* @
|
|
258
|
+
* Negate vector (immutable unless target provided)
|
|
259
|
+
* @param {Vec4} [target=null] - Optional target vector
|
|
260
|
+
* @returns {Vec4} New vector or target
|
|
228
261
|
*/
|
|
229
|
-
negate() {
|
|
262
|
+
negate(target = null) {
|
|
263
|
+
if (target) {
|
|
264
|
+
target._x = -this._x;
|
|
265
|
+
target._y = -this._y;
|
|
266
|
+
target._z = -this._z;
|
|
267
|
+
target._w = -this._w;
|
|
268
|
+
return target;
|
|
269
|
+
}
|
|
230
270
|
return new Vec4(-this._x, -this._y, -this._z, -this._w);
|
|
231
271
|
}
|
|
232
272
|
|
|
@@ -286,15 +326,23 @@ export class Vec4 {
|
|
|
286
326
|
}
|
|
287
327
|
|
|
288
328
|
/**
|
|
289
|
-
* Normalize to unit length (immutable)
|
|
290
|
-
* @
|
|
329
|
+
* Normalize to unit length (immutable unless target provided)
|
|
330
|
+
* @param {Vec4} [target=null] - Optional target vector
|
|
331
|
+
* @returns {Vec4} New normalized vector or target
|
|
291
332
|
*/
|
|
292
|
-
normalize() {
|
|
333
|
+
normalize(target = null) {
|
|
293
334
|
const len = this.length();
|
|
294
335
|
if (len < 1e-10) {
|
|
336
|
+
if (target) {
|
|
337
|
+
target._x = 0;
|
|
338
|
+
target._y = 0;
|
|
339
|
+
target._z = 0;
|
|
340
|
+
target._w = 0;
|
|
341
|
+
return target;
|
|
342
|
+
}
|
|
295
343
|
return new Vec4(0, 0, 0, 0);
|
|
296
344
|
}
|
|
297
|
-
return this.scale(1 / len);
|
|
345
|
+
return this.scale(1 / len, target);
|
|
298
346
|
}
|
|
299
347
|
|
|
300
348
|
/**
|
|
@@ -314,9 +362,17 @@ export class Vec4 {
|
|
|
314
362
|
* Linear interpolation to another vector
|
|
315
363
|
* @param {Vec4} v - Target vector
|
|
316
364
|
* @param {number} t - Interpolation factor (0-1)
|
|
317
|
-
* @
|
|
318
|
-
|
|
319
|
-
|
|
365
|
+
* @param {Vec4} [target=null] - Optional target vector
|
|
366
|
+
* @returns {Vec4} New interpolated vector or target
|
|
367
|
+
*/
|
|
368
|
+
lerp(v, t, target = null) {
|
|
369
|
+
if (target) {
|
|
370
|
+
target._x = this._x + (v._x - this._x) * t;
|
|
371
|
+
target._y = this._y + (v._y - this._y) * t;
|
|
372
|
+
target._z = this._z + (v._z - this._z) * t;
|
|
373
|
+
target._w = this._w + (v._w - this._w) * t;
|
|
374
|
+
return target;
|
|
375
|
+
}
|
|
320
376
|
return new Vec4(
|
|
321
377
|
this._x + (v._x - this._x) * t,
|
|
322
378
|
this._y + (v._y - this._y) * t,
|
|
@@ -352,33 +408,74 @@ export class Vec4 {
|
|
|
352
408
|
/**
|
|
353
409
|
* Project 4D point to 3D using perspective projection
|
|
354
410
|
* Projects from 4D to 3D by dividing by (d - w)
|
|
355
|
-
* @param {number} d - Distance parameter (usually 2-5)
|
|
356
|
-
* @param {object} [options] - Projection options
|
|
411
|
+
* @param {number|object} d - Distance parameter (usually 2-5) or options object
|
|
412
|
+
* @param {object|Vec4} [options] - Projection options or target vector
|
|
413
|
+
* @param {Vec4} [target] - Target vector to store result
|
|
357
414
|
* @returns {Vec4} Projected point (w component is 0)
|
|
358
415
|
*/
|
|
359
|
-
projectPerspective(d = 2, options = {}) {
|
|
416
|
+
projectPerspective(d = 2, options = {}, target = null) {
|
|
360
417
|
if (typeof d === 'object') {
|
|
418
|
+
// usage: projectPerspective({ distance: 2, ... }, target?)
|
|
419
|
+
if (options instanceof Vec4) {
|
|
420
|
+
target = options;
|
|
421
|
+
}
|
|
361
422
|
options = d;
|
|
362
423
|
d = options.distance ?? options.d ?? 2;
|
|
424
|
+
} else {
|
|
425
|
+
// usage: projectPerspective(d, options?, target?)
|
|
426
|
+
// usage: projectPerspective(d, target?)
|
|
427
|
+
if (options instanceof Vec4) {
|
|
428
|
+
target = options;
|
|
429
|
+
options = {};
|
|
430
|
+
}
|
|
363
431
|
}
|
|
432
|
+
|
|
433
|
+
options = options || {};
|
|
434
|
+
|
|
364
435
|
const epsilon = options.epsilon ?? 1e-5;
|
|
365
436
|
const denom = d - this._w;
|
|
366
437
|
const clamped = Math.abs(denom) < epsilon ? (denom >= 0 ? epsilon : -epsilon) : denom;
|
|
367
438
|
const scale = 1 / clamped;
|
|
439
|
+
|
|
440
|
+
if (target) {
|
|
441
|
+
target._x = this._x * scale;
|
|
442
|
+
target._y = this._y * scale;
|
|
443
|
+
target._z = this._z * scale;
|
|
444
|
+
target._w = 0;
|
|
445
|
+
return target;
|
|
446
|
+
}
|
|
447
|
+
|
|
368
448
|
return new Vec4(this._x * scale, this._y * scale, this._z * scale, 0);
|
|
369
449
|
}
|
|
370
450
|
|
|
371
451
|
/**
|
|
372
452
|
* Project 4D point to 3D using stereographic projection
|
|
373
453
|
* Maps 4D hypersphere to 3D space
|
|
374
|
-
* @param {object} [options] - Projection options
|
|
454
|
+
* @param {object|Vec4} [options] - Projection options or target vector
|
|
455
|
+
* @param {Vec4} [target] - Target vector to store result
|
|
375
456
|
* @returns {Vec4} Projected point (w component is 0)
|
|
376
457
|
*/
|
|
377
|
-
projectStereographic(options = {}) {
|
|
458
|
+
projectStereographic(options = {}, target = null) {
|
|
459
|
+
if (options instanceof Vec4) {
|
|
460
|
+
target = options;
|
|
461
|
+
options = {};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
options = options || {};
|
|
465
|
+
|
|
378
466
|
const epsilon = options.epsilon ?? 1e-5;
|
|
379
467
|
const denom = 1 - this._w;
|
|
380
468
|
const clamped = Math.abs(denom) < epsilon ? (denom >= 0 ? epsilon : -epsilon) : denom;
|
|
381
469
|
const scale = 1 / clamped;
|
|
470
|
+
|
|
471
|
+
if (target) {
|
|
472
|
+
target._x = this._x * scale;
|
|
473
|
+
target._y = this._y * scale;
|
|
474
|
+
target._z = this._z * scale;
|
|
475
|
+
target._w = 0;
|
|
476
|
+
return target;
|
|
477
|
+
}
|
|
478
|
+
|
|
382
479
|
return new Vec4(this._x * scale, this._y * scale, this._z * scale, 0);
|
|
383
480
|
}
|
|
384
481
|
|
package/src/scene/Node4D.js
CHANGED
|
@@ -500,29 +500,74 @@ export class Node4D {
|
|
|
500
500
|
* @private
|
|
501
501
|
*/
|
|
502
502
|
_updateLocalMatrix() {
|
|
503
|
-
//
|
|
504
|
-
this._localMatrix
|
|
503
|
+
// Ensure matrix exists
|
|
504
|
+
if (!this._localMatrix) {
|
|
505
|
+
this._localMatrix = new Mat4x4();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const m = this._localMatrix.data;
|
|
509
|
+
const s = this._scale;
|
|
510
|
+
const p = this._position;
|
|
511
|
+
|
|
512
|
+
// 1. Write rotation directly to local matrix (No allocation)
|
|
513
|
+
this._rotation.toMatrix(m);
|
|
514
|
+
|
|
515
|
+
// 2. Apply scale (Diagonal matrix multiplication on the right)
|
|
516
|
+
// M = M * S
|
|
517
|
+
// Columns of M are scaled by s.x, s.y, s.z, s.w
|
|
518
|
+
|
|
519
|
+
// Col 0
|
|
520
|
+
m[0] *= s.x; m[1] *= s.x; m[2] *= s.x; m[3] *= s.x;
|
|
521
|
+
// Col 1
|
|
522
|
+
m[4] *= s.y; m[5] *= s.y; m[6] *= s.y; m[7] *= s.y;
|
|
523
|
+
// Col 2
|
|
524
|
+
m[8] *= s.z; m[9] *= s.z; m[10] *= s.z; m[11] *= s.z;
|
|
525
|
+
// Col 3
|
|
526
|
+
m[12] *= s.w; m[13] *= s.w; m[14] *= s.w; m[15] *= s.w;
|
|
527
|
+
|
|
528
|
+
// 3. Apply translation (Matrix multiplication on the left)
|
|
529
|
+
// M = T * M
|
|
530
|
+
// T is standard 3D translation:
|
|
531
|
+
// [ 1 0 0 px ]
|
|
532
|
+
// [ 0 1 0 py ]
|
|
533
|
+
// [ 0 0 1 pz ]
|
|
534
|
+
// [ 0 0 0 1 ]
|
|
535
|
+
//
|
|
536
|
+
// Row 0 += px * Row 3
|
|
537
|
+
// Row 1 += py * Row 3
|
|
538
|
+
// Row 2 += pz * Row 3
|
|
539
|
+
|
|
540
|
+
const px = p.x;
|
|
541
|
+
const py = p.y;
|
|
542
|
+
const pz = p.z;
|
|
543
|
+
|
|
544
|
+
// Row 3 elements of M (which are used in the calculation)
|
|
545
|
+
const m3 = m[3];
|
|
546
|
+
const m7 = m[7];
|
|
547
|
+
const m11 = m[11];
|
|
548
|
+
const m15 = m[15];
|
|
549
|
+
|
|
550
|
+
if (px !== 0) {
|
|
551
|
+
m[0] += px * m3;
|
|
552
|
+
m[4] += px * m7;
|
|
553
|
+
m[8] += px * m11;
|
|
554
|
+
m[12] += px * m15;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (py !== 0) {
|
|
558
|
+
m[1] += py * m3;
|
|
559
|
+
m[5] += py * m7;
|
|
560
|
+
m[9] += py * m11;
|
|
561
|
+
m[13] += py * m15;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (pz !== 0) {
|
|
565
|
+
m[2] += pz * m3;
|
|
566
|
+
m[6] += pz * m7;
|
|
567
|
+
m[10] += pz * m11;
|
|
568
|
+
m[14] += pz * m15;
|
|
569
|
+
}
|
|
505
570
|
|
|
506
|
-
// Apply scale
|
|
507
|
-
const scaleMatrix = Mat4x4.identity();
|
|
508
|
-
scaleMatrix.set(0, 0, this._scale.x);
|
|
509
|
-
scaleMatrix.set(1, 1, this._scale.y);
|
|
510
|
-
scaleMatrix.set(2, 2, this._scale.z);
|
|
511
|
-
scaleMatrix.set(3, 3, this._scale.w);
|
|
512
|
-
|
|
513
|
-
// Apply rotation (toMatrix returns Float32Array, wrap in Mat4x4)
|
|
514
|
-
const rotationMatrix = new Mat4x4(this._rotation.toMatrix());
|
|
515
|
-
|
|
516
|
-
// Apply translation (in 4D, translation is stored in last column, keep [3,3]=1)
|
|
517
|
-
const translationMatrix = Mat4x4.identity();
|
|
518
|
-
translationMatrix.set(0, 3, this._position.x);
|
|
519
|
-
translationMatrix.set(1, 3, this._position.y);
|
|
520
|
-
translationMatrix.set(2, 3, this._position.z);
|
|
521
|
-
// Note: position.w is the 4th spatial coordinate, handled separately
|
|
522
|
-
// Matrix[3,3] must remain 1 for proper transformation
|
|
523
|
-
|
|
524
|
-
// Compose: T * R * S
|
|
525
|
-
this._localMatrix = translationMatrix.multiply(rotationMatrix).multiply(scaleMatrix);
|
|
526
571
|
this._localDirty = false;
|
|
527
572
|
}
|
|
528
573
|
|
|
@@ -535,10 +580,15 @@ export class Node4D {
|
|
|
535
580
|
this._updateLocalMatrix();
|
|
536
581
|
}
|
|
537
582
|
|
|
583
|
+
// Ensure matrix exists
|
|
584
|
+
if (!this._worldMatrix) {
|
|
585
|
+
this._worldMatrix = new Mat4x4();
|
|
586
|
+
}
|
|
587
|
+
|
|
538
588
|
if (this._parent) {
|
|
539
|
-
this.
|
|
589
|
+
this._parent.worldMatrix.multiply(this._localMatrix, this._worldMatrix);
|
|
540
590
|
} else {
|
|
541
|
-
this._worldMatrix
|
|
591
|
+
this._worldMatrix.copy(this._localMatrix);
|
|
542
592
|
}
|
|
543
593
|
|
|
544
594
|
this._worldDirty = false;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import Projection from '../math/Projection.js';
|
|
3
|
+
import Vec4 from '../math/Vec4.js';
|
|
4
|
+
|
|
5
|
+
describe('Projection Class', () => {
|
|
6
|
+
it('should project using perspective', () => {
|
|
7
|
+
const v = new Vec4(1, 1, 1, 0);
|
|
8
|
+
const p = Projection.perspective(v, 2);
|
|
9
|
+
expect(p.x).toBeCloseTo(0.5);
|
|
10
|
+
expect(p.y).toBeCloseTo(0.5);
|
|
11
|
+
expect(p.z).toBeCloseTo(0.5);
|
|
12
|
+
expect(p.w).toBe(0);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should support target vector in perspective', () => {
|
|
16
|
+
const v = new Vec4(1, 1, 1, 0);
|
|
17
|
+
const target = new Vec4();
|
|
18
|
+
const result = Projection.perspective(v, 2, {}, target);
|
|
19
|
+
expect(result).toBe(target);
|
|
20
|
+
expect(target.x).toBeCloseTo(0.5);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should project array using perspectiveArray', () => {
|
|
24
|
+
const vectors = [new Vec4(1, 1, 1, 0), new Vec4(2, 2, 2, 0)];
|
|
25
|
+
const result = Projection.perspectiveArray(vectors, 2);
|
|
26
|
+
expect(result.length).toBe(2);
|
|
27
|
+
expect(result[0].x).toBeCloseTo(0.5);
|
|
28
|
+
expect(result[1].x).toBeCloseTo(1.0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should reuse target array in perspectiveArray', () => {
|
|
32
|
+
const vectors = [new Vec4(1, 1, 1, 0)];
|
|
33
|
+
const targetArray = [new Vec4()];
|
|
34
|
+
const result = Projection.perspectiveArray(vectors, 2, {}, targetArray);
|
|
35
|
+
expect(result).toBe(targetArray);
|
|
36
|
+
expect(targetArray[0].x).toBeCloseTo(0.5);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
file_path = 'src/math/Projection.js'
|
|
4
|
+
|
|
5
|
+
with open(file_path, 'r') as f:
|
|
6
|
+
content = f.read()
|
|
7
|
+
|
|
8
|
+
# Replace perspective JSDoc and Implementation
|
|
9
|
+
perspective_old = r""" * @param {Vec4} v - 4D point
|
|
10
|
+
* @param {number} d - Distance parameter (typically 1.5-5)
|
|
11
|
+
* @returns {Vec4} Projected point (w=0)
|
|
12
|
+
*/
|
|
13
|
+
static perspective(v, d = 2, options = {}) {
|
|
14
|
+
if (typeof d === 'object') {
|
|
15
|
+
options = d;
|
|
16
|
+
d = options.d ?? 2;
|
|
17
|
+
}
|
|
18
|
+
const epsilon = options.epsilon ?? DEFAULT_EPSILON;
|
|
19
|
+
const denom = clampDenominator(d - v.w, epsilon);
|
|
20
|
+
const scale = 1 / denom;
|
|
21
|
+
return new Vec4(v.x * scale, v.y * scale, v.z * scale, 0);
|
|
22
|
+
}"""
|
|
23
|
+
|
|
24
|
+
perspective_new = r""" * @param {Vec4} v - 4D point
|
|
25
|
+
* @param {number} d - Distance parameter (typically 1.5-5)
|
|
26
|
+
* @param {object} [options] - Projection options
|
|
27
|
+
* @param {Vec4} [target] - Optional target vector to write result to
|
|
28
|
+
* @returns {Vec4} Projected point (w=0)
|
|
29
|
+
*/
|
|
30
|
+
static perspective(v, d = 2, options = {}, target = null) {
|
|
31
|
+
if (typeof d === 'object') {
|
|
32
|
+
options = d;
|
|
33
|
+
d = options.d ?? 2;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Handle options overload or direct target argument
|
|
37
|
+
if (!target && options && options.target) {
|
|
38
|
+
target = options.target;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const epsilon = (options && options.epsilon) ?? DEFAULT_EPSILON;
|
|
42
|
+
const denom = clampDenominator(d - v.w, epsilon);
|
|
43
|
+
const scale = 1 / denom;
|
|
44
|
+
|
|
45
|
+
if (target) {
|
|
46
|
+
return target.set(v.x * scale, v.y * scale, v.z * scale, 0);
|
|
47
|
+
}
|
|
48
|
+
return new Vec4(v.x * scale, v.y * scale, v.z * scale, 0);
|
|
49
|
+
}"""
|
|
50
|
+
|
|
51
|
+
if perspective_old in content:
|
|
52
|
+
content = content.replace(perspective_old, perspective_new)
|
|
53
|
+
else:
|
|
54
|
+
print("Could not find perspective implementation to replace.")
|
|
55
|
+
# Attempt more robust search if exact match fails? No, simpler is safer for now.
|
|
56
|
+
|
|
57
|
+
# Replace perspectiveArray JSDoc and Implementation
|
|
58
|
+
perspective_array_old = r""" /**
|
|
59
|
+
* Project array of Vec4s using perspective projection
|
|
60
|
+
* @param {Vec4[]} vectors
|
|
61
|
+
* @param {number} d
|
|
62
|
+
* @returns {Vec4[]}
|
|
63
|
+
*/
|
|
64
|
+
static perspectiveArray(vectors, d = 2, options = {}) {
|
|
65
|
+
return vectors.map(v => Projection.perspective(v, d, options));
|
|
66
|
+
}"""
|
|
67
|
+
|
|
68
|
+
perspective_array_new = r""" /**
|
|
69
|
+
* Project array of Vec4s using perspective projection
|
|
70
|
+
* @param {Vec4[]} vectors
|
|
71
|
+
* @param {number} d
|
|
72
|
+
* @param {object} [options]
|
|
73
|
+
* @param {Vec4[]} [target] - Optional target array to write results to
|
|
74
|
+
* @returns {Vec4[]}
|
|
75
|
+
*/
|
|
76
|
+
static perspectiveArray(vectors, d = 2, options = {}, target = null) {
|
|
77
|
+
// Handle options overload for 'd'
|
|
78
|
+
if (typeof d === 'object') {
|
|
79
|
+
options = d;
|
|
80
|
+
d = options.d ?? 2;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!target) {
|
|
84
|
+
return vectors.map(v => Projection.perspective(v, d, options));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const count = vectors.length;
|
|
88
|
+
// Iterate and reuse
|
|
89
|
+
for (let i = 0; i < count; i++) {
|
|
90
|
+
const out = target[i];
|
|
91
|
+
if (out) {
|
|
92
|
+
Projection.perspective(vectors[i], d, options, out);
|
|
93
|
+
} else {
|
|
94
|
+
target[i] = Projection.perspective(vectors[i], d, options);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return target;
|
|
99
|
+
}"""
|
|
100
|
+
|
|
101
|
+
if perspective_array_old in content:
|
|
102
|
+
content = content.replace(perspective_array_old, perspective_array_new)
|
|
103
|
+
else:
|
|
104
|
+
print("Could not find perspectiveArray implementation to replace.")
|
|
105
|
+
|
|
106
|
+
with open(file_path, 'w') as f:
|
|
107
|
+
f.write(content)
|
|
108
|
+
|
|
109
|
+
print("Updated Projection.js")
|