reze-engine 0.15.0 → 0.16.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.
Files changed (93) hide show
  1. package/README.md +3 -3
  2. package/dist/camera.d.ts +2 -0
  3. package/dist/camera.d.ts.map +1 -1
  4. package/dist/camera.js +8 -3
  5. package/dist/engine.d.ts +19 -4
  6. package/dist/engine.d.ts.map +1 -1
  7. package/dist/engine.js +294 -27
  8. package/dist/gpu-profile.d.ts +19 -0
  9. package/dist/gpu-profile.d.ts.map +1 -0
  10. package/dist/gpu-profile.js +120 -0
  11. package/dist/ik-solver.d.ts.map +1 -1
  12. package/dist/ik-solver.js +13 -7
  13. package/dist/math.d.ts +2 -0
  14. package/dist/math.d.ts.map +1 -1
  15. package/dist/math.js +65 -46
  16. package/dist/model.d.ts +28 -1
  17. package/dist/model.d.ts.map +1 -1
  18. package/dist/model.js +259 -75
  19. package/dist/physics/body.d.ts +2 -0
  20. package/dist/physics/body.d.ts.map +1 -1
  21. package/dist/physics/body.js +30 -0
  22. package/dist/physics/constraint.d.ts +13 -0
  23. package/dist/physics/constraint.d.ts.map +1 -1
  24. package/dist/physics/constraint.js +13 -0
  25. package/dist/physics/contact.d.ts +28 -0
  26. package/dist/physics/contact.d.ts.map +1 -1
  27. package/dist/physics/contact.js +27 -32
  28. package/dist/physics/physics.d.ts +3 -0
  29. package/dist/physics/physics.d.ts.map +1 -1
  30. package/dist/physics/physics.js +57 -8
  31. package/dist/physics/profile.d.ts +18 -0
  32. package/dist/physics/profile.d.ts.map +1 -0
  33. package/dist/physics/profile.js +44 -0
  34. package/dist/physics/solver.d.ts.map +1 -1
  35. package/dist/physics/solver.js +436 -293
  36. package/dist/pmx-loader.d.ts.map +1 -1
  37. package/dist/pmx-loader.js +2 -30
  38. package/dist/shaders/materials/body.d.ts +1 -1
  39. package/dist/shaders/materials/body.d.ts.map +1 -1
  40. package/dist/shaders/materials/body.js +3 -1
  41. package/dist/shaders/materials/cloth_rough.d.ts +1 -1
  42. package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
  43. package/dist/shaders/materials/cloth_rough.js +3 -1
  44. package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
  45. package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
  46. package/dist/shaders/materials/common.d.ts +2 -2
  47. package/dist/shaders/materials/common.d.ts.map +1 -1
  48. package/dist/shaders/materials/common.js +8 -0
  49. package/dist/shaders/materials/default.d.ts +1 -1
  50. package/dist/shaders/materials/default.d.ts.map +1 -1
  51. package/dist/shaders/materials/eye.d.ts +1 -1
  52. package/dist/shaders/materials/eye.d.ts.map +1 -1
  53. package/dist/shaders/materials/face.d.ts +1 -1
  54. package/dist/shaders/materials/face.d.ts.map +1 -1
  55. package/dist/shaders/materials/face.js +4 -1
  56. package/dist/shaders/materials/hair.d.ts +1 -1
  57. package/dist/shaders/materials/hair.d.ts.map +1 -1
  58. package/dist/shaders/materials/metal.d.ts +1 -1
  59. package/dist/shaders/materials/metal.d.ts.map +1 -1
  60. package/dist/shaders/materials/stockings.d.ts +1 -1
  61. package/dist/shaders/materials/stockings.d.ts.map +1 -1
  62. package/dist/shaders/materials/stockings.js +13 -9
  63. package/dist/shaders/passes/composite.d.ts +1 -1
  64. package/dist/shaders/passes/composite.d.ts.map +1 -1
  65. package/dist/shaders/passes/composite.js +18 -12
  66. package/dist/shaders/passes/morph.d.ts +2 -0
  67. package/dist/shaders/passes/morph.d.ts.map +1 -0
  68. package/dist/shaders/passes/morph.js +49 -0
  69. package/package.json +1 -1
  70. package/src/camera.ts +9 -3
  71. package/src/engine.ts +336 -29
  72. package/src/ik-solver.ts +14 -7
  73. package/src/math.ts +41 -51
  74. package/src/model.ts +288 -91
  75. package/src/physics/body.ts +29 -0
  76. package/src/physics/constraint.ts +30 -0
  77. package/src/physics/contact.ts +43 -30
  78. package/src/physics/physics.ts +58 -17
  79. package/src/physics/solver.ts +431 -284
  80. package/src/pmx-loader.ts +2 -35
  81. package/src/shaders/materials/body.ts +3 -1
  82. package/src/shaders/materials/cloth_rough.ts +3 -1
  83. package/src/shaders/materials/common.ts +8 -0
  84. package/src/shaders/materials/face.ts +4 -1
  85. package/src/shaders/materials/stockings.ts +13 -9
  86. package/src/shaders/passes/composite.ts +18 -12
  87. package/src/shaders/passes/morph.ts +50 -0
  88. package/dist/physics-debug.d.ts +0 -30
  89. package/dist/physics-debug.d.ts.map +0 -1
  90. package/dist/physics-debug.js +0 -526
  91. package/dist/shaders/passes/physics-debug.d.ts +0 -2
  92. package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
  93. package/dist/shaders/passes/physics-debug.js +0 -69
