@nakednous/tree 0.0.10 → 0.0.12
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 +51 -14
- package/dist/index.js +771 -565
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,40 +31,580 @@ const _j = Object.freeze([0, -1, 0]);
|
|
|
31
31
|
const _k = Object.freeze([0, 0, -1]);
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* @file
|
|
35
|
-
* @module tree/
|
|
34
|
+
* @file Quaternion algebra and mat4/mat3 conversions.
|
|
35
|
+
* @module tree/quat
|
|
36
36
|
* @license AGPL-3.0-only
|
|
37
37
|
*
|
|
38
|
-
*
|
|
38
|
+
* Quaternions are stored as flat [x, y, z, w] arrays (w-last, glTF layout).
|
|
39
39
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* All functions follow the out-first, zero-allocation contract.
|
|
41
|
+
* Conversion functions bridge between quaternion and matrix representations
|
|
42
|
+
* but do not perform any higher-level graphics operations — those belong
|
|
43
|
+
* in form.js (matrix construction from specs) or track.js (animation).
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
// =========================================================================
|
|
48
|
+
// Basic ops
|
|
49
|
+
// =========================================================================
|
|
50
|
+
|
|
51
|
+
/** Set all four components. @returns {number[]} out */
|
|
52
|
+
const qSet = (out, x, y, z, w) => {
|
|
53
|
+
out[0] = x; out[1] = y; out[2] = z; out[3] = w; return out;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Copy quaternion a into out. @returns {number[]} out */
|
|
57
|
+
const qCopy = (out, a) => {
|
|
58
|
+
out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; return out;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Dot product of two quaternions. */
|
|
62
|
+
const qDot = (a, b) => a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3];
|
|
63
|
+
|
|
64
|
+
/** Normalise quaternion in-place. @returns {number[]} out */
|
|
65
|
+
const qNormalize = (out) => {
|
|
66
|
+
const l = Math.sqrt(out[0]*out[0]+out[1]*out[1]+out[2]*out[2]+out[3]*out[3]) || 1;
|
|
67
|
+
out[0]/=l; out[1]/=l; out[2]/=l; out[3]/=l; return out;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/** Negate quaternion (same rotation, different hemisphere). @returns {number[]} out */
|
|
71
|
+
const qNegate = (out, a) => {
|
|
72
|
+
out[0]=-a[0]; out[1]=-a[1]; out[2]=-a[2]; out[3]=-a[3]; return out;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Hamilton product out = a * b. @returns {number[]} out */
|
|
76
|
+
const qMul = (out, a, b) => {
|
|
77
|
+
const ax=a[0],ay=a[1],az=a[2],aw=a[3], bx=b[0],by=b[1],bz=b[2],bw=b[3];
|
|
78
|
+
out[0]=aw*bx+ax*bw+ay*bz-az*by;
|
|
79
|
+
out[1]=aw*by-ax*bz+ay*bw+az*bx;
|
|
80
|
+
out[2]=aw*bz+ax*by-ay*bx+az*bw;
|
|
81
|
+
out[3]=aw*bw-ax*bx-ay*by-az*bz;
|
|
82
|
+
return out;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// =========================================================================
|
|
86
|
+
// Interpolation
|
|
87
|
+
// =========================================================================
|
|
88
|
+
|
|
89
|
+
/** Spherical linear interpolation. @returns {number[]} out */
|
|
90
|
+
const qSlerp = (out, a, b, t) => {
|
|
91
|
+
let bx=b[0],by=b[1],bz=b[2],bw=b[3];
|
|
92
|
+
let d = a[0]*bx+a[1]*by+a[2]*bz+a[3]*bw;
|
|
93
|
+
if (d < 0) { bx=-bx; by=-by; bz=-bz; bw=-bw; d=-d; }
|
|
94
|
+
let f0, f1;
|
|
95
|
+
if (1-d > 1e-10) {
|
|
96
|
+
const th=Math.acos(d), st=Math.sin(th);
|
|
97
|
+
f0=Math.sin((1-t)*th)/st; f1=Math.sin(t*th)/st;
|
|
98
|
+
} else {
|
|
99
|
+
f0=1-t; f1=t;
|
|
100
|
+
}
|
|
101
|
+
out[0]=a[0]*f0+bx*f1; out[1]=a[1]*f0+by*f1;
|
|
102
|
+
out[2]=a[2]*f0+bz*f1; out[3]=a[3]*f0+bw*f1;
|
|
103
|
+
return qNormalize(out);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Normalised linear interpolation (nlerp).
|
|
108
|
+
* Cheaper than slerp; slightly non-constant angular velocity.
|
|
109
|
+
* Handles antipodal quats by flipping b when dot < 0.
|
|
110
|
+
* @returns {number[]} out
|
|
111
|
+
*/
|
|
112
|
+
const qNlerp = (out, a, b, t) => {
|
|
113
|
+
let bx=b[0],by=b[1],bz=b[2],bw=b[3];
|
|
114
|
+
if (a[0]*bx+a[1]*by+a[2]*bz+a[3]*bw < 0) { bx=-bx; by=-by; bz=-bz; bw=-bw; }
|
|
115
|
+
out[0]=a[0]+t*(bx-a[0]); out[1]=a[1]+t*(by-a[1]);
|
|
116
|
+
out[2]=a[2]+t*(bz-a[2]); out[3]=a[3]+t*(bw-a[3]);
|
|
117
|
+
return qNormalize(out);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// =========================================================================
|
|
121
|
+
// Construction
|
|
122
|
+
// =========================================================================
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build a quaternion from axis-angle.
|
|
126
|
+
* @param {number[]} out
|
|
127
|
+
* @param {number} ax @param {number} ay @param {number} az Axis (need not be unit).
|
|
128
|
+
* @param {number} angle Radians.
|
|
129
|
+
* @returns {number[]} out
|
|
130
|
+
*/
|
|
131
|
+
const qFromAxisAngle = (out, ax, ay, az, angle) => {
|
|
132
|
+
const half = angle * 0.5;
|
|
133
|
+
const s = Math.sin(half);
|
|
134
|
+
const len = Math.sqrt(ax*ax + ay*ay + az*az) || 1;
|
|
135
|
+
out[0] = s * ax / len; out[1] = s * ay / len; out[2] = s * az / len;
|
|
136
|
+
out[3] = Math.cos(half);
|
|
137
|
+
return out;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Build a quaternion from a look direction (−Z forward) and optional up (default +Y).
|
|
142
|
+
* @param {number[]} out
|
|
143
|
+
* @param {number[]} dir Forward direction [x,y,z].
|
|
144
|
+
* @param {number[]} [up] Up vector [x,y,z].
|
|
145
|
+
* @returns {number[]} out
|
|
146
|
+
*/
|
|
147
|
+
const qFromLookDir = (out, dir, up) => {
|
|
148
|
+
let fx=dir[0],fy=dir[1],fz=dir[2];
|
|
149
|
+
const fl=Math.sqrt(fx*fx+fy*fy+fz*fz)||1;
|
|
150
|
+
fx/=fl; fy/=fl; fz/=fl;
|
|
151
|
+
let ux=up?up[0]:0, uy=up?up[1]:1, uz=up?up[2]:0;
|
|
152
|
+
let rx=uy*fz-uz*fy, ry=uz*fx-ux*fz, rz=ux*fy-uy*fx;
|
|
153
|
+
const rl=Math.sqrt(rx*rx+ry*ry+rz*rz)||1;
|
|
154
|
+
rx/=rl; ry/=rl; rz/=rl;
|
|
155
|
+
ux=fy*rz-fz*ry; uy=fz*rx-fx*rz; uz=fx*ry-fy*rx;
|
|
156
|
+
return qFromRotMat3x3(out, rx,ry,rz, ux,uy,uz, -fx,-fy,-fz);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Build a quaternion from a 3×3 rotation matrix (9 row-major scalars).
|
|
161
|
+
* @returns {number[]} out (normalised)
|
|
162
|
+
*/
|
|
163
|
+
const qFromRotMat3x3 = (out, m00,m01,m02, m10,m11,m12, m20,m21,m22) => {
|
|
164
|
+
const tr = m00+m11+m22;
|
|
165
|
+
if (tr > 0) {
|
|
166
|
+
const s=0.5/Math.sqrt(tr+1);
|
|
167
|
+
out[3]=0.25/s; out[0]=(m21-m12)*s; out[1]=(m02-m20)*s; out[2]=(m10-m01)*s;
|
|
168
|
+
} else if (m00>m11 && m00>m22) {
|
|
169
|
+
const s=2*Math.sqrt(1+m00-m11-m22);
|
|
170
|
+
out[3]=(m21-m12)/s; out[0]=0.25*s; out[1]=(m01+m10)/s; out[2]=(m02+m20)/s;
|
|
171
|
+
} else if (m11>m22) {
|
|
172
|
+
const s=2*Math.sqrt(1+m11-m00-m22);
|
|
173
|
+
out[3]=(m02-m20)/s; out[0]=(m01+m10)/s; out[1]=0.25*s; out[2]=(m12+m21)/s;
|
|
174
|
+
} else {
|
|
175
|
+
const s=2*Math.sqrt(1+m22-m00-m11);
|
|
176
|
+
out[3]=(m10-m01)/s; out[0]=(m02+m20)/s; out[1]=(m12+m21)/s; out[2]=0.25*s;
|
|
177
|
+
}
|
|
178
|
+
return qNormalize(out);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extract a unit quaternion from the upper-left 3×3 of a column-major mat4.
|
|
183
|
+
* @param {number[]} out
|
|
184
|
+
* @param {Float32Array|number[]} m Column-major mat4.
|
|
185
|
+
* @returns {number[]} out
|
|
186
|
+
*/
|
|
187
|
+
const qFromMat4 = (out, m) =>
|
|
188
|
+
qFromRotMat3x3(out, m[0],m[4],m[8], m[1],m[5],m[9], m[2],m[6],m[10]);
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Write a quaternion into the rotation block of a column-major mat4.
|
|
192
|
+
* Translation and perspective rows/cols are set to identity values.
|
|
193
|
+
* @param {Float32Array|number[]} out 16-element array.
|
|
194
|
+
* @param {number[]} q [x,y,z,w].
|
|
195
|
+
* @returns {Float32Array|number[]} out
|
|
196
|
+
*/
|
|
197
|
+
const qToMat4 = (out, q) => {
|
|
198
|
+
const x=q[0],y=q[1],z=q[2],w=q[3];
|
|
199
|
+
const x2=x+x,y2=y+y,z2=z+z;
|
|
200
|
+
const xx=x*x2,xy=x*y2,xz=x*z2,yy=y*y2,yz=y*z2,zz=z*z2,wx=w*x2,wy=w*y2,wz=w*z2;
|
|
201
|
+
out[0]=1-(yy+zz); out[1]=xy+wz; out[2]=xz-wy; out[3]=0;
|
|
202
|
+
out[4]=xy-wz; out[5]=1-(xx+zz); out[6]=yz+wx; out[7]=0;
|
|
203
|
+
out[8]=xz+wy; out[9]=yz-wx; out[10]=1-(xx+yy); out[11]=0;
|
|
204
|
+
out[12]=0; out[13]=0; out[14]=0; out[15]=1;
|
|
205
|
+
return out;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// =========================================================================
|
|
209
|
+
// Decomposition
|
|
210
|
+
// =========================================================================
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Decompose a unit quaternion into { axis:[x,y,z], angle } (radians).
|
|
214
|
+
* @param {number[]} q [x,y,z,w].
|
|
215
|
+
* @param {Object} [out]
|
|
216
|
+
* @returns {{ axis: number[], angle: number }}
|
|
217
|
+
*/
|
|
218
|
+
const quatToAxisAngle = (q, out) => {
|
|
219
|
+
out = out || {};
|
|
220
|
+
const x=q[0],y=q[1],z=q[2],w=q[3];
|
|
221
|
+
const sinHalf = Math.sqrt(x*x+y*y+z*z);
|
|
222
|
+
if (sinHalf < 1e-8) { out.axis=[0,1,0]; out.angle=0; return out; }
|
|
223
|
+
out.angle = 2*Math.atan2(sinHalf, w);
|
|
224
|
+
out.axis = [x/sinHalf, y/sinHalf, z/sinHalf];
|
|
225
|
+
return out;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @file Matrix construction from geometric specs and partial decomposition.
|
|
230
|
+
* @module tree/form
|
|
231
|
+
* @license AGPL-3.0-only
|
|
232
|
+
*
|
|
233
|
+
* Constructs mat4s from higher-level specs: TRS transforms, orthonormal
|
|
234
|
+
* bases, lookat parameters, projection parameters, and special-purpose
|
|
235
|
+
* matrices (bias, reflection).
|
|
236
|
+
*
|
|
237
|
+
* Design invariant: form.js has no dependency on query.js. Construction
|
|
238
|
+
* from specs requires only scalar arithmetic and quaternion conversions.
|
|
239
|
+
* Callers compose the resulting matrices using query.js (mat4Mul etc.).
|
|
240
|
+
*
|
|
241
|
+
* Lookat constructors live here because a camera is just a frame — the eye
|
|
242
|
+
* matrix is the camera object's model matrix, not a camera-specific concept.
|
|
243
|
+
* There is no camera module; mat4View and mat4Eye are frame
|
|
244
|
+
* constructions that happen to use lookat parameterisation.
|
|
245
|
+
*
|
|
246
|
+
* Projection constructors live here because they construct matrices from
|
|
247
|
+
* geometric parameters. Projection scalar reads (projNear, projFov, etc.)
|
|
248
|
+
* live in query.js — they interrogate an existing projection matrix.
|
|
249
|
+
*
|
|
250
|
+
* Partial decomposers (mat4To___) are the inverse of construction — they
|
|
251
|
+
* extract a single component from an existing matrix. Kept alongside
|
|
252
|
+
* constructors because they are paired operations on the same components.
|
|
253
|
+
*
|
|
254
|
+
* Imports quat.js only. No dependency on query.js, visibility.js, or track.js.
|
|
255
|
+
*
|
|
256
|
+
* All functions follow the out-first, zero-allocation contract.
|
|
257
|
+
* Returns null on degeneracy where applicable.
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
// =========================================================================
|
|
262
|
+
// Frame construction
|
|
263
|
+
// =========================================================================
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Rigid frame from orthonormal basis + translation.
|
|
267
|
+
* The primitive that lookat constructors use internally.
|
|
268
|
+
*
|
|
269
|
+
* Column-major layout: col0=right, col1=up, col2=forward, col3=translation.
|
|
270
|
+
*
|
|
271
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
272
|
+
* @param {number} rx,ry,rz Right vector (col 0).
|
|
273
|
+
* @param {number} ux,uy,uz Up vector (col 1).
|
|
274
|
+
* @param {number} fx,fy,fz Forward vec (col 2).
|
|
275
|
+
* @param {number} tx,ty,tz Translation (col 3).
|
|
276
|
+
* @returns {Float32Array|number[]} out
|
|
277
|
+
*/
|
|
278
|
+
function mat4FromBasis(out, rx,ry,rz, ux,uy,uz, fx,fy,fz, tx,ty,tz) {
|
|
279
|
+
out[0]=rx; out[1]=ry; out[2]=rz; out[3]=0;
|
|
280
|
+
out[4]=ux; out[5]=uy; out[6]=uz; out[7]=0;
|
|
281
|
+
out[8]=fx; out[9]=fy; out[10]=fz; out[11]=0;
|
|
282
|
+
out[12]=tx; out[13]=ty; out[14]=tz; out[15]=1;
|
|
283
|
+
return out;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* View matrix (world→eye) from lookat parameters.
|
|
288
|
+
* Cheaper than building the eye matrix and inverting.
|
|
289
|
+
*
|
|
290
|
+
* Convention: −Z axis points toward center (camera looks along −Z in eye space).
|
|
291
|
+
*
|
|
292
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
293
|
+
* @param {number} ex,ey,ez Eye (camera) position.
|
|
294
|
+
* @param {number} cx,cy,cz Center (look-at target).
|
|
295
|
+
* @param {number} ux,uy,uz World up hint (need not be unit).
|
|
296
|
+
* @returns {Float32Array|number[]} out
|
|
297
|
+
*/
|
|
298
|
+
function mat4View(out, ex,ey,ez, cx,cy,cz, ux,uy,uz) {
|
|
299
|
+
// z = normalize(eye - center) (camera +Z away from target)
|
|
300
|
+
let zx=ex-cx, zy=ey-cy, zz=ez-cz;
|
|
301
|
+
const zl=Math.sqrt(zx*zx+zy*zy+zz*zz)||1;
|
|
302
|
+
zx/=zl; zy/=zl; zz/=zl;
|
|
303
|
+
// x = normalize(up × z) (right)
|
|
304
|
+
let xx=uy*zz-uz*zy, xy=uz*zx-ux*zz, xz=ux*zy-uy*zx;
|
|
305
|
+
const xl=Math.sqrt(xx*xx+xy*xy+xz*xz)||1;
|
|
306
|
+
xx/=xl; xy/=xl; xz/=xl;
|
|
307
|
+
// y = z × x (up_ortho, guaranteed perpendicular)
|
|
308
|
+
const yx=zy*xz-zz*xy, yy=zz*xx-zx*xz, yz=zx*xy-zy*xx;
|
|
309
|
+
// View = [R | -R·t] (column-major)
|
|
310
|
+
out[0]=xx; out[1]=yx; out[2]=zx; out[3]=0;
|
|
311
|
+
out[4]=xy; out[5]=yy; out[6]=zy; out[7]=0;
|
|
312
|
+
out[8]=xz; out[9]=yz; out[10]=zz; out[11]=0;
|
|
313
|
+
out[12]=-(xx*ex+xy*ey+xz*ez);
|
|
314
|
+
out[13]=-(yx*ex+yy*ey+yz*ez);
|
|
315
|
+
out[14]=-(zx*ex+zy*ey+zz*ez);
|
|
316
|
+
out[15]=1;
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Eye matrix (eye→world) from lookat parameters.
|
|
322
|
+
* Transpose of the rotation block + direct translation column.
|
|
323
|
+
* Same inputs as mat4View.
|
|
324
|
+
*
|
|
325
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
326
|
+
* @param {number} ex,ey,ez Eye (camera) position.
|
|
327
|
+
* @param {number} cx,cy,cz Center (look-at target).
|
|
328
|
+
* @param {number} ux,uy,uz World up hint (need not be unit).
|
|
329
|
+
* @returns {Float32Array|number[]} out
|
|
330
|
+
*/
|
|
331
|
+
function mat4Eye(out, ex,ey,ez, cx,cy,cz, ux,uy,uz) {
|
|
332
|
+
// Same basis computation as mat4View.
|
|
333
|
+
let zx=ex-cx, zy=ey-cy, zz=ez-cz;
|
|
334
|
+
const zl=Math.sqrt(zx*zx+zy*zy+zz*zz)||1;
|
|
335
|
+
zx/=zl; zy/=zl; zz/=zl;
|
|
336
|
+
let xx=uy*zz-uz*zy, xy=uz*zx-ux*zz, xz=ux*zy-uy*zx;
|
|
337
|
+
const xl=Math.sqrt(xx*xx+xy*xy+xz*xz)||1;
|
|
338
|
+
xx/=xl; xy/=xl; xz/=xl;
|
|
339
|
+
const yx=zy*xz-zz*xy, yy=zz*xx-zx*xz, yz=zx*xy-zy*xx;
|
|
340
|
+
// Eye matrix = [R^T | t] (rotation transposed, translation = eye position)
|
|
341
|
+
out[0]=xx; out[1]=xy; out[2]=xz; out[3]=0;
|
|
342
|
+
out[4]=yx; out[5]=yy; out[6]=yz; out[7]=0;
|
|
343
|
+
out[8]=zx; out[9]=zy; out[10]=zz; out[11]=0;
|
|
344
|
+
out[12]=ex; out[13]=ey; out[14]=ez; out[15]=1;
|
|
345
|
+
return out;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// =========================================================================
|
|
349
|
+
// TRS construction
|
|
350
|
+
// =========================================================================
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Column-major mat4 from flat TRS scalars.
|
|
354
|
+
* No struct allocation — all components passed as plain numbers.
|
|
355
|
+
*
|
|
356
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
357
|
+
* @param {number} tx,ty,tz Translation.
|
|
358
|
+
* @param {number} qx,qy,qz,qw Rotation quaternion [x,y,z,w].
|
|
359
|
+
* @param {number} sx,sy,sz Scale.
|
|
360
|
+
* @returns {Float32Array|number[]} out
|
|
361
|
+
*/
|
|
362
|
+
function mat4FromTRS(out, tx,ty,tz, qx,qy,qz,qw, sx,sy,sz) {
|
|
363
|
+
const x2=qx+qx,y2=qy+qy,z2=qz+qz;
|
|
364
|
+
const xx=qx*x2,xy=qx*y2,xz=qx*z2,yy=qy*y2,yz=qy*z2,zz=qz*z2;
|
|
365
|
+
const wx=qw*x2,wy=qw*y2,wz=qw*z2;
|
|
366
|
+
out[0]=(1-(yy+zz))*sx; out[1]=(xy+wz)*sx; out[2]=(xz-wy)*sx; out[3]=0;
|
|
367
|
+
out[4]=(xy-wz)*sy; out[5]=(1-(xx+zz))*sy; out[6]=(yz+wx)*sy; out[7]=0;
|
|
368
|
+
out[8]=(xz+wy)*sz; out[9]=(yz-wx)*sz; out[10]=(1-(xx+yy))*sz; out[11]=0;
|
|
369
|
+
out[12]=tx; out[13]=ty; out[14]=tz; out[15]=1;
|
|
370
|
+
return out;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Translation-only mat4.
|
|
375
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
376
|
+
* @param {number} tx,ty,tz
|
|
377
|
+
* @returns {Float32Array|number[]} out
|
|
378
|
+
*/
|
|
379
|
+
function mat4FromTranslation(out, tx,ty,tz) {
|
|
380
|
+
out[0]=1; out[1]=0; out[2]=0; out[3]=0;
|
|
381
|
+
out[4]=0; out[5]=1; out[6]=0; out[7]=0;
|
|
382
|
+
out[8]=0; out[9]=0; out[10]=1; out[11]=0;
|
|
383
|
+
out[12]=tx; out[13]=ty; out[14]=tz; out[15]=1;
|
|
384
|
+
return out;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Scale-only mat4.
|
|
389
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
390
|
+
* @param {number} sx,sy,sz
|
|
391
|
+
* @returns {Float32Array|number[]} out
|
|
392
|
+
*/
|
|
393
|
+
function mat4FromScale(out, sx,sy,sz) {
|
|
394
|
+
out[0]=sx; out[1]=0; out[2]=0; out[3]=0;
|
|
395
|
+
out[4]=0; out[5]=sy; out[6]=0; out[7]=0;
|
|
396
|
+
out[8]=0; out[9]=0; out[10]=sz; out[11]=0;
|
|
397
|
+
out[12]=0; out[13]=0; out[14]=0; out[15]=1;
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// =========================================================================
|
|
402
|
+
// Projection construction
|
|
403
|
+
// =========================================================================
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Perspective projection matrix.
|
|
407
|
+
*
|
|
408
|
+
* NDC convention: ndcZMin = WEBGL (−1) or WEBGPU (0).
|
|
409
|
+
* near maps to ndcZMin, far maps to +1.
|
|
410
|
+
*
|
|
411
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
412
|
+
* @param {number} fov Vertical field of view (radians).
|
|
413
|
+
* @param {number} aspect Width / height.
|
|
414
|
+
* @param {number} near Near plane distance (positive).
|
|
415
|
+
* @param {number} far Far plane distance (positive, > near).
|
|
416
|
+
* @param {number} ndcZMin -1 (WEBGL) or 0 (WEBGPU).
|
|
417
|
+
* @returns {Float32Array|number[]} out
|
|
418
|
+
*/
|
|
419
|
+
function mat4Perspective(out, fov, aspect, near, far, ndcZMin) {
|
|
420
|
+
const f = 1 / Math.tan(fov * 0.5);
|
|
421
|
+
out[0]=f/aspect; out[1]=0; out[2]=0; out[3]=0;
|
|
422
|
+
out[4]=0; out[5]=f; out[6]=0; out[7]=0;
|
|
423
|
+
out[8]=0; out[9]=0;
|
|
424
|
+
out[10]=(ndcZMin*near-far)/(far-near);
|
|
425
|
+
out[11]=-1;
|
|
426
|
+
out[12]=0; out[13]=0;
|
|
427
|
+
out[14]=(ndcZMin-1)*far*near/(far-near);
|
|
428
|
+
out[15]=0;
|
|
429
|
+
return out;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Orthographic projection matrix.
|
|
434
|
+
*
|
|
435
|
+
* NDC convention: ndcZMin = WEBGL (−1) or WEBGPU (0).
|
|
436
|
+
*
|
|
437
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
438
|
+
* @param {number} left,right,bottom,top Frustum extents.
|
|
439
|
+
* @param {number} near,far Clip plane distances (positive).
|
|
440
|
+
* @param {number} ndcZMin -1 (WEBGL) or 0 (WEBGPU).
|
|
441
|
+
* @returns {Float32Array|number[]} out
|
|
442
|
+
*/
|
|
443
|
+
function mat4Ortho(out, left, right, bottom, top, near, far, ndcZMin) {
|
|
444
|
+
const rl=1/(right-left), tb=1/(top-bottom), fn=1/(far-near);
|
|
445
|
+
out[0]=2*rl; out[1]=0; out[2]=0; out[3]=0;
|
|
446
|
+
out[4]=0; out[5]=2*tb; out[6]=0; out[7]=0;
|
|
447
|
+
out[8]=0; out[9]=0;
|
|
448
|
+
out[10]=(ndcZMin-1)*fn;
|
|
449
|
+
out[11]=0;
|
|
450
|
+
out[12]=-(right+left)*rl; out[13]=-(top+bottom)*tb;
|
|
451
|
+
out[14]=(ndcZMin*far-near)*fn;
|
|
452
|
+
out[15]=1;
|
|
453
|
+
return out;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Frustum (off-centre perspective) projection matrix.
|
|
458
|
+
*
|
|
459
|
+
* NDC convention: ndcZMin = WEBGL (−1) or WEBGPU (0).
|
|
460
|
+
*
|
|
461
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
462
|
+
* @param {number} left,right,bottom,top Near-plane extents.
|
|
463
|
+
* @param {number} near,far Clip plane distances (positive).
|
|
464
|
+
* @param {number} ndcZMin -1 (WEBGL) or 0 (WEBGPU).
|
|
465
|
+
* @returns {Float32Array|number[]} out
|
|
466
|
+
*/
|
|
467
|
+
function mat4Frustum(out, left, right, bottom, top, near, far, ndcZMin) {
|
|
468
|
+
const rl=1/(right-left), tb=1/(top-bottom);
|
|
469
|
+
out[0]=2*near*rl; out[1]=0; out[2]=0; out[3]=0;
|
|
470
|
+
out[4]=0; out[5]=2*near*tb; out[6]=0; out[7]=0;
|
|
471
|
+
out[8]=(right+left)*rl; out[9]=(top+bottom)*tb;
|
|
472
|
+
out[10]=(ndcZMin*near-far)/(far-near);
|
|
473
|
+
out[11]=-1;
|
|
474
|
+
out[12]=0; out[13]=0;
|
|
475
|
+
out[14]=(ndcZMin-1)*far*near/(far-near);
|
|
476
|
+
out[15]=0;
|
|
477
|
+
return out;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// =========================================================================
|
|
481
|
+
// Special-purpose construction
|
|
482
|
+
// =========================================================================
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Bias matrix: remaps xyz from NDC to texture/UV space [0,1].
|
|
486
|
+
* xy always remap from [−1,1]; z remaps from [ndcZMin,1].
|
|
487
|
+
* Used to transform light-space NDC coordinates for shadow map sampling.
|
|
488
|
+
*
|
|
489
|
+
* Column-major (WebGL, ndcZMin=−1):
|
|
490
|
+
* [ 0.5 0 0 0.5 ]
|
|
491
|
+
* [ 0 0.5 0 0.5 ]
|
|
492
|
+
* [ 0 0 0.5 0.5 ]
|
|
493
|
+
* [ 0 0 0 1 ]
|
|
494
|
+
*
|
|
495
|
+
* Column-major (WebGPU, ndcZMin=0):
|
|
496
|
+
* [ 0.5 0 0 0.5 ]
|
|
497
|
+
* [ 0 0.5 0 0.5 ]
|
|
498
|
+
* [ 0 0 1 0 ]
|
|
499
|
+
* [ 0 0 0 1 ]
|
|
500
|
+
*
|
|
501
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
502
|
+
* @param {number} ndcZMin WEBGL (−1) or WEBGPU (0).
|
|
503
|
+
* @returns {Float32Array|number[]} out
|
|
504
|
+
*/
|
|
505
|
+
function mat4Bias(out, ndcZMin) {
|
|
506
|
+
const sz = 1 / (1 - ndcZMin);
|
|
507
|
+
const tz = -ndcZMin / (1 - ndcZMin);
|
|
508
|
+
out[0]=0.5; out[1]=0; out[2]=0; out[3]=0;
|
|
509
|
+
out[4]=0; out[5]=0.5; out[6]=0; out[7]=0;
|
|
510
|
+
out[8]=0; out[9]=0; out[10]=sz; out[11]=0;
|
|
511
|
+
out[12]=0.5; out[13]=0.5; out[14]=tz; out[15]=1;
|
|
512
|
+
return out;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Reflection matrix across a plane ax + by + cz = d.
|
|
517
|
+
* [nx, ny, nz] must be a unit normal.
|
|
518
|
+
*
|
|
519
|
+
* @param {Float32Array|number[]} out 16-element destination.
|
|
520
|
+
* @param {number} nx,ny,nz Unit plane normal.
|
|
521
|
+
* @param {number} d Plane offset (dot(point_on_plane, normal)).
|
|
522
|
+
* @returns {Float32Array|number[]} out
|
|
523
|
+
*/
|
|
524
|
+
function mat4Reflect(out, nx,ny,nz,d) {
|
|
525
|
+
out[0]=1-2*nx*nx; out[1]=-2*ny*nx; out[2]=-2*nz*nx; out[3]=0;
|
|
526
|
+
out[4]=-2*nx*ny; out[5]=1-2*ny*ny; out[6]=-2*nz*ny; out[7]=0;
|
|
527
|
+
out[8]=-2*nx*nz; out[9]=-2*ny*nz; out[10]=1-2*nz*nz; out[11]=0;
|
|
528
|
+
out[12]=2*d*nx; out[13]=2*d*ny; out[14]=2*d*nz; out[15]=1;
|
|
529
|
+
return out;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// =========================================================================
|
|
533
|
+
// Partial decomposition (mat4To___ mirrors mat4From___)
|
|
534
|
+
// =========================================================================
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Extract translation from a column-major mat4 (column 3).
|
|
538
|
+
* @param {Float32Array|number[]} out3 3-element destination.
|
|
539
|
+
* @param {Float32Array|number[]} m 16-element source.
|
|
540
|
+
* @returns {Float32Array|number[]} out3
|
|
541
|
+
*/
|
|
542
|
+
function mat4ToTranslation(out3, m) {
|
|
543
|
+
out3[0]=m[12]; out3[1]=m[13]; out3[2]=m[14];
|
|
544
|
+
return out3;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Extract scale from a column-major mat4 (column lengths of rotation block).
|
|
549
|
+
* Assumes no shear.
|
|
550
|
+
* @param {Float32Array|number[]} out3 3-element destination.
|
|
551
|
+
* @param {Float32Array|number[]} m 16-element source.
|
|
552
|
+
* @returns {Float32Array|number[]} out3
|
|
553
|
+
*/
|
|
554
|
+
function mat4ToScale(out3, m) {
|
|
555
|
+
out3[0]=Math.sqrt(m[0]*m[0]+m[1]*m[1]+m[2]*m[2]);
|
|
556
|
+
out3[1]=Math.sqrt(m[4]*m[4]+m[5]*m[5]+m[6]*m[6]);
|
|
557
|
+
out3[2]=Math.sqrt(m[8]*m[8]+m[9]*m[9]+m[10]*m[10]);
|
|
558
|
+
return out3;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Extract rotation as a unit quaternion from a column-major mat4.
|
|
563
|
+
* Scale is factored out from each column before extraction.
|
|
564
|
+
* Assumes no shear.
|
|
565
|
+
* @param {number[]} out4 4-element [x,y,z,w] destination.
|
|
566
|
+
* @param {Float32Array|number[]} m 16-element source.
|
|
567
|
+
* @returns {number[]} out4
|
|
568
|
+
*/
|
|
569
|
+
function mat4ToRotation(out4, m) {
|
|
570
|
+
const sx=Math.sqrt(m[0]*m[0]+m[1]*m[1]+m[2]*m[2])||1;
|
|
571
|
+
const sy=Math.sqrt(m[4]*m[4]+m[5]*m[5]+m[6]*m[6])||1;
|
|
572
|
+
const sz=Math.sqrt(m[8]*m[8]+m[9]*m[9]+m[10]*m[10])||1;
|
|
573
|
+
return qFromRotMat3x3(out4,
|
|
574
|
+
m[0]/sx, m[4]/sy, m[8]/sz,
|
|
575
|
+
m[1]/sx, m[5]/sy, m[9]/sz,
|
|
576
|
+
m[2]/sx, m[6]/sy, m[10]/sz);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* @file Matrix arithmetic, space-transform dispatch, and projection queries.
|
|
581
|
+
* @module tree/query
|
|
582
|
+
* @license AGPL-3.0-only
|
|
583
|
+
*
|
|
584
|
+
* The operative layer — receives existing matrices and extracts information.
|
|
585
|
+
* Contrast with form.js which constructs matrices from specs.
|
|
586
|
+
*
|
|
587
|
+
* form.js — you have specs, you want a matrix
|
|
588
|
+
* query.js — you have a matrix, you want information
|
|
42
589
|
*
|
|
43
|
-
*
|
|
590
|
+
* No dependency on form.js. Operating on matrices requires no knowledge
|
|
591
|
+
* of how they were constructed.
|
|
44
592
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* V = view (world → eye)
|
|
48
|
-
* M = model (local → world)
|
|
593
|
+
* Storage: column-major Float32Array / ArrayLike<number>.
|
|
594
|
+
* Element [col*4 + row] = M[row, col].
|
|
49
595
|
*
|
|
50
|
-
*
|
|
51
|
-
* This is what _worldToScreen, _ensurePV, etc. compute.
|
|
596
|
+
* Multiply: mat4Mul(out, A, B) = A · B (standard math order).
|
|
52
597
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* (scaled first, then rotated, then translated — last-written-first-applied).
|
|
598
|
+
* Pipeline: clip = P · V · M · v
|
|
599
|
+
* P = projection (eye → clip)
|
|
600
|
+
* V = view (world → eye)
|
|
601
|
+
* M = model (local → world)
|
|
58
602
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* So p5's pvMatrix() = V.clone().mult(P) = P · V — same as ours.
|
|
63
|
-
* The bridge extracts .mat4 (Float32Array) and feeds it directly,
|
|
64
|
-
* or uses mat4Mul(out, proj, view) for the non-cached path.
|
|
603
|
+
* NDC convention parameter (ndcZMin):
|
|
604
|
+
* WEBGL = -1 z ∈ [−1, 1]
|
|
605
|
+
* WEBGPU = 0 z ∈ [0, 1]
|
|
65
606
|
*
|
|
66
|
-
*
|
|
67
|
-
* Every mutating function writes to a caller-provided `out` and returns `out`.
|
|
607
|
+
* All functions follow the out-first, zero-allocation contract.
|
|
68
608
|
* Returns null on degeneracy (singular matrix, etc.).
|
|
69
609
|
*/
|
|
70
610
|
|
|
@@ -193,6 +733,24 @@ function mat4MulPoint(out, m, x, y, z) {
|
|
|
193
733
|
return out;
|
|
194
734
|
}
|
|
195
735
|
|
|
736
|
+
/**
|
|
737
|
+
* Apply only the 3×3 linear block of a mat4 to a direction vector.
|
|
738
|
+
* No translation, no perspective divide. Suitable for directions and normals
|
|
739
|
+
* when the matrix is known to be orthogonal (use mat3NormalFromMat4 for normals
|
|
740
|
+
* under non-uniform scale).
|
|
741
|
+
*
|
|
742
|
+
* @param {Float32Array|number[]} out 3-element destination.
|
|
743
|
+
* @param {Float32Array|number[]} m 16-element mat4.
|
|
744
|
+
* @param {number} dx,dy,dz Input direction.
|
|
745
|
+
* @returns {Float32Array|number[]} out
|
|
746
|
+
*/
|
|
747
|
+
function mat4MulDir(out, m, dx, dy, dz) {
|
|
748
|
+
out[0] = m[0]*dx + m[4]*dy + m[8]*dz;
|
|
749
|
+
out[1] = m[1]*dx + m[5]*dy + m[9]*dz;
|
|
750
|
+
out[2] = m[2]*dx + m[6]*dy + m[10]*dz;
|
|
751
|
+
return out;
|
|
752
|
+
}
|
|
753
|
+
|
|
196
754
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
197
755
|
// Projection queries (read scalars from a projection mat4)
|
|
198
756
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -320,13 +878,13 @@ function mat3Direction(out, from, to) {
|
|
|
320
878
|
//
|
|
321
879
|
// Matrices bag m:
|
|
322
880
|
// {
|
|
323
|
-
// pMatrix:
|
|
324
|
-
// vMatrix:
|
|
325
|
-
// eMatrix?:
|
|
326
|
-
// pvMatrix?:
|
|
327
|
-
// ipvMatrix?:Float32Array(16) — inv(P · V); lazy
|
|
328
|
-
// fromFrame?:Float32Array(16) — MATRIX source frame (custom space)
|
|
329
|
-
// toFrameInv?:Float32Array(16)
|
|
881
|
+
// pMatrix: Float32Array(16) — projection (eye → clip)
|
|
882
|
+
// vMatrix: Float32Array(16) — view (world → eye)
|
|
883
|
+
// eMatrix?: Float32Array(16) — eye (eye → world, inv view); lazy
|
|
884
|
+
// pvMatrix?: Float32Array(16) — P · V; lazy
|
|
885
|
+
// ipvMatrix?: Float32Array(16) — inv(P · V); lazy
|
|
886
|
+
// fromFrame?: Float32Array(16) — MATRIX source frame (custom space)
|
|
887
|
+
// toFrameInv?:Float32Array(16) — inv(MATRIX dest frame)
|
|
330
888
|
// }
|
|
331
889
|
//
|
|
332
890
|
|
|
@@ -337,90 +895,62 @@ function _worldToScreen(out, px, py, pz, pv, vp, ndcZMin) {
|
|
|
337
895
|
const y = pv[1]*px+pv[5]*py+pv[9]*pz+pv[13];
|
|
338
896
|
const z = pv[2]*px+pv[6]*py+pv[10]*pz+pv[14];
|
|
339
897
|
const w = pv[3]*px+pv[7]*py+pv[11]*pz+pv[15];
|
|
340
|
-
const xi = (w !== 0 && w !== 1) ?
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
out[
|
|
345
|
-
out[
|
|
346
|
-
out[2] = (zi - ndcZMin) / ndcZRange;
|
|
898
|
+
const xi = (w !== 0 && w !== 1) ? 1/w : 1;
|
|
899
|
+
const nx = x*xi, ny = y*xi, nz = z*xi;
|
|
900
|
+
const vpX=vp[0], vpY=vp[1], vpW=Math.abs(vp[2]), vpH=Math.abs(vp[3]);
|
|
901
|
+
out[0] = vpX + vpW * (nx + 1) * 0.5;
|
|
902
|
+
out[1] = vpY + vpH * (1 - (ny + 1) * 0.5);
|
|
903
|
+
out[2] = (nz - ndcZMin) / (1 - ndcZMin);
|
|
347
904
|
return out;
|
|
348
905
|
}
|
|
349
906
|
|
|
350
|
-
function _screenToWorld(out,
|
|
351
|
-
const
|
|
352
|
-
const nx = (
|
|
353
|
-
const ny = (
|
|
354
|
-
const nz =
|
|
355
|
-
|
|
356
|
-
const y = ipv[1]*nx+ipv[5]*ny+ipv[9]*nz+ipv[13];
|
|
357
|
-
const z = ipv[2]*nx+ipv[6]*ny+ipv[10]*nz+ipv[14];
|
|
358
|
-
const w = ipv[3]*nx+ipv[7]*ny+ipv[11]*nz+ipv[15];
|
|
359
|
-
out[0]=x/w; out[1]=y/w; out[2]=z/w;
|
|
360
|
-
return out;
|
|
907
|
+
function _screenToWorld(out, sx, sy, sz, ipv, vp, ndcZMin) {
|
|
908
|
+
const vpX=vp[0], vpY=vp[1], vpW=Math.abs(vp[2]), vpH=Math.abs(vp[3]);
|
|
909
|
+
const nx = (sx - vpX) / vpW * 2 - 1;
|
|
910
|
+
const ny = 1 - (sy - vpY) / vpH * 2;
|
|
911
|
+
const nz = sz * (1 - ndcZMin) + ndcZMin;
|
|
912
|
+
return mat4MulPoint(out, ipv, nx, ny, nz);
|
|
361
913
|
}
|
|
362
914
|
|
|
363
915
|
function _worldToNDC(out, px, py, pz, pv) {
|
|
364
|
-
const x
|
|
365
|
-
const y
|
|
366
|
-
const z
|
|
367
|
-
const w
|
|
368
|
-
|
|
916
|
+
const x=pv[0]*px+pv[4]*py+pv[8]*pz+pv[12];
|
|
917
|
+
const y=pv[1]*px+pv[5]*py+pv[9]*pz+pv[13];
|
|
918
|
+
const z=pv[2]*px+pv[6]*py+pv[10]*pz+pv[14];
|
|
919
|
+
const w=pv[3]*px+pv[7]*py+pv[11]*pz+pv[15];
|
|
920
|
+
const xi = (w !== 0 && w !== 1) ? 1/w : 1;
|
|
921
|
+
out[0]=x*xi; out[1]=y*xi; out[2]=z*xi;
|
|
369
922
|
return out;
|
|
370
923
|
}
|
|
371
924
|
|
|
372
|
-
function _ndcToWorld(out,
|
|
373
|
-
|
|
374
|
-
const y = ipv[1]*px+ipv[5]*py+ipv[9]*pz+ipv[13];
|
|
375
|
-
const z = ipv[2]*px+ipv[6]*py+ipv[10]*pz+ipv[14];
|
|
376
|
-
const w = ipv[3]*px+ipv[7]*py+ipv[11]*pz+ipv[15];
|
|
377
|
-
out[0]=x/w; out[1]=y/w; out[2]=z/w;
|
|
378
|
-
return out;
|
|
925
|
+
function _ndcToWorld(out, nx, ny, nz, ipv) {
|
|
926
|
+
return mat4MulPoint(out, ipv, nx, ny, nz);
|
|
379
927
|
}
|
|
380
928
|
|
|
381
|
-
function _screenToNDC(out,
|
|
382
|
-
const
|
|
383
|
-
out[0] = (
|
|
384
|
-
out[1] = (
|
|
385
|
-
out[2] =
|
|
929
|
+
function _screenToNDC(out, sx, sy, sz, vp, ndcZMin) {
|
|
930
|
+
const vpX=vp[0], vpY=vp[1], vpW=Math.abs(vp[2]), vpH=Math.abs(vp[3]);
|
|
931
|
+
out[0] = (sx - vpX) / vpW * 2 - 1;
|
|
932
|
+
out[1] = 1 - (sy - vpY) / vpH * 2;
|
|
933
|
+
out[2] = sz * (1 - ndcZMin) + ndcZMin;
|
|
386
934
|
return out;
|
|
387
935
|
}
|
|
388
936
|
|
|
389
|
-
function _ndcToScreen(out,
|
|
390
|
-
const
|
|
391
|
-
out[0] = (
|
|
392
|
-
out[1] = (
|
|
393
|
-
out[2] = (
|
|
937
|
+
function _ndcToScreen(out, nx, ny, nz, vp, ndcZMin) {
|
|
938
|
+
const vpX=vp[0], vpY=vp[1], vpW=Math.abs(vp[2]), vpH=Math.abs(vp[3]);
|
|
939
|
+
out[0] = vpX + vpW * (nx + 1) * 0.5;
|
|
940
|
+
out[1] = vpY + vpH * (1 - (ny + 1) * 0.5);
|
|
941
|
+
out[2] = (nz - ndcZMin) / (1 - ndcZMin);
|
|
394
942
|
return out;
|
|
395
943
|
}
|
|
396
944
|
|
|
397
|
-
// ── _ensurePV — return pvMatrix from bag, computing inline if absent ──────
|
|
398
|
-
|
|
399
945
|
function _ensurePV(m) {
|
|
400
946
|
if (m.pvMatrix) return m.pvMatrix;
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
p[1]*v[0]+p[5]*v[1]+p[9]*v[2]+p[13]*v[3],
|
|
405
|
-
p[2]*v[0]+p[6]*v[1]+p[10]*v[2]+p[14]*v[3],
|
|
406
|
-
p[3]*v[0]+p[7]*v[1]+p[11]*v[2]+p[15]*v[3],
|
|
407
|
-
p[0]*v[4]+p[4]*v[5]+p[8]*v[6]+p[12]*v[7],
|
|
408
|
-
p[1]*v[4]+p[5]*v[5]+p[9]*v[6]+p[13]*v[7],
|
|
409
|
-
p[2]*v[4]+p[6]*v[5]+p[10]*v[6]+p[14]*v[7],
|
|
410
|
-
p[3]*v[4]+p[7]*v[5]+p[11]*v[6]+p[15]*v[7],
|
|
411
|
-
p[0]*v[8]+p[4]*v[9]+p[8]*v[10]+p[12]*v[11],
|
|
412
|
-
p[1]*v[8]+p[5]*v[9]+p[9]*v[10]+p[13]*v[11],
|
|
413
|
-
p[2]*v[8]+p[6]*v[9]+p[10]*v[10]+p[14]*v[11],
|
|
414
|
-
p[3]*v[8]+p[7]*v[9]+p[11]*v[10]+p[15]*v[11],
|
|
415
|
-
p[0]*v[12]+p[4]*v[13]+p[8]*v[14]+p[12]*v[15],
|
|
416
|
-
p[1]*v[12]+p[5]*v[13]+p[9]*v[14]+p[13]*v[15],
|
|
417
|
-
p[2]*v[12]+p[6]*v[13]+p[10]*v[14]+p[14]*v[15],
|
|
418
|
-
p[3]*v[12]+p[7]*v[13]+p[11]*v[14]+p[15]*v[15],
|
|
419
|
-
];
|
|
947
|
+
m.pvMatrix = new Float32Array(16);
|
|
948
|
+
mat4Mul(m.pvMatrix, m.pMatrix, m.vMatrix);
|
|
949
|
+
return m.pvMatrix;
|
|
420
950
|
}
|
|
421
951
|
|
|
422
952
|
/**
|
|
423
|
-
* Map a point between coordinate spaces.
|
|
953
|
+
* Map a point between named coordinate spaces.
|
|
424
954
|
*
|
|
425
955
|
* @param {Vec3} out Result written here.
|
|
426
956
|
* @param {number} px,py,pz Input point.
|
|
@@ -548,90 +1078,55 @@ function mapLocation(out, px, py, pz, from, to, m, vp, ndcZMin) {
|
|
|
548
1078
|
return out;
|
|
549
1079
|
}
|
|
550
1080
|
|
|
551
|
-
// ── Direction helpers
|
|
1081
|
+
// ── Direction leaf helpers ───────────────────────────────────────────────
|
|
552
1082
|
|
|
553
1083
|
/** Apply the 3×3 linear part of a mat4 (rotation/scale, no translation). */
|
|
554
|
-
function _applyDir(out,
|
|
555
|
-
out[0]=
|
|
556
|
-
out[1]=
|
|
557
|
-
out[2]=
|
|
1084
|
+
function _applyDir(out, m, dx, dy, dz) {
|
|
1085
|
+
out[0]=m[0]*dx+m[4]*dy+m[8]*dz;
|
|
1086
|
+
out[1]=m[1]*dx+m[5]*dy+m[9]*dz;
|
|
1087
|
+
out[2]=m[2]*dx+m[6]*dy+m[10]*dz;
|
|
558
1088
|
return out;
|
|
559
1089
|
}
|
|
560
1090
|
|
|
561
1091
|
function _worldToScreenDir(out, dx, dy, dz, proj, view, vpW, vpH, ndcZMin) {
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
sdx *= pixPerUnit;
|
|
573
|
-
sdy *= pixPerUnit;
|
|
574
|
-
} else {
|
|
575
|
-
const orthoW = Math.abs(projRight(proj, ndcZMin) - projLeft(proj, ndcZMin));
|
|
576
|
-
sdx *= vpW / orthoW;
|
|
577
|
-
sdy *= vpH / Math.abs(projTop(proj, ndcZMin) - projBottom(proj, ndcZMin));
|
|
578
|
-
}
|
|
579
|
-
const near = projNear(proj, ndcZMin), far = projFar(proj);
|
|
580
|
-
const depthRange = near - far;
|
|
581
|
-
let sdz;
|
|
582
|
-
if (isPersp) {
|
|
583
|
-
sdz = edz / (depthRange / Math.tan(projFov(proj) / 2));
|
|
584
|
-
} else {
|
|
585
|
-
sdz = edz / (depthRange / (Math.abs(projRight(proj, ndcZMin) - projLeft(proj, ndcZMin)) / vpW));
|
|
586
|
-
}
|
|
587
|
-
out[0] = sdx; out[1] = sdy; out[2] = sdz;
|
|
1092
|
+
// Transform to clip space (no w divide for direction).
|
|
1093
|
+
const vx=view[0]*dx+view[4]*dy+view[8]*dz;
|
|
1094
|
+
const vy=view[1]*dx+view[5]*dy+view[9]*dz;
|
|
1095
|
+
const vz=view[2]*dx+view[6]*dy+view[10]*dz;
|
|
1096
|
+
const cx=proj[0]*vx+proj[4]*vy+proj[8]*vz;
|
|
1097
|
+
const cy=proj[1]*vx+proj[5]*vy+proj[9]*vz;
|
|
1098
|
+
const cz=proj[2]*vx+proj[6]*vy+proj[10]*vz;
|
|
1099
|
+
// NDC→screen scale (direction, no offset).
|
|
1100
|
+
out[0]=cx*vpW*0.5; out[1]=-cy*vpH*0.5;
|
|
1101
|
+
out[2]=cz*(1-ndcZMin)*0.5;
|
|
588
1102
|
return out;
|
|
589
1103
|
}
|
|
590
1104
|
|
|
591
|
-
function _screenToWorldDir(out, dx, dy, dz, proj,
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
edy *= 2 * k / vpH;
|
|
600
|
-
} else {
|
|
601
|
-
const orthoW = Math.abs(projRight(proj, ndcZMin) - projLeft(proj, ndcZMin));
|
|
602
|
-
edx *= orthoW / vpW;
|
|
603
|
-
edy *= Math.abs(projTop(proj, ndcZMin) - projBottom(proj, ndcZMin)) / vpH;
|
|
604
|
-
}
|
|
605
|
-
const near = projNear(proj, ndcZMin), far = projFar(proj);
|
|
606
|
-
const depthRange = near - far;
|
|
607
|
-
let edz;
|
|
608
|
-
if (isPersp) {
|
|
609
|
-
edz = dz * (depthRange / Math.tan(projFov(proj) / 2));
|
|
610
|
-
} else {
|
|
611
|
-
edz = dz * (depthRange / (Math.abs(projRight(proj, ndcZMin) - projLeft(proj, ndcZMin)) / vpW));
|
|
612
|
-
}
|
|
613
|
-
_applyDir(out, eye, edx, edy, edz);
|
|
1105
|
+
function _screenToWorldDir(out, dx, dy, dz, proj, eMatrix, vpW, vpH, ndcZMin) {
|
|
1106
|
+
// Screen direction → NDC direction.
|
|
1107
|
+
const nx=dx/(vpW*0.5), ny=-dy/(vpH*0.5);
|
|
1108
|
+
const nz=dz/((1-ndcZMin)*0.5);
|
|
1109
|
+
// NDC direction → eye direction (inverse projection, linear only).
|
|
1110
|
+
const ex=nx/proj[0], ey=ny/proj[5], ez=nz;
|
|
1111
|
+
// Eye direction → world direction.
|
|
1112
|
+
_applyDir(out, eMatrix, ex, ey, ez);
|
|
614
1113
|
return out;
|
|
615
1114
|
}
|
|
616
1115
|
|
|
617
1116
|
function _screenToNDCDir(out, dx, dy, dz, vpW, vpH, ndcZMin) {
|
|
618
|
-
|
|
619
|
-
out[
|
|
620
|
-
out[1] = 2 * dy / vpH;
|
|
621
|
-
out[2] = dz * ndcZRange;
|
|
1117
|
+
out[0]=dx/(vpW*0.5); out[1]=-dy/(vpH*0.5);
|
|
1118
|
+
out[2]=dz/((1-ndcZMin)*0.5);
|
|
622
1119
|
return out;
|
|
623
1120
|
}
|
|
624
1121
|
|
|
625
1122
|
function _ndcToScreenDir(out, dx, dy, dz, vpW, vpH, ndcZMin) {
|
|
626
|
-
|
|
627
|
-
out[
|
|
628
|
-
out[1] = vpH * dy / 2;
|
|
629
|
-
out[2] = dz / ndcZRange;
|
|
1123
|
+
out[0]=dx*vpW*0.5; out[1]=-dy*vpH*0.5;
|
|
1124
|
+
out[2]=dz*(1-ndcZMin)*0.5;
|
|
630
1125
|
return out;
|
|
631
1126
|
}
|
|
632
1127
|
|
|
633
1128
|
/**
|
|
634
|
-
* Map a direction
|
|
1129
|
+
* Map a direction between named coordinate spaces.
|
|
635
1130
|
* Same bag contract as mapLocation.
|
|
636
1131
|
*/
|
|
637
1132
|
function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
@@ -645,7 +1140,7 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
645
1140
|
if (from === WORLD && to === SCREEN)
|
|
646
1141
|
return _worldToScreenDir(out, dx,dy,dz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
647
1142
|
if (from === SCREEN && to === WORLD)
|
|
648
|
-
return _screenToWorldDir(out, dx,dy,dz, m.pMatrix, m.
|
|
1143
|
+
return _screenToWorldDir(out, dx,dy,dz, m.pMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
649
1144
|
|
|
650
1145
|
// SCREEN ↔ NDC
|
|
651
1146
|
if (from === SCREEN && to === NDC)
|
|
@@ -662,7 +1157,7 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
662
1157
|
if (from === NDC && to === WORLD) {
|
|
663
1158
|
_ndcToScreenDir(out, dx,dy,dz, vpW, vpH, ndcZMin);
|
|
664
1159
|
const sx=out[0],sy=out[1],sz=out[2];
|
|
665
|
-
return _screenToWorldDir(out, sx,sy,sz, m.pMatrix, m.
|
|
1160
|
+
return _screenToWorldDir(out, sx,sy,sz, m.pMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
666
1161
|
}
|
|
667
1162
|
|
|
668
1163
|
// EYE ↔ SCREEN
|
|
@@ -672,7 +1167,7 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
672
1167
|
return _worldToScreenDir(out, wx,wy,wz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
673
1168
|
}
|
|
674
1169
|
if (from === SCREEN && to === EYE) {
|
|
675
|
-
_screenToWorldDir(out, dx,dy,dz, m.pMatrix, m.
|
|
1170
|
+
_screenToWorldDir(out, dx,dy,dz, m.pMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
676
1171
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
677
1172
|
return _applyDir(out, m.vMatrix, wx,wy,wz);
|
|
678
1173
|
}
|
|
@@ -688,7 +1183,7 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
688
1183
|
if (from === NDC && to === EYE) {
|
|
689
1184
|
_ndcToScreenDir(out, dx,dy,dz, vpW, vpH, ndcZMin);
|
|
690
1185
|
const sx=out[0],sy=out[1],sz=out[2];
|
|
691
|
-
_screenToWorldDir(out, sx,sy,sz, m.pMatrix, m.
|
|
1186
|
+
_screenToWorldDir(out, sx,sy,sz, m.pMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
692
1187
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
693
1188
|
return _applyDir(out, m.vMatrix, wx,wy,wz);
|
|
694
1189
|
}
|
|
@@ -716,7 +1211,7 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
716
1211
|
return _worldToScreenDir(out, wx,wy,wz, m.pMatrix, m.vMatrix, vpW, vpH, ndcZMin);
|
|
717
1212
|
}
|
|
718
1213
|
if (from === SCREEN && to === MATRIX) {
|
|
719
|
-
_screenToWorldDir(out, dx,dy,dz, m.pMatrix, m.
|
|
1214
|
+
_screenToWorldDir(out, dx,dy,dz, m.pMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
720
1215
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
721
1216
|
return _applyDir(out, m.toFrameInv, wx,wy,wz);
|
|
722
1217
|
}
|
|
@@ -732,7 +1227,7 @@ function mapDirection(out, dx, dy, dz, from, to, m, vp, ndcZMin) {
|
|
|
732
1227
|
if (from === NDC && to === MATRIX) {
|
|
733
1228
|
_ndcToScreenDir(out, dx,dy,dz, vpW, vpH, ndcZMin);
|
|
734
1229
|
const sx=out[0],sy=out[1],sz=out[2];
|
|
735
|
-
_screenToWorldDir(out, sx,sy,sz, m.pMatrix, m.
|
|
1230
|
+
_screenToWorldDir(out, sx,sy,sz, m.pMatrix, m.eMatrix, vpW, vpH, ndcZMin);
|
|
736
1231
|
const wx=out[0],wy=out[1],wz=out[2];
|
|
737
1232
|
return _applyDir(out, m.toFrameInv, wx,wy,wz);
|
|
738
1233
|
}
|
|
@@ -791,14 +1286,13 @@ function pixelRatio(proj, vpH, eyeZ, ndcZMin) {
|
|
|
791
1286
|
* @param {number} H Canvas height (CSS pixels).
|
|
792
1287
|
* @returns {Float32Array} proj (same reference)
|
|
793
1288
|
*/
|
|
794
|
-
function
|
|
1289
|
+
function mat4Pick(proj, px, py, W, H) {
|
|
795
1290
|
const cx = 2 * (px + 0.5) / W - 1;
|
|
796
|
-
const cy = -2 * (py + 0.5) / H + 1;
|
|
1291
|
+
const cy = -2 * (py + 0.5) / H + 1;
|
|
797
1292
|
const sx = W;
|
|
798
1293
|
const sy = H;
|
|
799
1294
|
const tx = -cx * W;
|
|
800
1295
|
const ty = -cy * H;
|
|
801
|
-
// P_pick = M_pick * P_orig (rows 2 and 3 are unchanged)
|
|
802
1296
|
for (let j = 0; j < 4; j++) {
|
|
803
1297
|
const a = proj[j * 4];
|
|
804
1298
|
const b = proj[j * 4 + 1];
|
|
@@ -810,14 +1304,19 @@ function applyPickMatrix(proj, px, py, W, H) {
|
|
|
810
1304
|
}
|
|
811
1305
|
|
|
812
1306
|
/**
|
|
813
|
-
* @file
|
|
1307
|
+
* @file Spline math and keyframe animation state machines.
|
|
814
1308
|
* @module tree/track
|
|
815
1309
|
* @license AGPL-3.0-only
|
|
816
1310
|
*
|
|
817
|
-
*
|
|
1311
|
+
* Quaternion algebra is provided by quat.js — this module imports and uses
|
|
1312
|
+
* it but does not define it. Spline helpers (hermiteVec3, lerpVec3) and
|
|
1313
|
+
* TRS↔mat4 conversions (transformToMat4, mat4ToTransform) remain here
|
|
1314
|
+
* because they are tightly coupled to the PoseTrack keyframe shape.
|
|
1315
|
+
*
|
|
1316
|
+
* Zero dependencies on p5, DOM, WebGL, or WebGPU.
|
|
818
1317
|
*
|
|
819
1318
|
* ── Exports ──────────────────────────────────────────────────────────────────
|
|
820
|
-
* Quaternion helpers
|
|
1319
|
+
* Quaternion helpers (re-exported from quat.js)
|
|
821
1320
|
* qSet qCopy qDot qNormalize qNegate qMul qSlerp qNlerp
|
|
822
1321
|
* qFromAxisAngle qFromLookDir qFromRotMat3x3 qFromMat4 qToMat4
|
|
823
1322
|
* quatToAxisAngle
|
|
@@ -857,13 +1356,12 @@ function applyPickMatrix(proj, px, py, W, H) {
|
|
|
857
1356
|
* reset() → onStop → _onStop → _onDeactivate
|
|
858
1357
|
*
|
|
859
1358
|
* ── Loop modes ────────────────────────────────────────────────────────────────
|
|
860
|
-
*
|
|
861
|
-
*
|
|
862
|
-
*
|
|
1359
|
+
* loop:false, bounce:false — play once, stop at end (fires onEnd)
|
|
1360
|
+
* loop:true, bounce:false — repeat, wrap back to start
|
|
1361
|
+
* loop:true, bounce:true — bounce forever at boundaries
|
|
1362
|
+
* loop:false, bounce:true — bounce once: flip at far boundary, stop at origin
|
|
863
1363
|
*
|
|
864
|
-
*
|
|
865
|
-
* bounce: true → loop is also set true
|
|
866
|
-
* loop: false → bounce is also cleared
|
|
1364
|
+
* bounce and loop are fully independent flags — no exclusivity enforced.
|
|
867
1365
|
*
|
|
868
1366
|
* ── Playback semantics (rate + _dir) ─────────────────────────────────────────
|
|
869
1367
|
* rate > 0 forward
|
|
@@ -886,175 +1384,6 @@ function applyPickMatrix(proj, px, py, W, H) {
|
|
|
886
1384
|
*/
|
|
887
1385
|
|
|
888
1386
|
|
|
889
|
-
// =========================================================================
|
|
890
|
-
// S1 Quaternion helpers (flat [x, y, z, w], w-last)
|
|
891
|
-
// =========================================================================
|
|
892
|
-
|
|
893
|
-
/** Set all four components. @returns {number[]} out */
|
|
894
|
-
const qSet = (out, x, y, z, w) => {
|
|
895
|
-
out[0] = x; out[1] = y; out[2] = z; out[3] = w; return out;
|
|
896
|
-
};
|
|
897
|
-
|
|
898
|
-
/** Copy quaternion a into out. @returns {number[]} out */
|
|
899
|
-
const qCopy = (out, a) => {
|
|
900
|
-
out[0] = a[0]; out[1] = a[1]; out[2] = a[2]; out[3] = a[3]; return out;
|
|
901
|
-
};
|
|
902
|
-
|
|
903
|
-
/** Dot product of two quaternions. */
|
|
904
|
-
const qDot = (a, b) => a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3];
|
|
905
|
-
|
|
906
|
-
/** Normalise quaternion in-place. @returns {number[]} out */
|
|
907
|
-
const qNormalize = (out) => {
|
|
908
|
-
const l = Math.sqrt(out[0]*out[0]+out[1]*out[1]+out[2]*out[2]+out[3]*out[3]) || 1;
|
|
909
|
-
out[0]/=l; out[1]/=l; out[2]/=l; out[3]/=l; return out;
|
|
910
|
-
};
|
|
911
|
-
|
|
912
|
-
/** Negate quaternion (same rotation, different hemisphere). @returns {number[]} out */
|
|
913
|
-
const qNegate = (out, a) => {
|
|
914
|
-
out[0]=-a[0]; out[1]=-a[1]; out[2]=-a[2]; out[3]=-a[3]; return out;
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
/** Hamilton product out = a * b. @returns {number[]} out */
|
|
918
|
-
const qMul = (out, a, b) => {
|
|
919
|
-
const ax=a[0],ay=a[1],az=a[2],aw=a[3], bx=b[0],by=b[1],bz=b[2],bw=b[3];
|
|
920
|
-
out[0]=aw*bx+ax*bw+ay*bz-az*by;
|
|
921
|
-
out[1]=aw*by-ax*bz+ay*bw+az*bx;
|
|
922
|
-
out[2]=aw*bz+ax*by-ay*bx+az*bw;
|
|
923
|
-
out[3]=aw*bw-ax*bx-ay*by-az*bz;
|
|
924
|
-
return out;
|
|
925
|
-
};
|
|
926
|
-
|
|
927
|
-
/** Spherical linear interpolation. @returns {number[]} out */
|
|
928
|
-
const qSlerp = (out, a, b, t) => {
|
|
929
|
-
let bx=b[0],by=b[1],bz=b[2],bw=b[3];
|
|
930
|
-
let d = a[0]*bx+a[1]*by+a[2]*bz+a[3]*bw;
|
|
931
|
-
if (d < 0) { bx=-bx; by=-by; bz=-bz; bw=-bw; d=-d; }
|
|
932
|
-
let f0, f1;
|
|
933
|
-
if (1-d > 1e-10) {
|
|
934
|
-
const th=Math.acos(d), st=Math.sin(th);
|
|
935
|
-
f0=Math.sin((1-t)*th)/st; f1=Math.sin(t*th)/st;
|
|
936
|
-
} else {
|
|
937
|
-
f0=1-t; f1=t;
|
|
938
|
-
}
|
|
939
|
-
out[0]=a[0]*f0+bx*f1; out[1]=a[1]*f0+by*f1;
|
|
940
|
-
out[2]=a[2]*f0+bz*f1; out[3]=a[3]*f0+bw*f1;
|
|
941
|
-
return qNormalize(out);
|
|
942
|
-
};
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* Normalised linear interpolation (nlerp).
|
|
946
|
-
* Cheaper than slerp; slightly non-constant angular velocity.
|
|
947
|
-
* Handles antipodal quats by flipping b when dot < 0.
|
|
948
|
-
* @returns {number[]} out
|
|
949
|
-
*/
|
|
950
|
-
const qNlerp = (out, a, b, t) => {
|
|
951
|
-
let bx=b[0],by=b[1],bz=b[2],bw=b[3];
|
|
952
|
-
if (a[0]*bx+a[1]*by+a[2]*bz+a[3]*bw < 0) { bx=-bx; by=-by; bz=-bz; bw=-bw; }
|
|
953
|
-
out[0]=a[0]+t*(bx-a[0]); out[1]=a[1]+t*(by-a[1]);
|
|
954
|
-
out[2]=a[2]+t*(bz-a[2]); out[3]=a[3]+t*(bw-a[3]);
|
|
955
|
-
return qNormalize(out);
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
/**
|
|
959
|
-
* Build a quaternion from axis-angle.
|
|
960
|
-
* @param {number[]} out
|
|
961
|
-
* @param {number} ax @param {number} ay @param {number} az Axis (need not be unit).
|
|
962
|
-
* @param {number} angle Radians.
|
|
963
|
-
* @returns {number[]} out
|
|
964
|
-
*/
|
|
965
|
-
const qFromAxisAngle = (out, ax, ay, az, angle) => {
|
|
966
|
-
const half = angle * 0.5;
|
|
967
|
-
const s = Math.sin(half);
|
|
968
|
-
const len = Math.sqrt(ax*ax + ay*ay + az*az) || 1;
|
|
969
|
-
out[0] = s * ax / len; out[1] = s * ay / len; out[2] = s * az / len;
|
|
970
|
-
out[3] = Math.cos(half);
|
|
971
|
-
return out;
|
|
972
|
-
};
|
|
973
|
-
|
|
974
|
-
/**
|
|
975
|
-
* Build a quaternion from a look direction (−Z forward) and optional up (default +Y).
|
|
976
|
-
* @param {number[]} out
|
|
977
|
-
* @param {number[]} dir Forward direction [x,y,z].
|
|
978
|
-
* @param {number[]} [up] Up vector [x,y,z].
|
|
979
|
-
* @returns {number[]} out
|
|
980
|
-
*/
|
|
981
|
-
const qFromLookDir = (out, dir, up) => {
|
|
982
|
-
let fx=dir[0],fy=dir[1],fz=dir[2];
|
|
983
|
-
const fl=Math.sqrt(fx*fx+fy*fy+fz*fz)||1;
|
|
984
|
-
fx/=fl; fy/=fl; fz/=fl;
|
|
985
|
-
let ux=up?up[0]:0, uy=up?up[1]:1, uz=up?up[2]:0;
|
|
986
|
-
let rx=uy*fz-uz*fy, ry=uz*fx-ux*fz, rz=ux*fy-uy*fx;
|
|
987
|
-
const rl=Math.sqrt(rx*rx+ry*ry+rz*rz)||1;
|
|
988
|
-
rx/=rl; ry/=rl; rz/=rl;
|
|
989
|
-
ux=fy*rz-fz*ry; uy=fz*rx-fx*rz; uz=fx*ry-fy*rx;
|
|
990
|
-
return qFromRotMat3x3(out, rx,ry,rz, ux,uy,uz, -fx,-fy,-fz);
|
|
991
|
-
};
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* Build a quaternion from a 3×3 rotation matrix (9 row-major scalars).
|
|
995
|
-
* @returns {number[]} out (normalised)
|
|
996
|
-
*/
|
|
997
|
-
const qFromRotMat3x3 = (out, m00,m01,m02, m10,m11,m12, m20,m21,m22) => {
|
|
998
|
-
const tr = m00+m11+m22;
|
|
999
|
-
if (tr > 0) {
|
|
1000
|
-
const s=0.5/Math.sqrt(tr+1);
|
|
1001
|
-
out[3]=0.25/s; out[0]=(m21-m12)*s; out[1]=(m02-m20)*s; out[2]=(m10-m01)*s;
|
|
1002
|
-
} else if (m00>m11 && m00>m22) {
|
|
1003
|
-
const s=2*Math.sqrt(1+m00-m11-m22);
|
|
1004
|
-
out[3]=(m21-m12)/s; out[0]=0.25*s; out[1]=(m01+m10)/s; out[2]=(m02+m20)/s;
|
|
1005
|
-
} else if (m11>m22) {
|
|
1006
|
-
const s=2*Math.sqrt(1+m11-m00-m22);
|
|
1007
|
-
out[3]=(m02-m20)/s; out[0]=(m01+m10)/s; out[1]=0.25*s; out[2]=(m12+m21)/s;
|
|
1008
|
-
} else {
|
|
1009
|
-
const s=2*Math.sqrt(1+m22-m00-m11);
|
|
1010
|
-
out[3]=(m10-m01)/s; out[0]=(m02+m20)/s; out[1]=(m12+m21)/s; out[2]=0.25*s;
|
|
1011
|
-
}
|
|
1012
|
-
return qNormalize(out);
|
|
1013
|
-
};
|
|
1014
|
-
|
|
1015
|
-
/**
|
|
1016
|
-
* Extract a unit quaternion from the upper-left 3×3 of a column-major mat4.
|
|
1017
|
-
* @param {number[]} out
|
|
1018
|
-
* @param {Float32Array|number[]} m Column-major mat4.
|
|
1019
|
-
* @returns {number[]} out
|
|
1020
|
-
*/
|
|
1021
|
-
const qFromMat4 = (out, m) =>
|
|
1022
|
-
qFromRotMat3x3(out, m[0],m[4],m[8], m[1],m[5],m[9], m[2],m[6],m[10]);
|
|
1023
|
-
|
|
1024
|
-
/**
|
|
1025
|
-
* Write a quaternion into the rotation block of a column-major mat4.
|
|
1026
|
-
* Translation and perspective rows/cols are set to identity values.
|
|
1027
|
-
* @param {Float32Array|number[]} out 16-element array.
|
|
1028
|
-
* @param {number[]} q [x,y,z,w].
|
|
1029
|
-
* @returns {Float32Array|number[]} out
|
|
1030
|
-
*/
|
|
1031
|
-
const qToMat4 = (out, q) => {
|
|
1032
|
-
const x=q[0],y=q[1],z=q[2],w=q[3];
|
|
1033
|
-
const x2=x+x,y2=y+y,z2=z+z;
|
|
1034
|
-
const xx=x*x2,xy=x*y2,xz=x*z2,yy=y*y2,yz=y*z2,zz=z*z2,wx=w*x2,wy=w*y2,wz=w*z2;
|
|
1035
|
-
out[0]=1-(yy+zz); out[1]=xy+wz; out[2]=xz-wy; out[3]=0;
|
|
1036
|
-
out[4]=xy-wz; out[5]=1-(xx+zz); out[6]=yz+wx; out[7]=0;
|
|
1037
|
-
out[8]=xz+wy; out[9]=yz-wx; out[10]=1-(xx+yy); out[11]=0;
|
|
1038
|
-
out[12]=0; out[13]=0; out[14]=0; out[15]=1;
|
|
1039
|
-
return out;
|
|
1040
|
-
};
|
|
1041
|
-
|
|
1042
|
-
/**
|
|
1043
|
-
* Decompose a unit quaternion into { axis:[x,y,z], angle } (radians).
|
|
1044
|
-
* @param {number[]} q [x,y,z,w].
|
|
1045
|
-
* @param {Object} [out]
|
|
1046
|
-
* @returns {{ axis: number[], angle: number }}
|
|
1047
|
-
*/
|
|
1048
|
-
const quatToAxisAngle = (q, out) => {
|
|
1049
|
-
out = out || {};
|
|
1050
|
-
const x=q[0],y=q[1],z=q[2],w=q[3];
|
|
1051
|
-
const sinHalf = Math.sqrt(x*x+y*y+z*z);
|
|
1052
|
-
if (sinHalf < 1e-8) { out.axis=[0,1,0]; out.angle=0; return out; }
|
|
1053
|
-
out.angle = 2*Math.atan2(sinHalf, w);
|
|
1054
|
-
out.axis = [x/sinHalf, y/sinHalf, z/sinHalf];
|
|
1055
|
-
return out;
|
|
1056
|
-
};
|
|
1057
|
-
|
|
1058
1387
|
// =========================================================================
|
|
1059
1388
|
// S2 Spline / vector helpers
|
|
1060
1389
|
// =========================================================================
|
|
@@ -1086,14 +1415,13 @@ const hermiteVec3 = (out, p0, m0, p1, m1, t) => {
|
|
|
1086
1415
|
|
|
1087
1416
|
// Centripetal CR outgoing tangent at p1 for segment p1→p2, scaled by dt1.
|
|
1088
1417
|
const _crTanOut = (out, p0, p1, p2, p3) => {
|
|
1089
|
-
const dt0=Math.pow(_dist3(p0,p1),0.5)||1, dt1=Math.pow(_dist3(p1,p2),0.5)||1;
|
|
1418
|
+
const dt0=Math.pow(_dist3(p0,p1),0.5)||1, dt1=Math.pow(_dist3(p1,p2),0.5)||1;
|
|
1090
1419
|
for (let i=0;i<3;i++) out[i]=((p1[i]-p0[i])/dt0-(p2[i]-p0[i])/(dt0+dt1)+(p2[i]-p1[i])/dt1)*dt1;
|
|
1091
1420
|
return out;
|
|
1092
1421
|
};
|
|
1093
1422
|
|
|
1094
|
-
// Centripetal CR incoming tangent at p2 for segment p1→p2, scaled by dt1.
|
|
1095
1423
|
const _crTanIn = (out, p0, p1, p2, p3) => {
|
|
1096
|
-
|
|
1424
|
+
const dt1=Math.pow(_dist3(p1,p2),0.5)||1, dt2=Math.pow(_dist3(p2,p3),0.5)||1;
|
|
1097
1425
|
for (let i=0;i<3;i++) out[i]=((p2[i]-p1[i])/dt1-(p3[i]-p1[i])/(dt1+dt2)+(p3[i]-p2[i])/dt2)*dt1;
|
|
1098
1426
|
return out;
|
|
1099
1427
|
};
|
|
@@ -1181,33 +1509,13 @@ const _EULER_ORDERS = new Set(['XYZ','XZY','YXZ','YZX','ZXY','ZYX']);
|
|
|
1181
1509
|
*
|
|
1182
1510
|
* Accepted forms:
|
|
1183
1511
|
*
|
|
1184
|
-
* [x,y,z,w]
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1187
|
-
* {
|
|
1188
|
-
*
|
|
1189
|
-
*
|
|
1190
|
-
* {
|
|
1191
|
-
* Object orientation — forward direction (−Z) with optional up hint.
|
|
1192
|
-
*
|
|
1193
|
-
* { eMatrix: mat4 }
|
|
1194
|
-
* Extract rotation block from an eye (eye→world) matrix.
|
|
1195
|
-
* Column-major Float32Array(16), plain Array, or { mat4 } wrapper.
|
|
1196
|
-
*
|
|
1197
|
-
* { mat3: mat3 }
|
|
1198
|
-
* Column-major 3×3 rotation matrix — Float32Array(9) or plain Array.
|
|
1199
|
-
*
|
|
1200
|
-
* { euler:[rx,ry,rz], order?:'YXZ' }
|
|
1201
|
-
* Intrinsic Euler angles (radians). Angles are indexed by order position:
|
|
1202
|
-
* e[0] rotates around order[0] axis, e[1] around order[1], e[2] around order[2].
|
|
1203
|
-
* Supported orders: YXZ (default), XYZ, ZYX, ZXY, XZY, YZX.
|
|
1204
|
-
* Note: intrinsic ABC = extrinsic CBA with the same angles — to use
|
|
1205
|
-
* extrinsic order ABC, reverse the string and use intrinsic CBA.
|
|
1206
|
-
*
|
|
1207
|
-
* { from:[x,y,z], to:[x,y,z] }
|
|
1208
|
-
* Shortest-arc rotation from one direction onto another.
|
|
1209
|
-
* Both vectors are normalised internally.
|
|
1210
|
-
* Antiparallel input: 180° rotation around a perpendicular axis.
|
|
1512
|
+
* [x,y,z,w] — raw quaternion array
|
|
1513
|
+
* { axis:[x,y,z], angle } — axis-angle
|
|
1514
|
+
* { dir:[x,y,z], up?:[x,y,z] } — forward direction (−Z) with optional up
|
|
1515
|
+
* { eMatrix: mat4 } — rotation block of an eye matrix
|
|
1516
|
+
* { mat3: mat3 } — column-major 3×3 rotation matrix
|
|
1517
|
+
* { euler:[rx,ry,rz], order? } — intrinsic Euler (default order: YXZ)
|
|
1518
|
+
* { from:[x,y,z], to:[x,y,z] } — shortest-arc rotation
|
|
1211
1519
|
*
|
|
1212
1520
|
* @param {*} v
|
|
1213
1521
|
* @returns {number[]|null} [x,y,z,w] or null if unparseable.
|
|
@@ -1215,46 +1523,48 @@ const _EULER_ORDERS = new Set(['XYZ','XZY','YXZ','YZX','ZXY','ZYX']);
|
|
|
1215
1523
|
function _parseQuat(v) {
|
|
1216
1524
|
if (!v) return null;
|
|
1217
1525
|
|
|
1218
|
-
//
|
|
1219
|
-
if (
|
|
1526
|
+
// Raw array [x,y,z,w]
|
|
1527
|
+
if (Array.isArray(v) && v.length === 4) return [v[0],v[1],v[2],v[3]];
|
|
1528
|
+
if (ArrayBuffer.isView(v) && v.length >= 4) return [v[0],v[1],v[2],v[3]];
|
|
1529
|
+
|
|
1530
|
+
if (typeof v !== 'object') return null;
|
|
1220
1531
|
|
|
1221
1532
|
// { axis, angle }
|
|
1222
|
-
if (v.axis &&
|
|
1223
|
-
const
|
|
1224
|
-
return qFromAxisAngle([0,0,0,1],
|
|
1533
|
+
if (v.axis != null && v.angle != null) {
|
|
1534
|
+
const ax = Array.isArray(v.axis) ? v.axis : [v.axis.x||0, v.axis.y||0, v.axis.z||0];
|
|
1535
|
+
return qFromAxisAngle([0,0,0,1], ax[0],ax[1],ax[2], v.angle);
|
|
1225
1536
|
}
|
|
1226
1537
|
|
|
1227
1538
|
// { dir, up? }
|
|
1228
|
-
if (v.dir) {
|
|
1539
|
+
if (v.dir != null) {
|
|
1229
1540
|
const d = Array.isArray(v.dir) ? v.dir : [v.dir.x||0, v.dir.y||0, v.dir.z||0];
|
|
1230
1541
|
const u = v.up ? (Array.isArray(v.up) ? v.up : [v.up.x||0, v.up.y||0, v.up.z||0]) : null;
|
|
1231
1542
|
return qFromLookDir([0,0,0,1], d, u);
|
|
1232
1543
|
}
|
|
1233
1544
|
|
|
1234
|
-
// { eMatrix }
|
|
1545
|
+
// { eMatrix }
|
|
1235
1546
|
if (v.eMatrix != null) {
|
|
1236
1547
|
const m = (ArrayBuffer.isView(v.eMatrix) || Array.isArray(v.eMatrix))
|
|
1237
1548
|
? v.eMatrix : (v.eMatrix.mat4 ?? null);
|
|
1238
|
-
if (m
|
|
1549
|
+
if (!m || m.length < 16) return null;
|
|
1550
|
+
return qFromRotMat3x3([0,0,0,1], m[0],m[4],m[8], m[1],m[5],m[9], m[2],m[6],m[10]);
|
|
1239
1551
|
}
|
|
1240
1552
|
|
|
1241
|
-
// { mat3 }
|
|
1242
|
-
// col0=[m0,m1,m2], col1=[m3,m4,m5], col2=[m6,m7,m8]
|
|
1243
|
-
// row-major for qFromRotMat3x3: row0=[m0,m3,m6], row1=[m1,m4,m7], row2=[m2,m5,m8]
|
|
1553
|
+
// { mat3 }
|
|
1244
1554
|
if (v.mat3 != null) {
|
|
1245
|
-
const m = v.mat3
|
|
1246
|
-
|
|
1247
|
-
|
|
1555
|
+
const m = (ArrayBuffer.isView(v.mat3) || Array.isArray(v.mat3))
|
|
1556
|
+
? v.mat3 : null;
|
|
1557
|
+
if (!m || m.length < 9) return null;
|
|
1558
|
+
return qFromRotMat3x3([0,0,0,1], m[0],m[3],m[6], m[1],m[4],m[7], m[2],m[5],m[8]);
|
|
1248
1559
|
}
|
|
1249
1560
|
|
|
1250
|
-
// { euler, order? }
|
|
1561
|
+
// { euler, order? }
|
|
1251
1562
|
if (v.euler != null) {
|
|
1252
1563
|
const e = v.euler;
|
|
1253
1564
|
if (!Array.isArray(e) || e.length < 3) return null;
|
|
1254
|
-
const order = (
|
|
1255
|
-
? v.order : 'YXZ';
|
|
1565
|
+
const order = (v.order && _EULER_ORDERS.has(v.order)) ? v.order : 'YXZ';
|
|
1256
1566
|
const q = [0,0,0,1];
|
|
1257
|
-
const s = [0,0,0,1];
|
|
1567
|
+
const s = [0,0,0,1];
|
|
1258
1568
|
for (let i = 0; i < 3; i++) {
|
|
1259
1569
|
const ax = _EULER_AXES[order[i]];
|
|
1260
1570
|
qMul(q, q, qFromAxisAngle(s, ax[0],ax[1],ax[2], e[i]));
|
|
@@ -1262,7 +1572,7 @@ function _parseQuat(v) {
|
|
|
1262
1572
|
return q;
|
|
1263
1573
|
}
|
|
1264
1574
|
|
|
1265
|
-
// { from, to }
|
|
1575
|
+
// { from, to }
|
|
1266
1576
|
if (v.from != null && v.to != null) {
|
|
1267
1577
|
const f = Array.isArray(v.from) ? v.from : [v.from.x||0, v.from.y||0, v.from.z||0];
|
|
1268
1578
|
const t = Array.isArray(v.to) ? v.to : [v.to.x||0, v.to.y||0, v.to.z||0];
|
|
@@ -1271,22 +1581,14 @@ function _parseQuat(v) {
|
|
|
1271
1581
|
const fx=f[0]/fl, fy=f[1]/fl, fz=f[2]/fl;
|
|
1272
1582
|
const tx=t[0]/tl, ty=t[1]/tl, tz=t[2]/tl;
|
|
1273
1583
|
const dot = fx*tx + fy*ty + fz*tz;
|
|
1274
|
-
// parallel — identity
|
|
1275
1584
|
if (dot >= 1 - 1e-8) return [0,0,0,1];
|
|
1276
|
-
// antiparallel — 180° around any perpendicular axis
|
|
1277
1585
|
if (dot <= -1 + 1e-8) {
|
|
1278
|
-
// cross(from, X=[1,0,0]) = [0, fz, -fy]
|
|
1279
1586
|
let px=0, py=fz, pz=-fy;
|
|
1280
1587
|
let pl = Math.sqrt(px*px+py*py+pz*pz);
|
|
1281
|
-
if (pl < 1e-8) {
|
|
1282
|
-
// from ≈ ±X; try cross(from, Z=[0,0,1]) = [fy, -fx, 0]
|
|
1283
|
-
px=fy; py=-fx; pz=0;
|
|
1284
|
-
pl = Math.sqrt(px*px+py*py+pz*pz);
|
|
1285
|
-
}
|
|
1588
|
+
if (pl < 1e-8) { px=fy; py=-fx; pz=0; pl = Math.sqrt(px*px+py*py+pz*pz); }
|
|
1286
1589
|
if (pl < 1e-8) return [0,0,0,1];
|
|
1287
1590
|
return qFromAxisAngle([0,0,0,1], px/pl,py/pl,pz/pl, Math.PI);
|
|
1288
1591
|
}
|
|
1289
|
-
// general case — axis = normalize(cross(from, to))
|
|
1290
1592
|
let ax=fy*tz-fz*ty, ay=fz*tx-fx*tz, az=fx*ty-fy*tx;
|
|
1291
1593
|
const al = Math.sqrt(ax*ax+ay*ay+az*az) || 1;
|
|
1292
1594
|
return qFromAxisAngle([0,0,0,1], ax/al,ay/al,az/al,
|
|
@@ -1304,14 +1606,12 @@ function _parseQuat(v) {
|
|
|
1304
1606
|
* { mMatrix }
|
|
1305
1607
|
* Decompose a column-major mat4 into TRS via mat4ToTransform.
|
|
1306
1608
|
* Float32Array(16), plain Array, or { mat4 } wrapper.
|
|
1307
|
-
* pos from col3, scl from column lengths, rot from normalised rotation block.
|
|
1308
1609
|
*
|
|
1309
1610
|
* { pos?, rot?, scl?, tanIn?, tanOut? }
|
|
1310
1611
|
* Explicit TRS. pos and scl are vec3, rot accepts any form from _parseQuat.
|
|
1311
1612
|
* All fields are optional — missing pos/scl default to [0,0,0] / [1,1,1],
|
|
1312
1613
|
* missing rot defaults to identity.
|
|
1313
1614
|
* tanIn/tanOut are optional vec3 tangents for Hermite interpolation.
|
|
1314
|
-
* When absent, centripetal Catmull-Rom tangents are auto-computed at eval time.
|
|
1315
1615
|
*
|
|
1316
1616
|
* @param {Object} spec
|
|
1317
1617
|
* @returns {{ pos:number[], rot:number[], scl:number[], tanIn:number[]|null, tanOut:number[]|null }|null}
|
|
@@ -1355,22 +1655,12 @@ function _sameTransform(a, b) {
|
|
|
1355
1655
|
* { eye, center?, up?, fov?, halfHeight?,
|
|
1356
1656
|
* eyeTanIn?, eyeTanOut?, centerTanIn?, centerTanOut? }
|
|
1357
1657
|
* Explicit lookat. center defaults to [0,0,0], up defaults to [0,1,0].
|
|
1358
|
-
* Both are normalised/stored as-is. eye must be a vec3.
|
|
1359
1658
|
* eyeTanIn/Out and centerTanIn/Out are optional vec3 tangents for Hermite.
|
|
1360
1659
|
* When absent, centripetal Catmull-Rom tangents are auto-computed at eval time.
|
|
1361
1660
|
*
|
|
1362
|
-
*
|
|
1363
|
-
*
|
|
1364
|
-
*
|
|
1365
|
-
* The matrix's up_ortho (col1) is intentionally NOT used as up —
|
|
1366
|
-
* passing it to cam.camera() shifts orbitControl's orbit reference.
|
|
1367
|
-
* Float32Array(16), plain Array, or { mat4 } wrapper.
|
|
1368
|
-
*
|
|
1369
|
-
* { eMatrix: mat4 }
|
|
1370
|
-
* Column-major eye matrix (eye→world, i.e. inverse view).
|
|
1371
|
-
* eye read directly from col3; center = eye + forward·1; up = [0,1,0].
|
|
1372
|
-
* Simpler extraction than vMatrix; prefer this form when eMatrix is available.
|
|
1373
|
-
* Float32Array(16), plain Array, or { mat4 } wrapper.
|
|
1661
|
+
* Removed forms (task 2):
|
|
1662
|
+
* { vMatrix } and { eMatrix } — use PoseTrack.add({ mMatrix: eMatrix }) for
|
|
1663
|
+
* full-fidelity capture including roll, or cam.capturePose() for lookat-style.
|
|
1374
1664
|
*
|
|
1375
1665
|
* @param {Object} spec
|
|
1376
1666
|
* @returns {{ eye:number[], center:number[], up:number[],
|
|
@@ -1381,36 +1671,8 @@ function _sameTransform(a, b) {
|
|
|
1381
1671
|
function _parseCameraSpec(spec) {
|
|
1382
1672
|
if (!spec || typeof spec !== 'object') return null;
|
|
1383
1673
|
|
|
1384
|
-
// {
|
|
1385
|
-
|
|
1386
|
-
const m = (ArrayBuffer.isView(spec.vMatrix) || Array.isArray(spec.vMatrix))
|
|
1387
|
-
? spec.vMatrix : (spec.vMatrix.mat4 ?? null);
|
|
1388
|
-
if (!m || m.length < 16) return null;
|
|
1389
|
-
const ex = -(m[0]*m[12] + m[4]*m[13] + m[8]*m[14]);
|
|
1390
|
-
const ey = -(m[1]*m[12] + m[5]*m[13] + m[9]*m[14]);
|
|
1391
|
-
const ez = -(m[2]*m[12] + m[6]*m[13] + m[10]*m[14]);
|
|
1392
|
-
const fx=-m[8], fy=-m[9], fz=-m[10];
|
|
1393
|
-
const fl=Math.sqrt(fx*fx+fy*fy+fz*fz)||1;
|
|
1394
|
-
return { eye:[ex,ey,ez], center:[ex+fx/fl,ey+fy/fl,ez+fz/fl], up:[0,1,0],
|
|
1395
|
-
fov:null, halfHeight:null,
|
|
1396
|
-
eyeTanIn:null, eyeTanOut:null, centerTanIn:null, centerTanOut:null };
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
// { eMatrix } — eye matrix (eye→world); eye = col3, forward = -col2
|
|
1400
|
-
if (spec.eMatrix != null) {
|
|
1401
|
-
const m = (ArrayBuffer.isView(spec.eMatrix) || Array.isArray(spec.eMatrix))
|
|
1402
|
-
? spec.eMatrix : (spec.eMatrix.mat4 ?? null);
|
|
1403
|
-
if (!m || m.length < 16) return null;
|
|
1404
|
-
const ex=m[12], ey=m[13], ez=m[14];
|
|
1405
|
-
const fx=-m[8], fy=-m[9], fz=-m[10];
|
|
1406
|
-
const fl=Math.sqrt(fx*fx+fy*fy+fz*fz)||1;
|
|
1407
|
-
return { eye:[ex,ey,ez], center:[ex+fx/fl,ey+fy/fl,ez+fz/fl], up:[0,1,0],
|
|
1408
|
-
fov:null, halfHeight:null,
|
|
1409
|
-
eyeTanIn:null, eyeTanOut:null, centerTanIn:null, centerTanOut:null };
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
// { eye, center?, up? } — explicit lookat (eye is a vec3, not a mat4)
|
|
1413
|
-
const eye = _parseVec3(spec.eye);
|
|
1674
|
+
// { eye, center?, up? } — explicit lookat
|
|
1675
|
+
const eye = _parseVec3(spec.eye);
|
|
1414
1676
|
if (!eye) return null;
|
|
1415
1677
|
const center = _parseVec3(spec.center) || [0,0,0];
|
|
1416
1678
|
const upRaw = spec.up ? _parseVec3(spec.up) : null;
|
|
@@ -1452,7 +1714,7 @@ class Track {
|
|
|
1452
1714
|
/** Loop at boundaries. @type {boolean} */
|
|
1453
1715
|
this.loop = false;
|
|
1454
1716
|
/** Ping-pong bounce (takes precedence over loop). @type {boolean} */
|
|
1455
|
-
this.bounce
|
|
1717
|
+
this.bounce = false;
|
|
1456
1718
|
/** Frames per segment (≥1). @type {number} */
|
|
1457
1719
|
this.duration = 30;
|
|
1458
1720
|
/** Current segment index. @type {number} */
|
|
@@ -1463,9 +1725,9 @@ class Track {
|
|
|
1463
1725
|
// Internal rate — never directly starts/stops playback
|
|
1464
1726
|
this._rate = 1;
|
|
1465
1727
|
// Internal bounce direction: +1 forward, -1 backward.
|
|
1466
|
-
// Flipped by tick() at boundaries. Never exposed publicly.
|
|
1467
|
-
// rate always holds the user-set value — only _dir changes.
|
|
1468
1728
|
this._dir = 1;
|
|
1729
|
+
// Scratch: true once _dir has been flipped in bounce-once mode.
|
|
1730
|
+
this._bounced = false;
|
|
1469
1731
|
|
|
1470
1732
|
// User-space hooks
|
|
1471
1733
|
/** @type {Function|null} */ this.onPlay = null;
|
|
@@ -1508,8 +1770,8 @@ class Track {
|
|
|
1508
1770
|
} else if (rateOrOpts && typeof rateOrOpts === 'object') {
|
|
1509
1771
|
const o = rateOrOpts;
|
|
1510
1772
|
if (_isNum(o.duration)) this.duration = Math.max(1, o.duration | 0);
|
|
1511
|
-
if ('loop'
|
|
1512
|
-
if ('bounce' in o)
|
|
1773
|
+
if ('loop' in o) this.loop = !!o.loop;
|
|
1774
|
+
if ('bounce' in o) this.bounce = !!o.bounce;
|
|
1513
1775
|
if (typeof o.onPlay === 'function') this.onPlay = o.onPlay;
|
|
1514
1776
|
if (typeof o.onEnd === 'function') this.onEnd = o.onEnd;
|
|
1515
1777
|
if (typeof o.onStop === 'function') this.onStop = o.onStop;
|
|
@@ -1525,6 +1787,7 @@ class Track {
|
|
|
1525
1787
|
const wasPlaying = this.playing;
|
|
1526
1788
|
this.playing = true;
|
|
1527
1789
|
if (!wasPlaying) {
|
|
1790
|
+
this._bounced = false;
|
|
1528
1791
|
if (typeof this.onPlay === 'function') { try { this.onPlay(this); } catch (_) {} }
|
|
1529
1792
|
this._onPlay?.();
|
|
1530
1793
|
this._onActivate?.();
|
|
@@ -1541,6 +1804,7 @@ class Track {
|
|
|
1541
1804
|
const wasPlaying = this.playing;
|
|
1542
1805
|
this.playing = false;
|
|
1543
1806
|
if (wasPlaying) {
|
|
1807
|
+
this._bounced = false;
|
|
1544
1808
|
if (typeof this.onStop === 'function') { try { this.onStop(this); } catch (_) {} }
|
|
1545
1809
|
this._onStop?.();
|
|
1546
1810
|
this._onDeactivate?.();
|
|
@@ -1562,7 +1826,7 @@ class Track {
|
|
|
1562
1826
|
this._onDeactivate?.();
|
|
1563
1827
|
}
|
|
1564
1828
|
this.keyframes.length = 0;
|
|
1565
|
-
this.seg = 0; this.f = 0; this._dir = 1;
|
|
1829
|
+
this.seg = 0; this.f = 0; this._dir = 1; this._bounced = false;
|
|
1566
1830
|
return this;
|
|
1567
1831
|
}
|
|
1568
1832
|
|
|
@@ -1624,7 +1888,7 @@ class Track {
|
|
|
1624
1888
|
f: this.f,
|
|
1625
1889
|
playing: this.playing,
|
|
1626
1890
|
loop: this.loop,
|
|
1627
|
-
bounce:
|
|
1891
|
+
bounce: this.bounce,
|
|
1628
1892
|
rate: this._rate,
|
|
1629
1893
|
duration: this.duration,
|
|
1630
1894
|
time: this.segments > 0 ? this.time() : 0
|
|
@@ -1649,7 +1913,8 @@ class Track {
|
|
|
1649
1913
|
const s = _clampS(this.seg * dur + this.f, 0, total);
|
|
1650
1914
|
const next = s + this._rate * this._dir;
|
|
1651
1915
|
|
|
1652
|
-
|
|
1916
|
+
// ── loop:true, bounce:true — bounce forever ───────────────────────────
|
|
1917
|
+
if (this.loop && this.bounce) {
|
|
1653
1918
|
let pos = next, flips = 0;
|
|
1654
1919
|
while (pos < 0 || pos > total) {
|
|
1655
1920
|
if (pos < 0) { pos = -pos; flips++; }
|
|
@@ -1660,11 +1925,36 @@ class Track {
|
|
|
1660
1925
|
return true;
|
|
1661
1926
|
}
|
|
1662
1927
|
|
|
1928
|
+
// ── loop:false, bounce:true — bounce once, stop at origin ────────────
|
|
1929
|
+
if (!this.loop && this.bounce) {
|
|
1930
|
+
if (next >= total) {
|
|
1931
|
+
// far boundary: reflect and flip direction once
|
|
1932
|
+
this._setCursorFromScalar(Math.min(total, 2 * total - next));
|
|
1933
|
+
this._dir = -this._dir;
|
|
1934
|
+
this._bounced = true;
|
|
1935
|
+
return true;
|
|
1936
|
+
}
|
|
1937
|
+
if (next <= 0) {
|
|
1938
|
+
// origin: stop (whether we bounced or started backward)
|
|
1939
|
+
this._setCursorFromScalar(0);
|
|
1940
|
+
this.playing = false;
|
|
1941
|
+
this._dir = 1; this._bounced = false;
|
|
1942
|
+
if (typeof this.onEnd === 'function') { try { this.onEnd(this); } catch (_) {} }
|
|
1943
|
+
this._onEnd?.();
|
|
1944
|
+
this._onDeactivate?.();
|
|
1945
|
+
return false;
|
|
1946
|
+
}
|
|
1947
|
+
this._setCursorFromScalar(next);
|
|
1948
|
+
return true;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
// ── loop:true, bounce:false — repeat forever ──────────────────────────
|
|
1663
1952
|
if (this.loop) {
|
|
1664
1953
|
this._setCursorFromScalar(((next % total) + total) % total);
|
|
1665
1954
|
return true;
|
|
1666
1955
|
}
|
|
1667
1956
|
|
|
1957
|
+
// ── loop:false, bounce:false — play once, stop at boundary ───────────
|
|
1668
1958
|
if (next <= 0) {
|
|
1669
1959
|
this._setCursorFromScalar(0);
|
|
1670
1960
|
this.playing = false;
|
|
@@ -1711,47 +2001,16 @@ class Track {
|
|
|
1711
2001
|
* tanOut — outgoing position tangent at this keyframe (Hermite mode).
|
|
1712
2002
|
* When only one is supplied, the other mirrors it.
|
|
1713
2003
|
* When neither is supplied, centripetal Catmull-Rom tangents are auto-computed.
|
|
1714
|
-
*
|
|
1715
|
-
* add() accepts individual specs or a bulk array of specs:
|
|
1716
|
-
*
|
|
1717
|
-
* { mMatrix } — full TRS from model matrix
|
|
1718
|
-
* { pos?, rot?, scl?, tanIn?, tanOut? } — direct TRS; all fields optional
|
|
1719
|
-
* { pos?, rot: [x,y,z,w] } — explicit quaternion
|
|
1720
|
-
* { pos?, rot: { axis, angle } } — axis-angle
|
|
1721
|
-
* { pos?, rot: { dir, up? } } — look direction
|
|
1722
|
-
* { pos?, rot: { eMatrix: mat4 } } — rotation from eye matrix
|
|
1723
|
-
* { pos?, rot: { mat3 } } — column-major 3×3 rotation matrix
|
|
1724
|
-
* { pos?, rot: { euler, order? } } — intrinsic Euler angles (default YXZ)
|
|
1725
|
-
* { pos?, rot: { from, to } } — shortest-arc between two directions
|
|
1726
|
-
* [ spec, spec, ... ] — bulk
|
|
1727
|
-
*
|
|
1728
|
-
* Missing fields default to: pos → [0,0,0], rot → [0,0,0,1], scl → [1,1,1].
|
|
1729
|
-
*
|
|
1730
|
-
* eval() writes { pos, rot, scl }:
|
|
1731
|
-
* pos — Hermite (tanIn/tanOut per keyframe; auto-CR when absent) or linear or step
|
|
1732
|
-
* rot — slerp (rotInterp='slerp') or nlerp or step
|
|
1733
|
-
* scl — lerp
|
|
1734
|
-
*
|
|
1735
|
-
* @example
|
|
1736
|
-
* const track = new PoseTrack()
|
|
1737
|
-
* track.add({ pos:[0,0,0] })
|
|
1738
|
-
* track.add({ pos:[100,0,0], tanOut:[0,50,0] }) // leave heading +Y
|
|
1739
|
-
* track.add({ pos:[200,0,0] })
|
|
1740
|
-
* track.play({ loop: true })
|
|
1741
|
-
* // per frame:
|
|
1742
|
-
* track.tick()
|
|
1743
|
-
* const out = { pos:[0,0,0], rot:[0,0,0,1], scl:[1,1,1] }
|
|
1744
|
-
* track.eval(out)
|
|
1745
2004
|
*/
|
|
1746
2005
|
class PoseTrack extends Track {
|
|
1747
2006
|
constructor() {
|
|
1748
2007
|
super();
|
|
1749
2008
|
/**
|
|
1750
2009
|
* Position interpolation mode.
|
|
1751
|
-
* - 'hermite' — cubic Hermite;
|
|
1752
|
-
*
|
|
2010
|
+
* - 'hermite' — cubic Hermite; auto-computes centripetal Catmull-Rom tangents
|
|
2011
|
+
* when none are stored (default)
|
|
1753
2012
|
* - 'linear' — lerp
|
|
1754
|
-
* - 'step' — snap to k0
|
|
2013
|
+
* - 'step' — snap to k0; useful for discrete state changes
|
|
1755
2014
|
* @type {'hermite'|'linear'|'step'}
|
|
1756
2015
|
*/
|
|
1757
2016
|
this.posInterp = 'hermite';
|
|
@@ -1836,11 +2095,9 @@ class PoseTrack extends Track {
|
|
|
1836
2095
|
} else {
|
|
1837
2096
|
const p0 = seg > 0 ? this.keyframes[seg - 1].pos : k0.pos;
|
|
1838
2097
|
const p3 = seg + 2 < n ? this.keyframes[seg + 2].pos : k1.pos;
|
|
1839
|
-
// tanOut on k0: use stored, else symmetric from tanIn, else auto-CR
|
|
1840
2098
|
const m0 = k0.tanOut != null ? k0.tanOut
|
|
1841
2099
|
: k0.tanIn != null ? k0.tanIn
|
|
1842
|
-
: _crTanOut(_m0, p0, k0.pos, k1.pos
|
|
1843
|
-
// tanIn on k1: use stored, else symmetric from tanOut, else auto-CR
|
|
2100
|
+
: _crTanOut(_m0, p0, k0.pos, k1.pos);
|
|
1844
2101
|
const m1 = k1.tanIn != null ? k1.tanIn
|
|
1845
2102
|
: k1.tanOut != null ? k1.tanOut
|
|
1846
2103
|
: _crTanIn(_m1, p0, k0.pos, k1.pos, p3);
|
|
@@ -1894,83 +2151,34 @@ class PoseTrack extends Track {
|
|
|
1894
2151
|
* interpolation of the eye and center paths respectively.
|
|
1895
2152
|
* When absent, centripetal Catmull-Rom tangents are auto-computed at eval time.
|
|
1896
2153
|
*
|
|
1897
|
-
* Each field is independently interpolated — eye and center along their
|
|
1898
|
-
* own paths, up nlerped on the unit sphere. This correctly handles cameras
|
|
1899
|
-
* that always look at a fixed target (center stays at origin throughout)
|
|
1900
|
-
* as well as free-fly paths where center moves independently.
|
|
1901
|
-
*
|
|
1902
2154
|
* Missing fields default to: center → [0,0,0], up → [0,1,0].
|
|
1903
2155
|
*
|
|
1904
2156
|
* add() accepts individual specs or a bulk array of specs:
|
|
1905
2157
|
*
|
|
1906
2158
|
* { eye, center?, up?, fov?, halfHeight?,
|
|
1907
2159
|
* eyeTanIn?, eyeTanOut?, centerTanIn?, centerTanOut? }
|
|
1908
|
-
*
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
1911
|
-
* { eMatrix: mat4 } eye matrix (eye→world); eye read from col3 directly
|
|
1912
|
-
* [ spec, spec, ... ] bulk
|
|
1913
|
-
*
|
|
1914
|
-
* Note on up for matrix forms:
|
|
1915
|
-
* up is always [0,1,0]. The matrix's col1 (up_ortho) is intentionally
|
|
1916
|
-
* not used — it differs from the hint [0,1,0] for upright cameras and
|
|
1917
|
-
* passing it to cam.camera() shifts orbitControl's orbit reference.
|
|
1918
|
-
* Use capturePose() (p5.tree bridge) when the real up hint is needed.
|
|
1919
|
-
*
|
|
1920
|
-
* eval() writes { eye, center, up, fov, halfHeight }:
|
|
1921
|
-
* eye — Hermite (auto-CR when no tangents stored) or linear or step
|
|
1922
|
-
* center — Hermite (auto-CR when no tangents stored) or linear or step
|
|
1923
|
-
* up — nlerp (normalize-after-lerp on unit sphere)
|
|
1924
|
-
* fov — lerp when both keyframes carry non-null fov; else null
|
|
1925
|
-
* halfHeight — lerp when both keyframes carry non-null halfHeight; else null
|
|
1926
|
-
*
|
|
1927
|
-
* @example
|
|
1928
|
-
* const track = new CameraTrack()
|
|
1929
|
-
* track.add({ eye:[0,0,500] }) // center defaults to [0,0,0]
|
|
1930
|
-
* track.add({ eye:[300,-150,0], center:[0,0,0] })
|
|
1931
|
-
* track.add({ eMatrix: myEyeMatrix })
|
|
1932
|
-
* track.add({ vMatrix: myViewMatrix })
|
|
1933
|
-
* track.play({ loop: true })
|
|
1934
|
-
* // per frame:
|
|
1935
|
-
* track.tick()
|
|
1936
|
-
* const out = { eye:[0,0,0], center:[0,0,0], up:[0,1,0] }
|
|
1937
|
-
* track.eval(out)
|
|
1938
|
-
* cam.camera(out.eye[0],out.eye[1],out.eye[2],
|
|
1939
|
-
* out.center[0],out.center[1],out.center[2],
|
|
1940
|
-
* out.up[0],out.up[1],out.up[2])
|
|
2160
|
+
*
|
|
2161
|
+
* To capture a matrix-based pose, use PoseTrack.add({ mMatrix: eMatrix })
|
|
2162
|
+
* for full-fidelity including roll, or cam.capturePose() for lookat-style.
|
|
1941
2163
|
*/
|
|
1942
2164
|
class CameraTrack extends Track {
|
|
1943
2165
|
constructor() {
|
|
1944
2166
|
super();
|
|
1945
2167
|
/**
|
|
1946
|
-
* Eye
|
|
1947
|
-
* - 'hermite' — cubic Hermite; auto-CR tangents when none stored (default)
|
|
1948
|
-
* - 'linear' — lerp
|
|
1949
|
-
* - 'step' — snap to k0 eye
|
|
2168
|
+
* Eye-path interpolation mode.
|
|
1950
2169
|
* @type {'hermite'|'linear'|'step'}
|
|
1951
2170
|
*/
|
|
1952
2171
|
this.eyeInterp = 'hermite';
|
|
1953
2172
|
/**
|
|
1954
|
-
* Center
|
|
1955
|
-
* 'linear' suits fixed or predictably moving targets (default).
|
|
1956
|
-
* 'hermite' gives smoother paths when center is also flying freely.
|
|
1957
|
-
* - 'hermite' — cubic Hermite; auto-CR tangents when none stored
|
|
1958
|
-
* - 'linear' — lerp
|
|
1959
|
-
* - 'step' — snap to k0 center
|
|
2173
|
+
* Center-path interpolation mode.
|
|
1960
2174
|
* @type {'hermite'|'linear'|'step'}
|
|
1961
2175
|
*/
|
|
1962
2176
|
this.centerInterp = 'linear';
|
|
1963
|
-
// Scratch for toCamera() — avoids hot-path allocations
|
|
1964
|
-
this._eye = [0,0,0];
|
|
1965
|
-
this._center = [0,0,0];
|
|
1966
|
-
this._up = [0,1,0];
|
|
1967
2177
|
}
|
|
1968
2178
|
|
|
1969
2179
|
/**
|
|
1970
2180
|
* Append one or more camera keyframes. Adjacent duplicates are skipped by default.
|
|
1971
|
-
*
|
|
1972
2181
|
* @param {Object|Object[]} spec
|
|
1973
|
-
* { eye, center?, up? } or { vMatrix: mat4 } or { eMatrix: mat4 } or an array of either.
|
|
1974
2182
|
* @param {{ deduplicate?: boolean }} [opts]
|
|
1975
2183
|
*/
|
|
1976
2184
|
add(spec, opts) {
|
|
@@ -1988,7 +2196,7 @@ class CameraTrack extends Track {
|
|
|
1988
2196
|
}
|
|
1989
2197
|
|
|
1990
2198
|
/**
|
|
1991
|
-
* Replace (or append at end) the keyframe at index.
|
|
2199
|
+
* Replace (or append at end) the camera keyframe at index.
|
|
1992
2200
|
* @param {number} index
|
|
1993
2201
|
* @param {Object} spec
|
|
1994
2202
|
* @returns {boolean}
|
|
@@ -2040,7 +2248,7 @@ class CameraTrack extends Track {
|
|
|
2040
2248
|
const p3 = seg + 2 < n ? this.keyframes[seg + 2].eye : k1.eye;
|
|
2041
2249
|
const m0 = k0.eyeTanOut != null ? k0.eyeTanOut
|
|
2042
2250
|
: k0.eyeTanIn != null ? k0.eyeTanIn
|
|
2043
|
-
: _crTanOut(_m0, p0, k0.eye, k1.eye
|
|
2251
|
+
: _crTanOut(_m0, p0, k0.eye, k1.eye);
|
|
2044
2252
|
const m1 = k1.eyeTanIn != null ? k1.eyeTanIn
|
|
2045
2253
|
: k1.eyeTanOut != null ? k1.eyeTanOut
|
|
2046
2254
|
: _crTanIn(_m1, p0, k0.eye, k1.eye, p3);
|
|
@@ -2050,33 +2258,31 @@ class CameraTrack extends Track {
|
|
|
2050
2258
|
// center — Hermite, linear, or step (independent lookat target)
|
|
2051
2259
|
if (this.centerInterp === 'step') {
|
|
2052
2260
|
out.center[0]=k0.center[0]; out.center[1]=k0.center[1]; out.center[2]=k0.center[2];
|
|
2053
|
-
} else if (this.centerInterp === '
|
|
2054
|
-
lerpVec3(out.center, k0.center, k1.center, t);
|
|
2055
|
-
} else {
|
|
2261
|
+
} else if (this.centerInterp === 'hermite') {
|
|
2056
2262
|
const c0 = seg > 0 ? this.keyframes[seg - 1].center : k0.center;
|
|
2057
2263
|
const c3 = seg + 2 < n ? this.keyframes[seg + 2].center : k1.center;
|
|
2058
2264
|
const m0 = k0.centerTanOut != null ? k0.centerTanOut
|
|
2059
2265
|
: k0.centerTanIn != null ? k0.centerTanIn
|
|
2060
|
-
: _crTanOut(_m0, c0, k0.center, k1.center
|
|
2266
|
+
: _crTanOut(_m0, c0, k0.center, k1.center);
|
|
2061
2267
|
const m1 = k1.centerTanIn != null ? k1.centerTanIn
|
|
2062
2268
|
: k1.centerTanOut != null ? k1.centerTanOut
|
|
2063
2269
|
: _crTanIn(_m1, c0, k0.center, k1.center, c3);
|
|
2064
2270
|
hermiteVec3(out.center, k0.center, m0, k1.center, m1, t);
|
|
2271
|
+
} else {
|
|
2272
|
+
lerpVec3(out.center, k0.center, k1.center, t);
|
|
2065
2273
|
}
|
|
2066
2274
|
|
|
2067
|
-
// up — nlerp
|
|
2068
|
-
|
|
2069
|
-
const
|
|
2070
|
-
|
|
2071
|
-
const ul = Math.sqrt(ux*ux+uy*uy+uz*uz) || 1;
|
|
2072
|
-
out.up[0]=ux/ul; out.up[1]=uy/ul; out.up[2]=uz/ul;
|
|
2275
|
+
// up — nlerp on unit sphere
|
|
2276
|
+
lerpVec3(out.up, k0.up, k1.up, t);
|
|
2277
|
+
const ul=Math.sqrt(out.up[0]*out.up[0]+out.up[1]*out.up[1]+out.up[2]*out.up[2])||1;
|
|
2278
|
+
out.up[0]/=ul; out.up[1]/=ul; out.up[2]/=ul;
|
|
2073
2279
|
|
|
2074
|
-
// fov — lerp
|
|
2075
|
-
out.fov
|
|
2076
|
-
? k0.fov
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2280
|
+
// fov / halfHeight — lerp when both keyframes carry non-null values
|
|
2281
|
+
out.fov = (k0.fov != null && k1.fov != null)
|
|
2282
|
+
? k0.fov + t * (k1.fov - k0.fov) : (k0.fov ?? k1.fov ?? null);
|
|
2283
|
+
out.halfHeight = (k0.halfHeight != null && k1.halfHeight != null)
|
|
2284
|
+
? k0.halfHeight + t * (k1.halfHeight - k0.halfHeight)
|
|
2285
|
+
: (k0.halfHeight ?? k1.halfHeight ?? null);
|
|
2080
2286
|
|
|
2081
2287
|
return out;
|
|
2082
2288
|
}
|
|
@@ -2239,5 +2445,5 @@ function boxVisibility(planes, x0, y0, z0, x1, y1, z1) {
|
|
|
2239
2445
|
return allIn ? VISIBLE : SEMIVISIBLE;
|
|
2240
2446
|
}
|
|
2241
2447
|
|
|
2242
|
-
export { CameraTrack, EYE, INVISIBLE, MATRIX, MODEL, NDC, ORIGIN, PLANE_BOTTOM, PLANE_FAR, PLANE_LEFT, PLANE_NEAR, PLANE_RIGHT, PLANE_TOP, PoseTrack, SCREEN, SEMIVISIBLE, VISIBLE, WEBGL, WEBGPU, WORLD, _i, _j, _k,
|
|
2448
|
+
export { CameraTrack, EYE, INVISIBLE, MATRIX, MODEL, NDC, ORIGIN, PLANE_BOTTOM, PLANE_FAR, PLANE_LEFT, PLANE_NEAR, PLANE_RIGHT, PLANE_TOP, PoseTrack, SCREEN, SEMIVISIBLE, VISIBLE, WEBGL, WEBGPU, WORLD, _i, _j, _k, boxVisibility, distanceToPlane, frustumPlanes, hermiteVec3, i, j, k, lerpVec3, mapDirection, mapLocation, mat3Direction, mat3NormalFromMat4, mat4Bias, mat4Eye, mat4FromBasis, mat4FromScale, mat4FromTRS, mat4FromTranslation, mat4Frustum, mat4Invert, mat4Location, mat4MV, mat4Mul, mat4MulDir, mat4MulPoint, mat4Ortho, mat4PV, mat4Perspective, mat4Pick, mat4Reflect, mat4ToRotation, mat4ToScale, mat4ToTransform, mat4ToTranslation, mat4Transpose, mat4View, pixelRatio, pointVisibility, projBottom, projFar, projFov, projHfov, projIsOrtho, projLeft, projNear, projRight, projTop, qCopy, qDot, qFromAxisAngle, qFromLookDir, qFromMat4, qFromRotMat3x3, qMul, qNegate, qNlerp, qNormalize, qSet, qSlerp, qToMat4, quatToAxisAngle, sphereVisibility, transformToMat4 };
|
|
2243
2449
|
//# sourceMappingURL=index.js.map
|