@vib3code/sdk 2.0.3-canary.3b3ca18 → 2.0.3-canary.4874bcf
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/features/CollectionManager.js +27 -9
- package/src/gallery/CollectionManager.js +27 -9
- package/src/geometry/warp/HypersphereCore.js +53 -24
- package/src/math/Mat4x4.js +272 -128
- package/src/math/Projection.js +57 -7
- package/src/math/Rotor4D.js +64 -28
- package/src/math/Vec4.js +65 -8
- package/src/quantum/QuantumVisualizer.js +28 -0
- package/src/scene/Node4D.js +74 -24
- package/src/testing/ProjectionClass.test.js +38 -0
- package/src/variations/VariationManager.js +6 -1
- package/src/wasm/WasmLoader.js +11 -6
- package/tools/update_projection.py +109 -0
package/src/math/Projection.js
CHANGED
|
@@ -36,16 +36,28 @@ export class Projection {
|
|
|
36
36
|
*
|
|
37
37
|
* @param {Vec4} v - 4D point
|
|
38
38
|
* @param {number} d - Distance parameter (typically 1.5-5)
|
|
39
|
+
* @param {object} [options] - Projection options
|
|
40
|
+
* @param {Vec4} [target] - Optional target vector to write result to
|
|
39
41
|
* @returns {Vec4} Projected point (w=0)
|
|
40
42
|
*/
|
|
41
|
-
static perspective(v, d = 2, options = {}) {
|
|
43
|
+
static perspective(v, d = 2, options = {}, target = null) {
|
|
42
44
|
if (typeof d === 'object') {
|
|
43
45
|
options = d;
|
|
44
46
|
d = options.d ?? 2;
|
|
45
47
|
}
|
|
46
|
-
|
|
48
|
+
|
|
49
|
+
// Handle options overload or direct target argument
|
|
50
|
+
if (!target && options && options.target) {
|
|
51
|
+
target = options.target;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const epsilon = (options && options.epsilon) ?? DEFAULT_EPSILON;
|
|
47
55
|
const denom = clampDenominator(d - v.w, epsilon);
|
|
48
56
|
const scale = 1 / denom;
|
|
57
|
+
|
|
58
|
+
if (target) {
|
|
59
|
+
return target.set(v.x * scale, v.y * scale, v.z * scale, 0);
|
|
60
|
+
}
|
|
49
61
|
return new Vec4(v.x * scale, v.y * scale, v.z * scale, 0);
|
|
50
62
|
}
|
|
51
63
|
|
|
@@ -60,12 +72,23 @@ export class Projection {
|
|
|
60
72
|
* The projection point is at (0, 0, 0, 1) - the "north pole"
|
|
61
73
|
*
|
|
62
74
|
* @param {Vec4} v - 4D point (ideally on unit hypersphere)
|
|
75
|
+
* @param {object|Vec4} [options] - Projection options or target vector
|
|
76
|
+
* @param {Vec4} [target] - Optional target vector to write result to
|
|
63
77
|
* @returns {Vec4} Projected point (w=0)
|
|
64
78
|
*/
|
|
65
|
-
static stereographic(v, options = {}) {
|
|
66
|
-
|
|
79
|
+
static stereographic(v, options = {}, target = null) {
|
|
80
|
+
if (options instanceof Vec4) {
|
|
81
|
+
target = options;
|
|
82
|
+
options = {};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const epsilon = (options && options.epsilon) ?? DEFAULT_EPSILON;
|
|
67
86
|
const denom = clampDenominator(1 - v.w, epsilon);
|
|
68
87
|
const scale = 1 / denom;
|
|
88
|
+
|
|
89
|
+
if (target) {
|
|
90
|
+
return target.set(v.x * scale, v.y * scale, v.z * scale, 0);
|
|
91
|
+
}
|
|
69
92
|
return new Vec4(v.x * scale, v.y * scale, v.z * scale, 0);
|
|
70
93
|
}
|
|
71
94
|
|
|
@@ -95,9 +118,13 @@ export class Projection {
|
|
|
95
118
|
* Parallel projection - no perspective distortion.
|
|
96
119
|
*
|
|
97
120
|
* @param {Vec4} v - 4D point
|
|
121
|
+
* @param {Vec4} [target] - Optional target vector to write result to
|
|
98
122
|
* @returns {Vec4} Projected point (w=0)
|
|
99
123
|
*/
|
|
100
|
-
static orthographic(v) {
|
|
124
|
+
static orthographic(v, target = null) {
|
|
125
|
+
if (target) {
|
|
126
|
+
return target.set(v.x, v.y, v.z, 0);
|
|
127
|
+
}
|
|
101
128
|
return new Vec4(v.x, v.y, v.z, 0);
|
|
102
129
|
}
|
|
103
130
|
|
|
@@ -126,10 +153,33 @@ export class Projection {
|
|
|
126
153
|
* Project array of Vec4s using perspective projection
|
|
127
154
|
* @param {Vec4[]} vectors
|
|
128
155
|
* @param {number} d
|
|
156
|
+
* @param {object} [options]
|
|
157
|
+
* @param {Vec4[]} [target] - Optional target array to write results to
|
|
129
158
|
* @returns {Vec4[]}
|
|
130
159
|
*/
|
|
131
|
-
static perspectiveArray(vectors, d = 2, options = {}) {
|
|
132
|
-
|
|
160
|
+
static perspectiveArray(vectors, d = 2, options = {}, target = null) {
|
|
161
|
+
// Handle options overload for 'd'
|
|
162
|
+
if (typeof d === 'object') {
|
|
163
|
+
options = d;
|
|
164
|
+
d = options.d ?? 2;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!target) {
|
|
168
|
+
return vectors.map(v => Projection.perspective(v, d, options));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const count = vectors.length;
|
|
172
|
+
// Iterate and reuse
|
|
173
|
+
for (let i = 0; i < count; i++) {
|
|
174
|
+
const out = target[i];
|
|
175
|
+
if (out) {
|
|
176
|
+
Projection.perspective(vectors[i], d, options, out);
|
|
177
|
+
} else {
|
|
178
|
+
target[i] = Projection.perspective(vectors[i], d, options);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return target;
|
|
133
183
|
}
|
|
134
184
|
|
|
135
185
|
/**
|
package/src/math/Rotor4D.js
CHANGED
|
@@ -276,48 +276,54 @@ export class Rotor4D {
|
|
|
276
276
|
* The result applies this rotation, then r's rotation
|
|
277
277
|
*
|
|
278
278
|
* @param {Rotor4D} r - Right operand
|
|
279
|
+
* @param {Rotor4D} [target=null] - Optional target rotor to write result into
|
|
279
280
|
* @returns {Rotor4D} Composed rotor
|
|
280
281
|
*/
|
|
281
|
-
multiply(r) {
|
|
282
|
+
multiply(r, target = null) {
|
|
282
283
|
// Full geometric product of two rotors in 4D
|
|
283
284
|
// This is derived from the geometric algebra product rules
|
|
284
285
|
|
|
285
286
|
const a = this;
|
|
286
287
|
const b = r;
|
|
287
288
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
a.
|
|
291
|
-
a.xw * b.xw - a.yw * b.yw - a.zw * b.zw - a.xyzw * b.xyzw,
|
|
289
|
+
// Compute all components first to ensure safety if target aliases a or b
|
|
290
|
+
const s = a.s * b.s - a.xy * b.xy - a.xz * b.xz - a.yz * b.yz -
|
|
291
|
+
a.xw * b.xw - a.yw * b.yw - a.zw * b.zw - a.xyzw * b.xyzw;
|
|
292
292
|
|
|
293
|
-
|
|
294
|
-
a.
|
|
295
|
-
a.xw * b.yw - a.yw * b.xw - a.zw * b.xyzw - a.xyzw * b.zw,
|
|
293
|
+
const xy = a.s * b.xy + a.xy * b.s + a.xz * b.yz - a.yz * b.xz +
|
|
294
|
+
a.xw * b.yw - a.yw * b.xw - a.zw * b.xyzw - a.xyzw * b.zw;
|
|
296
295
|
|
|
297
|
-
|
|
298
|
-
a.
|
|
299
|
-
a.xw * b.zw + a.yw * b.xyzw - a.zw * b.xw + a.xyzw * b.yw,
|
|
296
|
+
const xz = a.s * b.xz + a.xz * b.s - a.xy * b.yz + a.yz * b.xy +
|
|
297
|
+
a.xw * b.zw + a.yw * b.xyzw - a.zw * b.xw + a.xyzw * b.yw;
|
|
300
298
|
|
|
301
|
-
|
|
302
|
-
a.
|
|
303
|
-
a.xw * b.xyzw + a.yw * b.zw - a.zw * b.yw - a.xyzw * b.xw,
|
|
299
|
+
const yz = a.s * b.yz + a.yz * b.s + a.xy * b.xz - a.xz * b.xy -
|
|
300
|
+
a.xw * b.xyzw + a.yw * b.zw - a.zw * b.yw - a.xyzw * b.xw;
|
|
304
301
|
|
|
305
|
-
|
|
306
|
-
a.
|
|
307
|
-
a.yz * b.xyzw + a.yw * b.xy - a.zw * b.xz + a.xyzw * b.yz,
|
|
302
|
+
const xw = a.s * b.xw + a.xw * b.s - a.xy * b.yw + a.xz * b.zw +
|
|
303
|
+
a.yz * b.xyzw + a.yw * b.xy - a.zw * b.xz + a.xyzw * b.yz;
|
|
308
304
|
|
|
309
|
-
|
|
310
|
-
a.
|
|
311
|
-
a.yz * b.zw - a.xw * b.xy + a.zw * b.yz - a.xyzw * b.xz,
|
|
305
|
+
const yw = a.s * b.yw + a.yw * b.s + a.xy * b.xw - a.xz * b.xyzw -
|
|
306
|
+
a.yz * b.zw - a.xw * b.xy + a.zw * b.yz - a.xyzw * b.xz;
|
|
312
307
|
|
|
313
|
-
|
|
314
|
-
a.
|
|
315
|
-
a.yz * b.yw - a.xw * b.xz - a.yw * b.yz + a.xyzw * b.xy,
|
|
308
|
+
const zw = a.s * b.zw + a.zw * b.s + a.xy * b.xyzw + a.xz * b.xw +
|
|
309
|
+
a.yz * b.yw - a.xw * b.xz - a.yw * b.yz + a.xyzw * b.xy;
|
|
316
310
|
|
|
317
|
-
|
|
318
|
-
a.
|
|
319
|
-
|
|
320
|
-
)
|
|
311
|
+
const xyzw = a.s * b.xyzw + a.xyzw * b.s + a.xy * b.zw - a.xz * b.yw +
|
|
312
|
+
a.yz * b.xw + a.xw * b.yz - a.yw * b.xz + a.zw * b.xy;
|
|
313
|
+
|
|
314
|
+
if (target) {
|
|
315
|
+
target.s = s;
|
|
316
|
+
target.xy = xy;
|
|
317
|
+
target.xz = xz;
|
|
318
|
+
target.yz = yz;
|
|
319
|
+
target.xw = xw;
|
|
320
|
+
target.yw = yw;
|
|
321
|
+
target.zw = zw;
|
|
322
|
+
target.xyzw = xyzw;
|
|
323
|
+
return target;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return new Rotor4D(s, xy, xz, yz, xw, yw, zw, xyzw);
|
|
321
327
|
}
|
|
322
328
|
|
|
323
329
|
/**
|
|
@@ -429,9 +435,10 @@ export class Rotor4D {
|
|
|
429
435
|
|
|
430
436
|
/**
|
|
431
437
|
* Convert rotor to 4x4 rotation matrix (column-major for WebGL)
|
|
438
|
+
* @param {Float32Array|Array} [target] - Optional target array to write into
|
|
432
439
|
* @returns {Float32Array} 16-element array in column-major order
|
|
433
440
|
*/
|
|
434
|
-
toMatrix() {
|
|
441
|
+
toMatrix(target = null) {
|
|
435
442
|
// Normalize first for numerical stability
|
|
436
443
|
const n = this.norm();
|
|
437
444
|
const invN = n > 1e-10 ? 1 / n : 1;
|
|
@@ -495,6 +502,35 @@ export class Rotor4D {
|
|
|
495
502
|
// Formula derived from sandwich product R v R†
|
|
496
503
|
// Diagonal: s² minus bivectors containing that axis, plus others
|
|
497
504
|
// Off-diagonal: 2*s*bivector terms for single-plane contributions
|
|
505
|
+
|
|
506
|
+
if (target) {
|
|
507
|
+
// Column 0 (transformed X axis)
|
|
508
|
+
target[0] = s2 - xy2 - xz2 + yz2 - xw2 + yw2 + zw2 - xyzw2;
|
|
509
|
+
target[1] = sxy + xzyz + xwyw - zwxyzw;
|
|
510
|
+
target[2] = sxz - xyyz + xwzw + ywxyzw;
|
|
511
|
+
target[3] = sxw - xyyw - xzzw - yzxyzw;
|
|
512
|
+
|
|
513
|
+
// Column 1 (transformed Y axis)
|
|
514
|
+
target[4] = -sxy + xzyz + xwyw + zwxyzw;
|
|
515
|
+
target[5] = s2 - xy2 + xz2 - yz2 + xw2 - yw2 + zw2 - xyzw2;
|
|
516
|
+
target[6] = syz + xyxz + ywzw - xwxyzw;
|
|
517
|
+
target[7] = syw + xyxw - yzzw + xzxyzw;
|
|
518
|
+
|
|
519
|
+
// Column 2 (transformed Z axis)
|
|
520
|
+
target[8] = -sxz - xyyz + xwzw - ywxyzw;
|
|
521
|
+
target[9] = -syz + xyxz + ywzw + xwxyzw;
|
|
522
|
+
target[10] = s2 + xy2 - xz2 - yz2 + xw2 + yw2 - zw2 - xyzw2;
|
|
523
|
+
target[11] = szw + xzxw + yzyw - xyxyzw;
|
|
524
|
+
|
|
525
|
+
// Column 3 (transformed W axis)
|
|
526
|
+
target[12] = -sxw - xyyw - xzzw + yzxyzw;
|
|
527
|
+
target[13] = -syw + xyxw - yzzw - xzxyzw;
|
|
528
|
+
target[14] = -szw + xzxw + yzyw + xyxyzw;
|
|
529
|
+
target[15] = s2 + xy2 + xz2 + yz2 - xw2 - yw2 - zw2 - xyzw2;
|
|
530
|
+
|
|
531
|
+
return target;
|
|
532
|
+
}
|
|
533
|
+
|
|
498
534
|
return new Float32Array([
|
|
499
535
|
// Column 0 (transformed X axis)
|
|
500
536
|
s2 - xy2 - xz2 + yz2 - xw2 + yw2 + zw2 - xyzw2,
|
package/src/math/Vec4.js
CHANGED
|
@@ -313,7 +313,11 @@ export class Vec4 {
|
|
|
313
313
|
* @returns {number}
|
|
314
314
|
*/
|
|
315
315
|
distanceTo(v) {
|
|
316
|
-
|
|
316
|
+
const dx = this._x - v._x;
|
|
317
|
+
const dy = this._y - v._y;
|
|
318
|
+
const dz = this._z - v._z;
|
|
319
|
+
const dw = this._w - v._w;
|
|
320
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz + dw * dw);
|
|
317
321
|
}
|
|
318
322
|
|
|
319
323
|
/**
|
|
@@ -322,7 +326,11 @@ export class Vec4 {
|
|
|
322
326
|
* @returns {number}
|
|
323
327
|
*/
|
|
324
328
|
distanceToSquared(v) {
|
|
325
|
-
|
|
329
|
+
const dx = this._x - v._x;
|
|
330
|
+
const dy = this._y - v._y;
|
|
331
|
+
const dz = this._z - v._z;
|
|
332
|
+
const dw = this._w - v._w;
|
|
333
|
+
return dx * dx + dy * dy + dz * dz + dw * dw;
|
|
326
334
|
}
|
|
327
335
|
|
|
328
336
|
/**
|
|
@@ -408,42 +416,91 @@ export class Vec4 {
|
|
|
408
416
|
/**
|
|
409
417
|
* Project 4D point to 3D using perspective projection
|
|
410
418
|
* Projects from 4D to 3D by dividing by (d - w)
|
|
411
|
-
* @param {number} d - Distance parameter (usually 2-5)
|
|
412
|
-
* @param {object} [options] - Projection options
|
|
419
|
+
* @param {number|object} d - Distance parameter (usually 2-5) or options object
|
|
420
|
+
* @param {object|Vec4} [options] - Projection options or target vector
|
|
421
|
+
* @param {Vec4} [target] - Target vector to store result
|
|
413
422
|
* @returns {Vec4} Projected point (w component is 0)
|
|
414
423
|
*/
|
|
415
|
-
projectPerspective(d = 2, options = {}) {
|
|
424
|
+
projectPerspective(d = 2, options = {}, target = null) {
|
|
416
425
|
if (typeof d === 'object') {
|
|
426
|
+
// usage: projectPerspective({ distance: 2, ... }, target?)
|
|
427
|
+
if (options instanceof Vec4) {
|
|
428
|
+
target = options;
|
|
429
|
+
}
|
|
417
430
|
options = d;
|
|
418
431
|
d = options.distance ?? options.d ?? 2;
|
|
432
|
+
} else {
|
|
433
|
+
// usage: projectPerspective(d, options?, target?)
|
|
434
|
+
// usage: projectPerspective(d, target?)
|
|
435
|
+
if (options instanceof Vec4) {
|
|
436
|
+
target = options;
|
|
437
|
+
options = {};
|
|
438
|
+
}
|
|
419
439
|
}
|
|
440
|
+
|
|
441
|
+
options = options || {};
|
|
442
|
+
|
|
420
443
|
const epsilon = options.epsilon ?? 1e-5;
|
|
421
444
|
const denom = d - this._w;
|
|
422
445
|
const clamped = Math.abs(denom) < epsilon ? (denom >= 0 ? epsilon : -epsilon) : denom;
|
|
423
446
|
const scale = 1 / clamped;
|
|
447
|
+
|
|
448
|
+
if (target) {
|
|
449
|
+
target._x = this._x * scale;
|
|
450
|
+
target._y = this._y * scale;
|
|
451
|
+
target._z = this._z * scale;
|
|
452
|
+
target._w = 0;
|
|
453
|
+
return target;
|
|
454
|
+
}
|
|
455
|
+
|
|
424
456
|
return new Vec4(this._x * scale, this._y * scale, this._z * scale, 0);
|
|
425
457
|
}
|
|
426
458
|
|
|
427
459
|
/**
|
|
428
460
|
* Project 4D point to 3D using stereographic projection
|
|
429
461
|
* Maps 4D hypersphere to 3D space
|
|
430
|
-
* @param {object} [options] - Projection options
|
|
462
|
+
* @param {object|Vec4} [options] - Projection options or target vector
|
|
463
|
+
* @param {Vec4} [target] - Target vector to store result
|
|
431
464
|
* @returns {Vec4} Projected point (w component is 0)
|
|
432
465
|
*/
|
|
433
|
-
projectStereographic(options = {}) {
|
|
466
|
+
projectStereographic(options = {}, target = null) {
|
|
467
|
+
if (options instanceof Vec4) {
|
|
468
|
+
target = options;
|
|
469
|
+
options = {};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
options = options || {};
|
|
473
|
+
|
|
434
474
|
const epsilon = options.epsilon ?? 1e-5;
|
|
435
475
|
const denom = 1 - this._w;
|
|
436
476
|
const clamped = Math.abs(denom) < epsilon ? (denom >= 0 ? epsilon : -epsilon) : denom;
|
|
437
477
|
const scale = 1 / clamped;
|
|
478
|
+
|
|
479
|
+
if (target) {
|
|
480
|
+
target._x = this._x * scale;
|
|
481
|
+
target._y = this._y * scale;
|
|
482
|
+
target._z = this._z * scale;
|
|
483
|
+
target._w = 0;
|
|
484
|
+
return target;
|
|
485
|
+
}
|
|
486
|
+
|
|
438
487
|
return new Vec4(this._x * scale, this._y * scale, this._z * scale, 0);
|
|
439
488
|
}
|
|
440
489
|
|
|
441
490
|
/**
|
|
442
491
|
* Project 4D point to 3D using orthographic projection
|
|
443
492
|
* Simply drops the W component
|
|
493
|
+
* @param {Vec4} [target=null] - Optional target vector
|
|
444
494
|
* @returns {Vec4} Projected point (w component is 0)
|
|
445
495
|
*/
|
|
446
|
-
projectOrthographic() {
|
|
496
|
+
projectOrthographic(target = null) {
|
|
497
|
+
if (target) {
|
|
498
|
+
target._x = this._x;
|
|
499
|
+
target._y = this._y;
|
|
500
|
+
target._z = this._z;
|
|
501
|
+
target._w = 0;
|
|
502
|
+
return target;
|
|
503
|
+
}
|
|
447
504
|
return new Vec4(this._x, this._y, this._z, 0);
|
|
448
505
|
}
|
|
449
506
|
|
|
@@ -787,6 +787,11 @@ void main() {
|
|
|
787
787
|
}`;
|
|
788
788
|
|
|
789
789
|
this.program = this.createProgram(vertexShaderSource, fragmentShaderSource);
|
|
790
|
+
|
|
791
|
+
if (!this.program) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
790
795
|
this.uniforms = {
|
|
791
796
|
resolution: this.gl.getUniformLocation(this.program, 'u_resolution'),
|
|
792
797
|
time: this.gl.getUniformLocation(this.program, 'u_time'),
|
|
@@ -817,6 +822,18 @@ void main() {
|
|
|
817
822
|
* Create WebGL program from shaders
|
|
818
823
|
*/
|
|
819
824
|
createProgram(vertexSource, fragmentSource) {
|
|
825
|
+
// CRITICAL FIX: Check WebGL context state before shader operations
|
|
826
|
+
if (!this.gl) {
|
|
827
|
+
console.error('❌ Cannot create program: WebGL context is null');
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (this.gl.isContextLost()) {
|
|
832
|
+
console.error('❌ Cannot create program: WebGL context is lost');
|
|
833
|
+
this._contextLost = true;
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
|
|
820
837
|
const vertexShader = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
|
|
821
838
|
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
|
|
822
839
|
|
|
@@ -860,6 +877,7 @@ void main() {
|
|
|
860
877
|
|
|
861
878
|
if (this.gl.isContextLost()) {
|
|
862
879
|
console.error('❌ Cannot create shader: WebGL context is lost');
|
|
880
|
+
this._contextLost = true;
|
|
863
881
|
if (window.mobileDebug) {
|
|
864
882
|
window.mobileDebug.log(`❌ ${this.canvas?.id}: Cannot create shader - WebGL context is lost`);
|
|
865
883
|
}
|
|
@@ -924,6 +942,11 @@ void main() {
|
|
|
924
942
|
* Initialize vertex buffers
|
|
925
943
|
*/
|
|
926
944
|
initBuffers() {
|
|
945
|
+
// CRITICAL FIX: Check WebGL context state before buffer operations
|
|
946
|
+
if (!this.gl || this.gl.isContextLost()) {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
927
950
|
const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
|
|
928
951
|
|
|
929
952
|
this.buffer = this.gl.createBuffer();
|
|
@@ -939,6 +962,11 @@ void main() {
|
|
|
939
962
|
* Resize canvas and viewport
|
|
940
963
|
*/
|
|
941
964
|
resize() {
|
|
965
|
+
// CRITICAL FIX: Check WebGL context state before viewport operations
|
|
966
|
+
if (!this.gl || this.gl.isContextLost()) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
|
|
942
970
|
// Mobile-optimized canvas sizing
|
|
943
971
|
const dpr = Math.min(window.devicePixelRatio || 1, 2); // Cap at 2x for mobile performance
|
|
944
972
|
const width = this.canvas.clientWidth;
|
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
|
+
});
|
|
@@ -417,7 +417,12 @@ export class VariationManager {
|
|
|
417
417
|
* Get variation statistics
|
|
418
418
|
*/
|
|
419
419
|
getStatistics() {
|
|
420
|
-
|
|
420
|
+
let customCount = 0;
|
|
421
|
+
for (let i = 0; i < this.customVariations.length; i++) {
|
|
422
|
+
if (this.customVariations[i] !== null) {
|
|
423
|
+
customCount++;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
421
426
|
|
|
422
427
|
return {
|
|
423
428
|
totalVariations: this.totalVariations,
|
package/src/wasm/WasmLoader.js
CHANGED
|
@@ -200,12 +200,17 @@ function createFallbackModule() {
|
|
|
200
200
|
),
|
|
201
201
|
|
|
202
202
|
// Projections
|
|
203
|
-
projectPerspective: JsProjection.
|
|
204
|
-
projectStereographic: JsProjection.
|
|
205
|
-
projectOrthographic: JsProjection.
|
|
206
|
-
projectOblique: JsProjection.
|
|
207
|
-
projectSlice:
|
|
208
|
-
|
|
203
|
+
projectPerspective: JsProjection.Projection.perspective,
|
|
204
|
+
projectStereographic: JsProjection.Projection.stereographic,
|
|
205
|
+
projectOrthographic: JsProjection.Projection.orthographic,
|
|
206
|
+
projectOblique: JsProjection.Projection.oblique,
|
|
207
|
+
projectSlice: (v, wPlane, tolerance) => {
|
|
208
|
+
if (JsProjection.SliceProjection.isInSlice(v, wPlane, tolerance)) {
|
|
209
|
+
return JsProjection.Projection.orthographic(v);
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
},
|
|
213
|
+
projectToFloatArray: JsProjection.Projection.perspectivePacked
|
|
209
214
|
};
|
|
210
215
|
}
|
|
211
216
|
|