@@ -37,27 +37,49 @@ export interface Contact {
37
37
  appliedNormalImpulse: number
38
38
  appliedFrictionImpulse1: number
39
39
  appliedFrictionImpulse2: number
40
+
41
+ // Per-substep cache. Written by the solver's setup pass, read by iter.
42
+ // Normal row:
43
+ cAxN: number; cAyN: number; cAzN: number // rA × n
44
+ cBxN: number; cByN: number; cBzN: number // rB × n
45
+ jacInvN: number
46
+ bounceVel: number // restitution reference, captured at setup from initial relVelN
47
+ // Friction tangent 1:
48
+ t1x: number; t1y: number; t1z: number
49
+ cAxT1: number; cAyT1: number; cAzT1: number
50
+ cBxT1: number; cByT1: number; cBzT1: number
51
+ jacInvT1: number
52
+ // Friction tangent 2:
53
+ t2x: number; t2y: number; t2z: number
54
+ cAxT2: number; cAyT2: number; cAzT2: number
55
+ cBxT2: number; cByT2: number; cBzT2: number
56
+ jacInvT2: number
40
57
  }
41
58
 
42
59
  function makeContact(): Contact {
43
60
  return {
44
- bodyA: 0,
45
- bodyB: 0,
46
- rAx: 0,
47
- rAy: 0,
48
- rAz: 0,
49
- rBx: 0,
50
- rBy: 0,
51
- rBz: 0,
52
- nx: 0,
53
- ny: 0,
54
- nz: 0,
61
+ bodyA: 0, bodyB: 0,
62
+ rAx: 0, rAy: 0, rAz: 0,
63
+ rBx: 0, rBy: 0, rBz: 0,
64
+ nx: 0, ny: 0, nz: 0,
55
65
  depth: 0,
56
66
  friction: 0,
57
67
  restitution: 0,
58
68
  appliedNormalImpulse: 0,
59
69
  appliedFrictionImpulse1: 0,
60
70
  appliedFrictionImpulse2: 0,
71
+ cAxN: 0, cAyN: 0, cAzN: 0,
72
+ cBxN: 0, cByN: 0, cBzN: 0,
73
+ jacInvN: 0,
74
+ bounceVel: 0,
75
+ t1x: 0, t1y: 0, t1z: 0,
76
+ cAxT1: 0, cAyT1: 0, cAzT1: 0,
77
+ cBxT1: 0, cByT1: 0, cBzT1: 0,
78
+ jacInvT1: 0,
79
+ t2x: 0, t2y: 0, t2z: 0,
80
+ cAxT2: 0, cAyT2: 0, cAzT2: 0,
81
+ cBxT2: 0, cByT2: 0, cBzT2: 0,
82
+ jacInvT2: 0,
61
83
  }
62
84
  }
63
85
 
@@ -958,26 +980,17 @@ function flipLastNormal(pool: ContactPool): void {
958
980
  c.nz = -c.nz
959
981
  }
960
982
 
