reze-engine 0.27.3 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/engine.d.ts +2 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +19 -7
- package/dist/ik-solver.d.ts +1 -1
- package/dist/ik-solver.d.ts.map +1 -1
- package/dist/ik-solver.js +4 -29
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/math.d.ts +26 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +328 -0
- package/dist/shaders/passes/shadow.d.ts +1 -1
- package/dist/shaders/passes/shadow.d.ts.map +1 -1
- package/dist/shaders/passes/shadow.js +26 -3
- package/package.json +1 -1
- package/src/engine.ts +20 -7
- package/src/ik-solver.ts +5 -34
- package/src/index.ts +7 -2
- package/src/math.ts +345 -0
- package/src/shaders/passes/shadow.ts +26 -3
package/dist/math.js
CHANGED
|
@@ -90,6 +90,16 @@ export class Vec3 {
|
|
|
90
90
|
out.z = m[2] * nx + m[6] * ny + m[10] * nz;
|
|
91
91
|
return out;
|
|
92
92
|
}
|
|
93
|
+
// out = (x, y, -z): right-handed Y-up ↔ left-handed Y-up (involutive). Safe when out === v.
|
|
94
|
+
static mirrorZInto(v, out) {
|
|
95
|
+
out.x = v.x;
|
|
96
|
+
out.y = v.y;
|
|
97
|
+
out.z = -v.z;
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
static mirrorZ(v) {
|
|
101
|
+
return new Vec3(v.x, v.y, -v.z);
|
|
102
|
+
}
|
|
93
103
|
// In-place normalize returning length squared info via Vec3. Alias for normalize() but explicit.
|
|
94
104
|
normalizeInPlace() {
|
|
95
105
|
return this.normalize();
|
|
@@ -286,6 +296,318 @@ export class Quat {
|
|
|
286
296
|
const z = cy * cx * sz - sy * sx * cz;
|
|
287
297
|
return new Quat(x, y, z, w).normalize();
|
|
288
298
|
}
|
|
299
|
+
// 4D dot product. Negative means a and b are on opposite hemispheres (same rotation
|
|
300
|
+
// when |dot| ≈ 1 either way — quaternion double cover).
|
|
301
|
+
static dot(a, b) {
|
|
302
|
+
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
|
303
|
+
}
|
|
304
|
+
// Rotation angle between a and b in radians, insensitive to double cover.
|
|
305
|
+
static angleTo(a, b) {
|
|
306
|
+
const d = Math.abs(a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w);
|
|
307
|
+
return 2 * Math.acos(Math.min(1, d));
|
|
308
|
+
}
|
|
309
|
+
// out = conjugate of q (inverse for unit quaternions), without mutating q. Safe when out === q.
|
|
310
|
+
static conjugateInto(q, out) {
|
|
311
|
+
out.x = -q.x;
|
|
312
|
+
out.y = -q.y;
|
|
313
|
+
out.z = -q.z;
|
|
314
|
+
out.w = q.w;
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
// out = normalized lerp from a to b, taking the shorter path (negates b when dot < 0).
|
|
318
|
+
// Cheaper than slerp; non-constant angular velocity. Safe when out === a or out === b.
|
|
319
|
+
static nlerpInto(a, b, t, out) {
|
|
320
|
+
const d = a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
|
321
|
+
const s = d < 0 ? -1 : 1;
|
|
322
|
+
const x = a.x + (b.x * s - a.x) * t;
|
|
323
|
+
const y = a.y + (b.y * s - a.y) * t;
|
|
324
|
+
const z = a.z + (b.z * s - a.z) * t;
|
|
325
|
+
const w = a.w + (b.w * s - a.w) * t;
|
|
326
|
+
const invLen = 1 / Math.hypot(x, y, z, w);
|
|
327
|
+
out.x = x * invLen;
|
|
328
|
+
out.y = y * invLen;
|
|
329
|
+
out.z = z * invLen;
|
|
330
|
+
out.w = w * invLen;
|
|
331
|
+
return out;
|
|
332
|
+
}
|
|
333
|
+
static nlerp(a, b, t) {
|
|
334
|
+
return Quat.nlerpInto(a, b, t, new Quat(0, 0, 0, 1));
|
|
335
|
+
}
|
|
336
|
+
// out = v rotated by unit quaternion q (no matrix, no allocation). Safe when out === v.
|
|
337
|
+
static rotateVecInto(q, v, out) {
|
|
338
|
+
// v' = v + 2*qw*(qv × v) + 2*(qv × (qv × v))
|
|
339
|
+
const qx = q.x, qy = q.y, qz = q.z, qw = q.w;
|
|
340
|
+
const vx = v.x, vy = v.y, vz = v.z;
|
|
341
|
+
// t = 2 * (qv × v)
|
|
342
|
+
const tx = 2 * (qy * vz - qz * vy);
|
|
343
|
+
const ty = 2 * (qz * vx - qx * vz);
|
|
344
|
+
const tz = 2 * (qx * vy - qy * vx);
|
|
345
|
+
out.x = vx + qw * tx + qy * tz - qz * ty;
|
|
346
|
+
out.y = vy + qw * ty + qz * tx - qx * tz;
|
|
347
|
+
out.z = vz + qw * tz + qx * ty - qy * tx;
|
|
348
|
+
return out;
|
|
349
|
+
}
|
|
350
|
+
static rotateVec(q, v) {
|
|
351
|
+
return Quat.rotateVecInto(q, v, new Vec3(0, 0, 0));
|
|
352
|
+
}
|
|
353
|
+
// out = v rotated by the inverse (conjugate) of unit quaternion q. Safe when out === v.
|
|
354
|
+
static rotateVecInvInto(q, v, out) {
|
|
355
|
+
const qx = -q.x, qy = -q.y, qz = -q.z, qw = q.w;
|
|
356
|
+
const vx = v.x, vy = v.y, vz = v.z;
|
|
357
|
+
const tx = 2 * (qy * vz - qz * vy);
|
|
358
|
+
const ty = 2 * (qz * vx - qx * vz);
|
|
359
|
+
const tz = 2 * (qx * vy - qy * vx);
|
|
360
|
+
out.x = vx + qw * tx + qy * tz - qz * ty;
|
|
361
|
+
out.y = vy + qw * ty + qz * tx - qx * tz;
|
|
362
|
+
out.z = vz + qw * tz + qx * ty - qy * tx;
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
static rotateVecInv(q, v) {
|
|
366
|
+
return Quat.rotateVecInvInto(q, v, new Vec3(0, 0, 0));
|
|
367
|
+
}
|
|
368
|
+
// out = shortest-arc rotation taking unit vector `from` to unit vector `to`.
|
|
369
|
+
// Matches Babylon's FromUnitVectorsToRef exactly, including the near-antiparallel
|
|
370
|
+
// branch (w = 1 + dot < 0.001 → 180° about a perpendicular picked the same way).
|
|
371
|
+
static fromUnitVectorsInto(from, to, out) {
|
|
372
|
+
const r = from.x * to.x + from.y * to.y + from.z * to.z + 1;
|
|
373
|
+
if (r < 0.001) {
|
|
374
|
+
if (Math.abs(from.x) > Math.abs(from.z)) {
|
|
375
|
+
out.setXYZW(-from.y, from.x, 0, 0);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
out.setXYZW(0, -from.z, from.y, 0);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
// q = (from × to, 1 + from·to)
|
|
383
|
+
out.setXYZW(from.y * to.z - from.z * to.y, from.z * to.x - from.x * to.z, from.x * to.y - from.y * to.x, r);
|
|
384
|
+
}
|
|
385
|
+
const invLen = 1 / Math.sqrt(out.x * out.x + out.y * out.y + out.z * out.z + out.w * out.w);
|
|
386
|
+
out.setXYZW(out.x * invLen, out.y * invLen, out.z * invLen, out.w * invLen);
|
|
387
|
+
return out;
|
|
388
|
+
}
|
|
389
|
+
static fromUnitVectors(from, to) {
|
|
390
|
+
return Quat.fromUnitVectorsInto(from, to, new Quat(0, 0, 0, 1));
|
|
391
|
+
}
|
|
392
|
+
// out = rotation taking the standard basis onto the orthonormal axes x, y, z
|
|
393
|
+
// (the columns of the column-major rotation matrix): rotateVec(out, (1,0,0)) = x, etc.
|
|
394
|
+
static fromBasisInto(x, y, z, out) {
|
|
395
|
+
// Shepperd's method on the 3x3 with columns x, y, z.
|
|
396
|
+
const m00 = x.x, m01 = x.y, m02 = x.z;
|
|
397
|
+
const m10 = y.x, m11 = y.y, m12 = y.z;
|
|
398
|
+
const m20 = z.x, m21 = z.y, m22 = z.z;
|
|
399
|
+
const trace = m00 + m11 + m22;
|
|
400
|
+
if (trace > 0) {
|
|
401
|
+
const s = 0.5 / Math.sqrt(trace + 1);
|
|
402
|
+
out.setXYZW((m12 - m21) * s, (m20 - m02) * s, (m01 - m10) * s, 0.25 / s);
|
|
403
|
+
}
|
|
404
|
+
else if (m00 > m11 && m00 > m22) {
|
|
405
|
+
const s = 2 * Math.sqrt(1 + m00 - m11 - m22);
|
|
406
|
+
out.setXYZW(0.25 * s, (m10 + m01) / s, (m20 + m02) / s, (m12 - m21) / s);
|
|
407
|
+
}
|
|
408
|
+
else if (m11 > m22) {
|
|
409
|
+
const s = 2 * Math.sqrt(1 + m11 - m00 - m22);
|
|
410
|
+
out.setXYZW((m10 + m01) / s, 0.25 * s, (m21 + m12) / s, (m20 - m02) / s);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
const s = 2 * Math.sqrt(1 + m22 - m00 - m11);
|
|
414
|
+
out.setXYZW((m20 + m02) / s, (m21 + m12) / s, 0.25 * s, (m01 - m10) / s);
|
|
415
|
+
}
|
|
416
|
+
return out;
|
|
417
|
+
}
|
|
418
|
+
static fromBasis(x, y, z) {
|
|
419
|
+
return Quat.fromBasisInto(x, y, z, new Quat(0, 0, 0, 1));
|
|
420
|
+
}
|
|
421
|
+
// out = twist component of q around unit axis `a`, so that q = swing · twist
|
|
422
|
+
// (swing = Quat.multiply(q, conjugate(twist))). Singular when q is ~180° about an
|
|
423
|
+
// axis perpendicular to `a` — returns identity there.
|
|
424
|
+
static twistAroundAxisInto(q, a, out) {
|
|
425
|
+
const d = q.x * a.x + q.y * a.y + q.z * a.z;
|
|
426
|
+
const px = a.x * d;
|
|
427
|
+
const py = a.y * d;
|
|
428
|
+
const pz = a.z * d;
|
|
429
|
+
const len = Math.sqrt(px * px + py * py + pz * pz + q.w * q.w);
|
|
430
|
+
if (len < 1e-8) {
|
|
431
|
+
out.setIdentity();
|
|
432
|
+
return out;
|
|
433
|
+
}
|
|
434
|
+
out.setXYZW(px / len, py / len, pz / len, q.w / len);
|
|
435
|
+
return out;
|
|
436
|
+
}
|
|
437
|
+
static twistAroundAxis(q, a) {
|
|
438
|
+
return Quat.twistAroundAxisInto(q, a, new Quat(0, 0, 0, 1));
|
|
439
|
+
}
|
|
440
|
+
// out = orientation looking along `forward` (mapped to local +Z, the engine's LH forward)
|
|
441
|
+
// with local +Y toward `up`. Falls back to a reference up when forward ∥ up.
|
|
442
|
+
static lookRotationInto(forward, up, out) {
|
|
443
|
+
let zx = forward.x, zy = forward.y, zz = forward.z;
|
|
444
|
+
const zl = Math.sqrt(zx * zx + zy * zy + zz * zz);
|
|
445
|
+
if (zl === 0)
|
|
446
|
+
return out.setIdentity();
|
|
447
|
+
const zi = 1 / zl;
|
|
448
|
+
zx *= zi;
|
|
449
|
+
zy *= zi;
|
|
450
|
+
zz *= zi;
|
|
451
|
+
// x = up × z
|
|
452
|
+
let xx = up.y * zz - up.z * zy;
|
|
453
|
+
let xy = up.z * zx - up.x * zz;
|
|
454
|
+
let xz = up.x * zy - up.y * zx;
|
|
455
|
+
let xl = Math.sqrt(xx * xx + xy * xy + xz * xz);
|
|
456
|
+
if (xl < 1e-8) {
|
|
457
|
+
// forward ∥ up: substitute a reference up not parallel to forward
|
|
458
|
+
const uy = Math.abs(zy) < 0.99 ? 1 : 0;
|
|
459
|
+
const ux = 1 - uy;
|
|
460
|
+
xx = uy * zz;
|
|
461
|
+
xy = -ux * zz;
|
|
462
|
+
xz = ux * zy - uy * zx;
|
|
463
|
+
xl = Math.sqrt(xx * xx + xy * xy + xz * xz);
|
|
464
|
+
}
|
|
465
|
+
const xi = 1 / xl;
|
|
466
|
+
xx *= xi;
|
|
467
|
+
xy *= xi;
|
|
468
|
+
xz *= xi;
|
|
469
|
+
// y = z × x (unit: z ⊥ x)
|
|
470
|
+
const yx = zy * xz - zz * xy;
|
|
471
|
+
const yy = zz * xx - zx * xz;
|
|
472
|
+
const yz = zx * xy - zy * xx;
|
|
473
|
+
_lookX.setXYZ(xx, xy, xz);
|
|
474
|
+
_lookY.setXYZ(yx, yy, yz);
|
|
475
|
+
_lookZ.setXYZ(zx, zy, zz);
|
|
476
|
+
return Quat.fromBasisInto(_lookX, _lookY, _lookZ, out);
|
|
477
|
+
}
|
|
478
|
+
static lookRotation(forward, up) {
|
|
479
|
+
return Quat.lookRotationInto(forward, up, new Quat(0, 0, 0, 1));
|
|
480
|
+
}
|
|
481
|
+
// out = q with the Z axis mirrored: right-handed Y-up ↔ left-handed Y-up
|
|
482
|
+
// (conjugation by the Z-mirror; involutive). Pairs with Vec3.mirrorZInto. Safe when out === q.
|
|
483
|
+
static mirrorZInto(q, out) {
|
|
484
|
+
out.x = -q.x;
|
|
485
|
+
out.y = -q.y;
|
|
486
|
+
out.z = q.z;
|
|
487
|
+
out.w = q.w;
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
static mirrorZ(q) {
|
|
491
|
+
return new Quat(-q.x, -q.y, q.z, q.w);
|
|
492
|
+
}
|
|
493
|
+
// out = quaternion from euler angles (radians) applied in the given intrinsic order.
|
|
494
|
+
// fromEulerOrderInto(x, y, z, "YXZ", out) matches Quat.fromEuler (the MMD/PMX convention).
|
|
495
|
+
static fromEulerOrderInto(x, y, z, order, out) {
|
|
496
|
+
out.setIdentity();
|
|
497
|
+
for (let i = 0; i < 3; i++) {
|
|
498
|
+
const axis = order.charCodeAt(i) - 88; // "X" → 0, "Y" → 1, "Z" → 2
|
|
499
|
+
const angle = axis === 0 ? x : axis === 1 ? y : z;
|
|
500
|
+
const half = angle * 0.5;
|
|
501
|
+
const s = Math.sin(half);
|
|
502
|
+
const c = Math.cos(half);
|
|
503
|
+
const ax = out.x, ay = out.y, az = out.z, aw = out.w;
|
|
504
|
+
if (axis === 0) {
|
|
505
|
+
out.x = aw * s + ax * c;
|
|
506
|
+
out.y = ay * c + az * s;
|
|
507
|
+
out.z = az * c - ay * s;
|
|
508
|
+
out.w = aw * c - ax * s;
|
|
509
|
+
}
|
|
510
|
+
else if (axis === 1) {
|
|
511
|
+
out.x = ax * c - az * s;
|
|
512
|
+
out.y = aw * s + ay * c;
|
|
513
|
+
out.z = az * c + ax * s;
|
|
514
|
+
out.w = aw * c - ay * s;
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
out.x = ax * c + ay * s;
|
|
518
|
+
out.y = ay * c - ax * s;
|
|
519
|
+
out.z = aw * s + az * c;
|
|
520
|
+
out.w = aw * c - az * s;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return out;
|
|
524
|
+
}
|
|
525
|
+
static fromEulerOrder(x, y, z, order) {
|
|
526
|
+
return Quat.fromEulerOrderInto(x, y, z, order, new Quat(0, 0, 0, 1));
|
|
527
|
+
}
|
|
528
|
+
// Extract euler angles (radians) in the given intrinsic order into out (out.x/y/z = rotation
|
|
529
|
+
// about X/Y/Z). At gimbal lock (middle angle ±90°) the split between first and third angle is
|
|
530
|
+
// ambiguous; the third is set to 0.
|
|
531
|
+
static toEulerOrderInto(q, order, out) {
|
|
532
|
+
Mat4.fromQuatInto(q.x, q.y, q.z, q.w, _eulerMat, 0);
|
|
533
|
+
const m = _eulerMat;
|
|
534
|
+
const m11 = m[0], m12 = m[4], m13 = m[8];
|
|
535
|
+
const m21 = m[1], m22 = m[5], m23 = m[9];
|
|
536
|
+
const m31 = m[2], m32 = m[6], m33 = m[10];
|
|
537
|
+
const clamp = (v) => (v < -1 ? -1 : v > 1 ? 1 : v);
|
|
538
|
+
switch (order) {
|
|
539
|
+
case "XYZ":
|
|
540
|
+
out.y = Math.asin(clamp(m13));
|
|
541
|
+
if (Math.abs(m13) < 0.9999999) {
|
|
542
|
+
out.x = Math.atan2(-m23, m33);
|
|
543
|
+
out.z = Math.atan2(-m12, m11);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
out.x = Math.atan2(m32, m22);
|
|
547
|
+
out.z = 0;
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
550
|
+
case "YXZ":
|
|
551
|
+
out.x = Math.asin(-clamp(m23));
|
|
552
|
+
if (Math.abs(m23) < 0.9999999) {
|
|
553
|
+
out.y = Math.atan2(m13, m33);
|
|
554
|
+
out.z = Math.atan2(m21, m22);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
out.y = Math.atan2(-m31, m11);
|
|
558
|
+
out.z = 0;
|
|
559
|
+
}
|
|
560
|
+
break;
|
|
561
|
+
case "ZXY":
|
|
562
|
+
out.x = Math.asin(clamp(m32));
|
|
563
|
+
if (Math.abs(m32) < 0.9999999) {
|
|
564
|
+
out.y = Math.atan2(-m31, m33);
|
|
565
|
+
out.z = Math.atan2(-m12, m22);
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
out.y = 0;
|
|
569
|
+
out.z = Math.atan2(m21, m11);
|
|
570
|
+
}
|
|
571
|
+
break;
|
|
572
|
+
case "ZYX":
|
|
573
|
+
out.y = Math.asin(-clamp(m31));
|
|
574
|
+
if (Math.abs(m31) < 0.9999999) {
|
|
575
|
+
out.x = Math.atan2(m32, m33);
|
|
576
|
+
out.z = Math.atan2(m21, m11);
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
out.x = 0;
|
|
580
|
+
out.z = Math.atan2(-m12, m22);
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
case "YZX":
|
|
584
|
+
out.z = Math.asin(clamp(m21));
|
|
585
|
+
if (Math.abs(m21) < 0.9999999) {
|
|
586
|
+
out.x = Math.atan2(-m23, m22);
|
|
587
|
+
out.y = Math.atan2(-m31, m11);
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
out.x = 0;
|
|
591
|
+
out.y = Math.atan2(m13, m33);
|
|
592
|
+
}
|
|
593
|
+
break;
|
|
594
|
+
case "XZY":
|
|
595
|
+
out.z = Math.asin(-clamp(m12));
|
|
596
|
+
if (Math.abs(m12) < 0.9999999) {
|
|
597
|
+
out.x = Math.atan2(m32, m22);
|
|
598
|
+
out.y = Math.atan2(m13, m11);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
out.x = Math.atan2(-m23, m33);
|
|
602
|
+
out.y = 0;
|
|
603
|
+
}
|
|
604
|
+
break;
|
|
605
|
+
}
|
|
606
|
+
return out;
|
|
607
|
+
}
|
|
608
|
+
static toEulerOrder(q, order) {
|
|
609
|
+
return Quat.toEulerOrderInto(q, order, new Vec3(0, 0, 0));
|
|
610
|
+
}
|
|
289
611
|
}
|
|
290
612
|
export class Mat4 {
|
|
291
613
|
constructor(values) {
|
|
@@ -762,6 +1084,12 @@ export class Mat4 {
|
|
|
762
1084
|
return new Mat4(out);
|
|
763
1085
|
}
|
|
764
1086
|
}
|
|
1087
|
+
// Module-private scratch for Quat.toEulerOrderInto / lookRotationInto (never handed out,
|
|
1088
|
+
// so no cross-call stomping with the public pools below).
|
|
1089
|
+
const _eulerMat = new Float32Array(16);
|
|
1090
|
+
const _lookX = new Vec3(0, 0, 0);
|
|
1091
|
+
const _lookY = new Vec3(0, 0, 0);
|
|
1092
|
+
const _lookZ = new Vec3(0, 0, 0);
|
|
765
1093
|
// Preallocated scratch instances for hot paths. Each subsystem should use its own
|
|
766
1094
|
// slot to avoid cross-call stomping. Bump the count if more call sites need scratch.
|
|
767
1095
|
export const scratchMat4Values = [
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const SHADOW_DEPTH_SHADER_WGSL = "\nstruct LightVP { viewProj: mat4x4f, };\n@group(0) @binding(0) var<uniform> lp: LightVP;\n@group(0) @binding(1) var<storage, read> skinMats: array<mat4x4f>;\n@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f,\n @location(3) joints0: vec4<u32>, @location(4) weights0: vec4<f32>) ->
|
|
1
|
+
export declare const SHADOW_DEPTH_SHADER_WGSL = "\nstruct LightVP { viewProj: mat4x4f, };\n@group(0) @binding(0) var<uniform> lp: LightVP;\n@group(0) @binding(1) var<storage, read> skinMats: array<mat4x4f>;\n@group(0) @binding(2) var texSampler: sampler;\n@group(1) @binding(0) var diffuseTexture: texture_2d<f32>;\nstruct MaterialDiffuse { diffuse: vec4f, };\n@group(1) @binding(1) var<uniform> material: MaterialDiffuse;\n\nstruct VSOut {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n};\n\n@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f,\n @location(3) joints0: vec4<u32>, @location(4) weights0: vec4<f32>) -> VSOut {\n let pos4 = vec4f(position, 1.0);\n let ws = weights0.x + weights0.y + weights0.z + weights0.w;\n let inv = select(1.0, 1.0 / ws, ws > 0.0001);\n let nw = select(vec4f(1.0,0.0,0.0,0.0), weights0 * inv, ws > 0.0001);\n var sp = vec4f(0.0);\n for (var i = 0u; i < 4u; i++) { sp += (skinMats[joints0[i]] * pos4) * nw[i]; }\n var out: VSOut;\n out.position = lp.viewProj * vec4f(sp.xyz, 1.0);\n out.uv = uv;\n return out;\n}\n\n@fragment fn fs(in: VSOut) {\n let alpha = textureSample(diffuseTexture, texSampler, in.uv).a * material.diffuse.a;\n if (alpha < 0.5) { discard; }\n}\n";
|
|
2
2
|
//# sourceMappingURL=shadow.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shadow.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/shadow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shadow.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/shadow.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,wBAAwB,otCAgCpC,CAAA"}
|
|
@@ -1,16 +1,39 @@
|
|
|
1
|
-
// Shadow map depth
|
|
1
|
+
// Shadow map depth pass. Skinned VS + alpha-test FS (depth-only attachment, no
|
|
2
|
+
// color targets): texels where diffuse-texture alpha × material alpha fall below
|
|
3
|
+
// the cutoff are discarded, so lace casts lace-shaped shadows and a true veil
|
|
4
|
+
// casts nothing — per texel, with no per-material sheerness classification.
|
|
5
|
+
// Group 1 is the main pass's per-material bind group reused as-is; only
|
|
6
|
+
// bindings 0/1 are declared here (a layout may carry bindings a shader ignores).
|
|
2
7
|
export const SHADOW_DEPTH_SHADER_WGSL = /* wgsl */ `
|
|
3
8
|
struct LightVP { viewProj: mat4x4f, };
|
|
4
9
|
@group(0) @binding(0) var<uniform> lp: LightVP;
|
|
5
10
|
@group(0) @binding(1) var<storage, read> skinMats: array<mat4x4f>;
|
|
11
|
+
@group(0) @binding(2) var texSampler: sampler;
|
|
12
|
+
@group(1) @binding(0) var diffuseTexture: texture_2d<f32>;
|
|
13
|
+
struct MaterialDiffuse { diffuse: vec4f, };
|
|
14
|
+
@group(1) @binding(1) var<uniform> material: MaterialDiffuse;
|
|
15
|
+
|
|
16
|
+
struct VSOut {
|
|
17
|
+
@builtin(position) position: vec4f,
|
|
18
|
+
@location(0) uv: vec2f,
|
|
19
|
+
};
|
|
20
|
+
|
|
6
21
|
@vertex fn vs(@location(0) position: vec3f, @location(1) normal: vec3f, @location(2) uv: vec2f,
|
|
7
|
-
@location(3) joints0: vec4<u32>, @location(4) weights0: vec4<f32>) ->
|
|
22
|
+
@location(3) joints0: vec4<u32>, @location(4) weights0: vec4<f32>) -> VSOut {
|
|
8
23
|
let pos4 = vec4f(position, 1.0);
|
|
9
24
|
let ws = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
10
25
|
let inv = select(1.0, 1.0 / ws, ws > 0.0001);
|
|
11
26
|
let nw = select(vec4f(1.0,0.0,0.0,0.0), weights0 * inv, ws > 0.0001);
|
|
12
27
|
var sp = vec4f(0.0);
|
|
13
28
|
for (var i = 0u; i < 4u; i++) { sp += (skinMats[joints0[i]] * pos4) * nw[i]; }
|
|
14
|
-
|
|
29
|
+
var out: VSOut;
|
|
30
|
+
out.position = lp.viewProj * vec4f(sp.xyz, 1.0);
|
|
31
|
+
out.uv = uv;
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@fragment fn fs(in: VSOut) {
|
|
36
|
+
let alpha = textureSample(diffuseTexture, texSampler, in.uv).a * material.diffuse.a;
|
|
37
|
+
if (alpha < 0.5) { discard; }
|
|
15
38
|
}
|
|
16
39
|
`;
|
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -385,7 +385,8 @@ interface DrawCall {
|
|
|
385
385
|
// Present only for material draw calls (opaque/transparent) — the grouping walk skips
|
|
386
386
|
// any draw call without it.
|
|
387
387
|
baseBindGroupEntries?: GPUBindGroupEntry[]
|
|
388
|
-
/** Material draws only: false = excluded from the shadow map (
|
|
388
|
+
/** Material draws only: false = excluded from the shadow map (PMX cast-shadow
|
|
389
|
+
* flag off). Sheer texels are cut per fragment by the shadow pass's alpha test. */
|
|
389
390
|
castsShadow?: boolean
|
|
390
391
|
/** Edge-flagged materials: interleaved inverted-hull outline drawn right after
|
|
391
392
|
* this material with the outline pipeline. Shares this call's index range;
|
|
@@ -490,7 +491,7 @@ function buildAlphaSampler(
|
|
|
490
491
|
* neither fully opaque nor fully cut out (alpha in ~0.03..0.97). Together
|
|
491
492
|
* Bucketing itself is binary (babylon-mmd parity): ANY translucent coverage
|
|
492
493
|
* routes to the alpha-blend bucket. `avg` below this threshold additionally
|
|
493
|
-
* marks a material as fully sheer (a veil)
|
|
494
|
+
* marks a material as fully sheer (a veil). */
|
|
494
495
|
const SHEER_ALPHA_THRESHOLD = 0.7
|
|
495
496
|
function materialAlphaStats(
|
|
496
497
|
verts: Float32Array,
|
|
@@ -1770,6 +1771,7 @@ export class Engine {
|
|
|
1770
1771
|
entries: [
|
|
1771
1772
|
{ binding: 0, visibility: GPUShaderStage.VERTEX, buffer: { type: "uniform" } },
|
|
1772
1773
|
{ binding: 1, visibility: GPUShaderStage.VERTEX, buffer: { type: "read-only-storage" } },
|
|
1774
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: {} },
|
|
1773
1775
|
],
|
|
1774
1776
|
})
|
|
1775
1777
|
const shadowShader = this.device.createShaderModule({
|
|
@@ -1778,8 +1780,13 @@ export class Engine {
|
|
|
1778
1780
|
})
|
|
1779
1781
|
this.shadowDepthPipeline = this.device.createRenderPipeline({
|
|
1780
1782
|
label: "shadow depth pipeline",
|
|
1781
|
-
layout
|
|
1783
|
+
// Group 1 is the main pass's per-material layout so each shadow draw can
|
|
1784
|
+
// rebind the draw call's existing material bind group for the alpha test.
|
|
1785
|
+
layout: this.device.createPipelineLayout({
|
|
1786
|
+
bindGroupLayouts: [shadowBindGroupLayout, this.mainPerMaterialBindGroupLayout],
|
|
1787
|
+
}),
|
|
1782
1788
|
vertex: { module: shadowShader, entryPoint: "vs", buffers: fullVertexBuffers as GPUVertexBufferLayout[] },
|
|
1789
|
+
fragment: { module: shadowShader, entryPoint: "fs", targets: [] },
|
|
1783
1790
|
primitive: { cullMode: "none" },
|
|
1784
1791
|
depthStencil: {
|
|
1785
1792
|
format: "depth32float",
|
|
@@ -3357,6 +3364,7 @@ export class Engine {
|
|
|
3357
3364
|
entries: [
|
|
3358
3365
|
{ binding: 0, resource: { buffer: this.shadowLightVPBuffer } },
|
|
3359
3366
|
{ binding: 1, resource: { buffer: skinMatrixBuffer } },
|
|
3367
|
+
{ binding: 2, resource: this.materialSampler },
|
|
3360
3368
|
],
|
|
3361
3369
|
})
|
|
3362
3370
|
|
|
@@ -3687,9 +3695,12 @@ export class Engine {
|
|
|
3687
3695
|
// only guards against centroid-sampling noise on genuinely solid cloth.
|
|
3688
3696
|
const sheer = stats.avg < SHEER_ALPHA_THRESHOLD
|
|
3689
3697
|
const isTransparent = materialAlpha < 1.0 - 0.001 || sheer || stats.translucentFrac > 0.02
|
|
3690
|
-
// Shadow casting: the PMX author's own flag (bit 0x04, cast self-shadow)
|
|
3691
|
-
//
|
|
3692
|
-
|
|
3698
|
+
// Shadow casting: the PMX author's own flag (bit 0x04, cast self-shadow) —
|
|
3699
|
+
// exactly what MMD honors. Sheerness is handled per texel by the shadow
|
|
3700
|
+
// pass's alpha test, not by a per-material veto: a threshold on avg alpha
|
|
3701
|
+
// misclassified fully-worn opaque dresses (avg 0.69) as veils and stripped
|
|
3702
|
+
// their shadows, while any lower cliff would strand the next model.
|
|
3703
|
+
const castsShadow = (mat.edgeFlag & 0x04) !== 0
|
|
3693
3704
|
// Load-time classification log — one line per material, cheap and
|
|
3694
3705
|
// invaluable when a model renders wrong (bucket/outline/shadow disputes).
|
|
3695
3706
|
console.info(
|
|
@@ -4726,7 +4737,9 @@ export class Engine {
|
|
|
4726
4737
|
sp.setVertexBuffer(2, inst.weightsBuffer)
|
|
4727
4738
|
sp.setIndexBuffer(inst.indexBuffer, "uint32")
|
|
4728
4739
|
for (const draw of inst.shadowDrawCalls) {
|
|
4729
|
-
if (this.shouldRenderDrawCall(inst, draw))
|
|
4740
|
+
if (!this.shouldRenderDrawCall(inst, draw)) continue
|
|
4741
|
+
sp.setBindGroup(1, draw.bindGroup)
|
|
4742
|
+
sp.drawIndexed(draw.count, 1, draw.firstIndex, 0, 0)
|
|
4730
4743
|
}
|
|
4731
4744
|
}
|
|
4732
4745
|
|
package/src/ik-solver.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// IK solver (MMD-style; see Saba MMDIkSolver.cpp)
|
|
2
2
|
|
|
3
|
-
import { Mat4, Quat, Vec3 } from "./math"
|
|
3
|
+
import { Mat4, Quat, Vec3, type EulerOrder } from "./math"
|
|
4
4
|
import { Bone, IKLink, IKSolver, IKChainInfo } from "./model"
|
|
5
5
|
|
|
6
6
|
// Callback type for updating world matrix (provided by model to handle append transformations)
|
|
@@ -80,7 +80,6 @@ const _ikVec: Vec3[] = [
|
|
|
80
80
|
]
|
|
81
81
|
const _ikQuat: Quat[] = [
|
|
82
82
|
new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1),
|
|
83
|
-
new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1), new Quat(0, 0, 0, 1),
|
|
84
83
|
]
|
|
85
84
|
const _ikMat: Float32Array[] = [
|
|
86
85
|
new Float32Array(16), new Float32Array(16), new Float32Array(16), new Float32Array(16),
|
|
@@ -215,7 +214,7 @@ export class IKSolverSystem {
|
|
|
215
214
|
// _ikVec[0]=chainPos, [1]=ikPos, [2]=targetPos, [3]=chainTargetVec, [4]=chainIkVec,
|
|
216
215
|
// [5]=rotAxis, [6]=finalAxis, [7]=eulerTmp, [8]=limitedEuler
|
|
217
216
|
// _ikMat[0]=parentRot, [1]=invParentRot, [2]=quatToMatTmp
|
|
218
|
-
// _ikQuat[0]=ikRotation, [1]=combinedRot, [2]=localRotConj
|
|
217
|
+
// _ikQuat[0]=ikRotation, [1]=combinedRot, [2]=localRotConj
|
|
219
218
|
|
|
220
219
|
const chainPos = Vec3.setFromMat4Translation(worldMatrices[chainBoneIndex].values, _ikVec[0])
|
|
221
220
|
const ikPos = Vec3.setFromMat4Translation(worldMatrices[ikBoneIndex].values, _ikVec[1])
|
|
@@ -340,16 +339,8 @@ export class IKSolverSystem {
|
|
|
340
339
|
return Math.sqrt(dx * dx + dy * dy + dz * dz)
|
|
341
340
|
}
|
|
342
341
|
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
private static readonly EULER_AXES: readonly [number, number, number][][] = [
|
|
346
|
-
// YXZ: Y, X, Z
|
|
347
|
-
[[0, 1, 0], [1, 0, 0], [0, 0, 1]],
|
|
348
|
-
// ZYX: Z, Y, X
|
|
349
|
-
[[0, 0, 1], [0, 1, 0], [1, 0, 0]],
|
|
350
|
-
// XZY: X, Z, Y
|
|
351
|
-
[[1, 0, 0], [0, 0, 1], [0, 1, 0]],
|
|
352
|
-
]
|
|
342
|
+
// Maps InternalEulerRotationOrder to the shared math-layer order names.
|
|
343
|
+
private static readonly EULER_ORDER_NAMES: readonly EulerOrder[] = ["YXZ", "ZYX", "XZY"]
|
|
353
344
|
|
|
354
345
|
private static extractEulerAnglesInto(quat: Quat, order: InternalEulerRotationOrder, out: Vec3): void {
|
|
355
346
|
Mat4.fromQuatInto(quat.x, quat.y, quat.z, quat.w, _ikMat[2], 0)
|
|
@@ -396,27 +387,7 @@ export class IKSolverSystem {
|
|
|
396
387
|
}
|
|
397
388
|
|
|
398
389
|
private static reconstructQuatFromEulerInto(euler: Vec3, order: InternalEulerRotationOrder, out: Quat): void {
|
|
399
|
-
|
|
400
|
-
const a1 = axes[0], a2 = axes[1], a3 = axes[2]
|
|
401
|
-
const ang1 =
|
|
402
|
-
order === InternalEulerRotationOrder.YXZ ? euler.y
|
|
403
|
-
: order === InternalEulerRotationOrder.ZYX ? euler.z
|
|
404
|
-
: euler.x
|
|
405
|
-
const ang2 =
|
|
406
|
-
order === InternalEulerRotationOrder.YXZ ? euler.x
|
|
407
|
-
: order === InternalEulerRotationOrder.ZYX ? euler.y
|
|
408
|
-
: euler.z
|
|
409
|
-
const ang3 =
|
|
410
|
-
order === InternalEulerRotationOrder.YXZ ? euler.z
|
|
411
|
-
: order === InternalEulerRotationOrder.ZYX ? euler.x
|
|
412
|
-
: euler.y
|
|
413
|
-
|
|
414
|
-
// result = axisAngle(a1, ang1); then *= axisAngle(a2, ang2); then *= axisAngle(a3, ang3)
|
|
415
|
-
Quat.fromAxisAngleInto(a1[0], a1[1], a1[2], ang1, out)
|
|
416
|
-
Quat.fromAxisAngleInto(a2[0], a2[1], a2[2], ang2, _ikQuat[3])
|
|
417
|
-
Quat.multiplyInto(out, _ikQuat[3], out)
|
|
418
|
-
Quat.fromAxisAngleInto(a3[0], a3[1], a3[2], ang3, _ikQuat[3])
|
|
419
|
-
Quat.multiplyInto(out, _ikQuat[3], out)
|
|
390
|
+
Quat.fromEulerOrderInto(euler.x, euler.y, euler.z, this.EULER_ORDER_NAMES[order], out)
|
|
420
391
|
}
|
|
421
392
|
|
|
422
393
|
// Write parent's world rotation (translation stripped) into out Float32Array.
|
package/src/index.ts
CHANGED
|
@@ -53,7 +53,7 @@ export { STOCKINGS_GRAPH } from "./graph/presets/stockings"
|
|
|
53
53
|
export { EYE_GRAPH } from "./graph/presets/eye"
|
|
54
54
|
export { FACE_GRAPH } from "./graph/presets/face"
|
|
55
55
|
export { Model } from "./model"
|
|
56
|
-
export { Vec3, Quat, Mat4 } from "./math"
|
|
56
|
+
export { Vec3, Quat, Mat4, easeInOut, type EulerOrder } from "./math"
|
|
57
57
|
export type {
|
|
58
58
|
AnimationClip,
|
|
59
59
|
AnimationPlayOptions,
|
|
@@ -64,7 +64,12 @@ export type {
|
|
|
64
64
|
BoneInterpolation,
|
|
65
65
|
ControlPoint,
|
|
66
66
|
} from "./animation"
|
|
67
|
-
export {
|
|
67
|
+
export {
|
|
68
|
+
FPS,
|
|
69
|
+
bezierInterpolate,
|
|
70
|
+
interpolateControlPoints,
|
|
71
|
+
rawInterpolationToBoneInterpolation,
|
|
72
|
+
} from "./animation"
|
|
68
73
|
export { VMDLoader, type CameraKeyframe, type IkFrame } from "./vmd-loader"
|
|
69
74
|
export { CameraAnimation, type CameraPose } from "./camera-animation"
|
|
70
75
|
export { RezePhysics } from "./physics"
|