961
- // O(N²) AABB pair test. For 30–100 PMX bodies that's <5000 checks per
962
- // step; SAP / dynamic AABB tree pay off above ~500 bodies. The pair filter
963
- // uses the AND form `(maskA & groupB) && (maskB & groupA)` — both sides
964
- // must agree, which is what PMX rigs are tuned against.
983
+ // Iterate the prebuilt candidate-pair list and AABB-test each pair. The
984
+ // static-static and group/mask filters were applied once at construction
985
+ // see RigidBodyStore.getCollisionPairs. SAP / dynamic AABB tree pay off
986
+ // above ~500 bodies; below that this flat sweep wins on cache locality.
965
987
  export function findContacts(store: RigidBodyStore, pool: ContactPool): void {
966
988
  store.updateAabbs()
967
- const N = store.count
968
- const invMass = store.invMass
969
- const group = store.collisionGroup
970
- const mask = store.willCollideMask
971
-
972
- for (let i = 0; i < N; i++) {
973
- const gi = group[i]
974
- const mi = mask[i]
975
- const dynA = invMass[i] > 0
976
- for (let j = i + 1; j < N; j++) {
977
- if (!dynA && invMass[j] === 0) continue // both static — no resolution
978
- if ((mi & group[j]) === 0 || (mask[j] & gi) === 0) continue
979
- if (!aabbOverlap(store, i, j)) continue
980
- generateContacts(store, i, j, pool)
981
- }
989
+ const pairs = store.getCollisionPairs()
990
+ for (let p = 0; p < pairs.length; p += 2) {
991
+ const i = pairs[p]
992
+ const j = pairs[p + 1]
993
+ if (!aabbOverlap(store, i, j)) continue
994
+ generateContacts(store, i, j, pool)
982
995
  }
983
996
  }
@@ -21,11 +21,16 @@ export class RezePhysics {
21
21
  private constraints: SixDofSpringConstraint[]
22
22
  private contacts: ContactPool
23
23
  private firstFrame = true
24
- // Fixed-timestep accumulator: physics runs at 75 Hz regardless of render
25
- // rate, so spring impulse, damping, and integration are deterministic.
26
24
  private timeAccum = 0
27
- private readonly fixedTimeStep = 1 / 75
28
- private readonly maxSubSteps = 10
25
+ private readonly fixedTimeStep = 1 / 60
26
+ private readonly maxSubSteps = 6
27
+
28
+ // Fixed-timestep render interpolation ("Fix Your Timestep"): the dynamic body pose is
29
+ // rendered as lerp(prev, curr, alpha) between the last two completed substeps, where
30
+ // alpha = leftover accumulator / fixedTimeStep. Removes the temporal aliasing that shows
31
+ // as hair/cloth judder when the render rate doesn't line up with the 60Hz physics step.
32
+ private prevPositions: Float32Array
33
+ private prevOrientations: Float32Array
29
34
 
30
35
  constructor(rigidbodies: Rigidbody[], joints: Joint[] = []) {
31
36
  this.rigidbodies = rigidbodies
@@ -34,6 +39,14 @@ export class RezePhysics {
34
39
  this.world = new World(new Vec3(0, -98, 0))
35
40
  this.constraints = buildConstraints(rigidbodies, joints)
36
41
  this.contacts = new ContactPool()
42
+ this.prevPositions = new Float32Array(this.store.count * 3)
43
+ this.prevOrientations = new Float32Array(this.store.count * 4)
44
+ }
45
+
46
+ // Snapshot the current body pose as the interpolation "previous" state.
47
+ private savePrevState(): void {
48
+ this.prevPositions.set(this.store.positions)
49
+ this.prevOrientations.set(this.store.orientations)
37
50
  }
38
51
 
39
52
  setGravity(gravity: Vec3): void {
@@ -72,6 +85,8 @@ export class RezePhysics {
72
85
  reset(boneWorldMatrices: Mat4[]): void {
73
86
  if (this.firstFrame) return
74
87
  this.snapBodiesToBones(boneWorldMatrices)
88
+ this.savePrevState() // prev == curr after a snap, so no interpolation jump
89
+ this.timeAccum = 0
75
90
  }
76
91
 
77
92
  step(dt: number, boneWorldMatrices: Mat4[], boneInverseBindMatrices: Float32Array): void {
@@ -80,6 +95,7 @@ export class RezePhysics {
80
95
  // Start at current bone pose, not the PMX bind pose, so animations
81
96
  // that skip frame 0 don't pop bodies on first step.
82
97
  this.snapBodiesToBones(boneWorldMatrices)
98
+ this.savePrevState()
83
99
  this.firstFrame = false
84
100
  }
85
101
 
@@ -89,17 +105,21 @@ export class RezePhysics {
89
105
  this.syncFromBones(boneWorldMatrices, dt)
90
106
 
91
107
  // Fixed-timestep substeps. The maxSubSteps cap prevents runaway after
92
- // a long stall (tab backgrounded, etc.).
108
+ // a long stall (tab backgrounded, etc.). Snapshot the pose before each step so
109
+ // after the loop prevState is one substep behind the live (current) state.
93
110
  this.timeAccum += dt
94
111
  let sub = 0
95
112
  while (this.timeAccum >= this.fixedTimeStep && sub < this.maxSubSteps) {
113
+ this.savePrevState()
96
114
  this.world.step(this.store, this.constraints, this.contacts, this.fixedTimeStep)
97
115
  this.timeAccum -= this.fixedTimeStep
98
116
  sub++
99
117
  }
100
118
  if (sub === this.maxSubSteps) this.timeAccum = 0
101
119
 
102
- this.applyDynamicsToBones(boneWorldMatrices)
120
+ // Fraction into the next (not-yet-taken) step; always in [0, 1).
121
+ const alpha = this.fixedTimeStep > 0 ? this.timeAccum / this.fixedTimeStep : 0
122
+ this.applyDynamicsToBones(boneWorldMatrices, alpha)
103
123
  }
104
124
 
105
125
  // Snap all bone-bound bodies to boneWorld × bodyOffset, zero velocities.
@@ -219,13 +239,18 @@ export class RezePhysics {
219
239
 
220
240
  // Dynamic bodies write their transform back to the bone matrix:
221
241
  // boneWorld = bodyWorld × bodyOffsetInverse.
222
- private applyDynamicsToBones(boneWorldMatrices: Mat4[]): void {
242
+ // The body pose is the render-interpolated pose between the previous and current
243
+ // substep states (alpha = fraction into the next step), which removes fixed-step judder.
244
+ private applyDynamicsToBones(boneWorldMatrices: Mat4[], alpha: number): void {
223
245
  const N = this.store.count
224
246
  const inv = this.store.bodyOffsetInverse
225
247
  const positions = this.store.positions
226
248
  const orientations = this.store.orientations
249
+ const prevPos = this.prevPositions
250
+ const prevOri = this.prevOrientations
227
251
  const types = this.store.type
228
252
  const boneIdx = this.store.boneIndex
253
+ const oneMinus = 1 - alpha
229
254
 
230
255
  for (let i = 0; i < N; i++) {
231
256
  if (types[i] !== RigidbodyType.Dynamic) continue
@@ -234,16 +259,32 @@ export class RezePhysics {
234
259
 
235
260
  const i3 = i * 3
236
261
  const i4 = i * 4
237
- Mat4.fromPositionRotationInto(
238
- positions[i3 + 0],
239
- positions[i3 + 1],
240
- positions[i3 + 2],
241
- orientations[i4 + 0],
242
- orientations[i4 + 1],
243
- orientations[i4 + 2],
244
- orientations[i4 + 3],
245
- _bodyMat,
246
- )
262
+
263
+ // Position: straight lerp.
264
+ const px = prevPos[i3 + 0] * oneMinus + positions[i3 + 0] * alpha
265
+ const py = prevPos[i3 + 1] * oneMinus + positions[i3 + 1] * alpha
266
+ const pz = prevPos[i3 + 2] * oneMinus + positions[i3 + 2] * alpha
267
+
268
+ // Orientation: shortest-arc nlerp (bodies rotate little per fixed step, so nlerp
269
+ // tracks slerp closely and avoids the trig).
270
+ const ax = prevOri[i4 + 0], ay = prevOri[i4 + 1], az = prevOri[i4 + 2], aw = prevOri[i4 + 3]
271
+ let bx = orientations[i4 + 0], by = orientations[i4 + 1], bz = orientations[i4 + 2], bw = orientations[i4 + 3]
272
+ if (ax * bx + ay * by + az * bz + aw * bw < 0) {
273
+ bx = -bx; by = -by; bz = -bz; bw = -bw
274
+ }
275
+ let qx = ax * oneMinus + bx * alpha
276
+ let qy = ay * oneMinus + by * alpha
277
+ let qz = az * oneMinus + bz * alpha
278
+ let qw = aw * oneMinus + bw * alpha
279
+ const ql = Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw)
280
+ if (ql > 0) {
281
+ const invL = 1 / ql
282
+ qx *= invL; qy *= invL; qz *= invL; qw *= invL
283
+ } else {
284
+ qx = 0; qy = 0; qz = 0; qw = 1
285
+ }
286
+
287
+ Mat4.fromPositionRotationInto(px, py, pz, qx, qy, qz, qw, _bodyMat)
247
288
  Mat4.multiplyArrays(_bodyMat, 0, inv, i * 16, _boneMat, 0)
248
289
 
249
290
  // Sanity gate against NaN / extreme values — silently drop the update.