reze-engine 0.16.1 → 0.16.3
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 +139 -284
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +5 -5
- package/dist/model.d.ts +4 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +14 -1
- package/dist/physics/body.d.ts +1 -0
- package/dist/physics/body.d.ts.map +1 -1
- package/dist/physics/body.js +2 -0
- package/dist/physics/constraint.d.ts +8 -1
- package/dist/physics/constraint.d.ts.map +1 -1
- package/dist/physics/constraint.js +13 -1
- package/dist/physics/physics.d.ts +15 -1
- package/dist/physics/physics.d.ts.map +1 -1
- package/dist/physics/physics.js +413 -48
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +223 -39
- package/dist/physics/types.d.ts +2 -1
- package/dist/physics/types.d.ts.map +1 -1
- package/dist/physics/types.js +5 -0
- package/dist/physics/world.d.ts +3 -0
- package/dist/physics/world.d.ts.map +1 -1
- package/dist/physics/world.js +31 -4
- package/dist/physics-debug.d.ts +30 -0
- package/dist/physics-debug.d.ts.map +1 -0
- package/dist/physics-debug.js +526 -0
- package/dist/pmx-loader.d.ts +2 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +37 -20
- package/dist/shaders/materials/body.d.ts +1 -1
- package/dist/shaders/materials/body.d.ts.map +1 -1
- package/dist/shaders/materials/body.js +90 -85
- package/dist/shaders/materials/cloth_rough.d.ts +1 -1
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_rough.js +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_smooth.js +2 -2
- package/dist/shaders/materials/common.d.ts +1 -1
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +10 -0
- package/dist/shaders/materials/default.d.ts +1 -1
- package/dist/shaders/materials/default.d.ts.map +1 -1
- package/dist/shaders/materials/default.js +37 -32
- package/dist/shaders/materials/eye.d.ts +1 -1
- package/dist/shaders/materials/eye.d.ts.map +1 -1
- package/dist/shaders/materials/eye.js +40 -35
- package/dist/shaders/materials/face.d.ts +1 -1
- package/dist/shaders/materials/face.d.ts.map +1 -1
- package/dist/shaders/materials/face.js +90 -85
- package/dist/shaders/materials/hair.d.ts +1 -1
- package/dist/shaders/materials/hair.d.ts.map +1 -1
- package/dist/shaders/materials/hair.js +85 -75
- package/dist/shaders/materials/metal.d.ts +1 -1
- package/dist/shaders/materials/metal.d.ts.map +1 -1
- package/dist/shaders/materials/metal.js +1 -1
- package/dist/shaders/materials/stockings.d.ts +1 -1
- package/dist/shaders/materials/stockings.d.ts.map +1 -1
- package/dist/shaders/materials/stockings.js +1 -1
- package/dist/shaders/passes/physics-debug.d.ts +2 -0
- package/dist/shaders/passes/physics-debug.d.ts.map +1 -0
- package/dist/shaders/passes/physics-debug.js +69 -0
- package/package.json +1 -1
- package/src/engine.ts +36 -21
- package/src/model.ts +18 -1
- package/src/physics/body.ts +5 -0
- package/src/physics/constraint.ts +35 -6
- package/src/physics/physics.ts +423 -56
- package/src/physics/solver.ts +213 -38
- package/src/physics/types.ts +10 -2
- package/src/physics/world.ts +33 -4
- package/src/pmx-loader.ts +38 -20
- package/src/shaders/materials/body.ts +97 -92
- package/src/shaders/materials/cloth_rough.ts +1 -1
- package/src/shaders/materials/cloth_smooth.ts +2 -2
- package/src/shaders/materials/common.ts +10 -0
- package/src/shaders/materials/default.ts +45 -40
- package/src/shaders/materials/eye.ts +48 -43
- package/src/shaders/materials/face.ts +97 -92
- package/src/shaders/materials/hair.ts +92 -82
- package/src/shaders/materials/metal.ts +1 -1
- package/src/shaders/materials/stockings.ts +1 -1
- package/dist/gpu-profile.d.ts +0 -19
- package/dist/gpu-profile.d.ts.map +0 -1
- package/dist/gpu-profile.js +0 -120
- package/dist/physics/profile.d.ts +0 -18
- package/dist/physics/profile.d.ts.map +0 -1
- package/dist/physics/profile.js +0 -44
package/src/physics/physics.ts
CHANGED
|
@@ -32,6 +32,34 @@ export class RezePhysics {
|
|
|
32
32
|
private prevPositions: Float32Array
|
|
33
33
|
private prevOrientations: Float32Array
|
|
34
34
|
|
|
35
|
+
// Per-frame kinematic body targets (boneWorld × bodyOffset). Kinematic
|
|
36
|
+
// bodies advance toward these incrementally per substep instead of jumping
|
|
37
|
+
// to the final pose before substep 1. Velocities come from the target
|
|
38
|
+
// trajectory (frame-to-frame target delta over render dt) — deriving them
|
|
39
|
+
// from the per-substep advancement instead ripples with the accumulator
|
|
40
|
+
// phase at render rates above 60 Hz and visibly excites cloth chains.
|
|
41
|
+
private kinTargetPos: Float32Array
|
|
42
|
+
private kinTargetOri: Float32Array
|
|
43
|
+
private kinTargetVel: Float32Array
|
|
44
|
+
private kinTargetAngVel: Float32Array
|
|
45
|
+
|
|
46
|
+
// For each dynamic body, the kinematic body its constraint chain roots at
|
|
47
|
+
// (-1 if unreachable). On a teleport, dynamic bodies are carried rigidly by
|
|
48
|
+
// their root's transform delta so cloth keeps its pose relative to the
|
|
49
|
+
// character instead of being dragged across the jump.
|
|
50
|
+
private kinRoot: Int32Array
|
|
51
|
+
// Kinematic bodies whose target jumped discontinuously this frame.
|
|
52
|
+
private teleportFlags: Uint8Array
|
|
53
|
+
// PMX mode-2 bodies jointed *directly* to a bone-follow body (breasts,
|
|
54
|
+
// chain roots). Only these get MMD's bone-position alignment: their bone's
|
|
55
|
+
// animated matrix is trustworthy, whereas deeper chain bones' animated
|
|
56
|
+
// matrices don't include the physics deflection of their parents, so
|
|
57
|
+
// pinning those would freeze the chain.
|
|
58
|
+
private alignPinned: Uint8Array
|
|
59
|
+
// Debug counter: frames on which a teleport (scrub/jump discontinuity)
|
|
60
|
+
// was detected and settled.
|
|
61
|
+
teleportCount = 0
|
|
62
|
+
|
|
35
63
|
constructor(rigidbodies: Rigidbody[], joints: Joint[] = []) {
|
|
36
64
|
this.rigidbodies = rigidbodies
|
|
37
65
|
this.joints = joints
|
|
@@ -41,6 +69,52 @@ export class RezePhysics {
|
|
|
41
69
|
this.contacts = new ContactPool()
|
|
42
70
|
this.prevPositions = new Float32Array(this.store.count * 3)
|
|
43
71
|
this.prevOrientations = new Float32Array(this.store.count * 4)
|
|
72
|
+
this.kinTargetPos = new Float32Array(this.store.count * 3)
|
|
73
|
+
this.kinTargetOri = new Float32Array(this.store.count * 4)
|
|
74
|
+
this.kinTargetVel = new Float32Array(this.store.count * 3)
|
|
75
|
+
this.kinTargetAngVel = new Float32Array(this.store.count * 3)
|
|
76
|
+
this.kinRoot = this.buildKinematicRoots()
|
|
77
|
+
this.teleportFlags = new Uint8Array(this.store.count)
|
|
78
|
+
|
|
79
|
+
this.alignPinned = new Uint8Array(this.store.count)
|
|
80
|
+
const types = this.store.type
|
|
81
|
+
for (const c of this.constraints) {
|
|
82
|
+
const tA = types[c.bodyA]
|
|
83
|
+
const tB = types[c.bodyB]
|
|
84
|
+
const aFollows = tA === RigidbodyType.Static || tA === RigidbodyType.Kinematic
|
|
85
|
+
const bFollows = tB === RigidbodyType.Static || tB === RigidbodyType.Kinematic
|
|
86
|
+
if (aFollows && this.store.aligned[c.bodyB]) this.alignPinned[c.bodyB] = 1
|
|
87
|
+
if (bFollows && this.store.aligned[c.bodyA]) this.alignPinned[c.bodyA] = 1
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// BFS over the joint graph from every kinematic body, assigning each
|
|
92
|
+
// reachable dynamic body the kinematic body it (transitively) hangs off.
|
|
93
|
+
private buildKinematicRoots(): Int32Array {
|
|
94
|
+
const N = this.store.count
|
|
95
|
+
const root = new Int32Array(N).fill(-1)
|
|
96
|
+
const types = this.store.type
|
|
97
|
+
const adj: number[][] = Array.from({ length: N }, () => [])
|
|
98
|
+
for (const c of this.constraints) {
|
|
99
|
+
adj[c.bodyA].push(c.bodyB)
|
|
100
|
+
adj[c.bodyB].push(c.bodyA)
|
|
101
|
+
}
|
|
102
|
+
const queue: number[] = []
|
|
103
|
+
for (let i = 0; i < N; i++) {
|
|
104
|
+
if (types[i] === RigidbodyType.Static || types[i] === RigidbodyType.Kinematic) {
|
|
105
|
+
root[i] = i
|
|
106
|
+
queue.push(i)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (let h = 0; h < queue.length; h++) {
|
|
110
|
+
const i = queue[h]
|
|
111
|
+
for (const j of adj[i]) {
|
|
112
|
+
if (root[j] !== -1) continue
|
|
113
|
+
root[j] = root[i]
|
|
114
|
+
queue.push(j)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return root
|
|
44
118
|
}
|
|
45
119
|
|
|
46
120
|
// Snapshot the current body pose as the interpolation "previous" state.
|
|
@@ -86,6 +160,13 @@ export class RezePhysics {
|
|
|
86
160
|
if (this.firstFrame) return
|
|
87
161
|
this.snapBodiesToBones(boneWorldMatrices)
|
|
88
162
|
this.savePrevState() // prev == curr after a snap, so no interpolation jump
|
|
163
|
+
// Reseed kinematic targets from the snapped pose — otherwise the next
|
|
164
|
+
// step derives the target-trajectory velocity against the pre-reset
|
|
165
|
+
// targets and feeds one frame of enormous anchor velocity to the solver.
|
|
166
|
+
this.kinTargetPos.set(this.store.positions)
|
|
167
|
+
this.kinTargetOri.set(this.store.orientations)
|
|
168
|
+
this.kinTargetVel.fill(0)
|
|
169
|
+
this.kinTargetAngVel.fill(0)
|
|
89
170
|
this.timeAccum = 0
|
|
90
171
|
}
|
|
91
172
|
|
|
@@ -96,26 +177,57 @@ export class RezePhysics {
|
|
|
96
177
|
// that skip frame 0 don't pop bodies on first step.
|
|
97
178
|
this.snapBodiesToBones(boneWorldMatrices)
|
|
98
179
|
this.savePrevState()
|
|
180
|
+
// Seed kinematic targets so the first frame's target-delta velocity
|
|
181
|
+
// is zero instead of a jump from the origin.
|
|
182
|
+
this.kinTargetPos.set(this.store.positions)
|
|
183
|
+
this.kinTargetOri.set(this.store.orientations)
|
|
99
184
|
this.firstFrame = false
|
|
100
185
|
}
|
|
101
186
|
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
187
|
+
// Compute this frame's kinematic targets from the current bone pose. A
|
|
188
|
+
// target jump beyond what continuous motion can produce (timeline scrub)
|
|
189
|
+
// is a teleport, handled per kinematic root: rigidly carry that root's
|
|
190
|
+
// dynamic chains across the jump with zeroed momentum, snap the root,
|
|
191
|
+
// and keep simulating — dragging cloth through a discontinuity at the
|
|
192
|
+
// raw derived velocity is what used to explode the solver. Chains under
|
|
193
|
+
// unaffected roots keep their momentum untouched.
|
|
194
|
+
if (this.computeKinematicTargets(boneWorldMatrices, dt)) {
|
|
195
|
+
this.teleportCount++
|
|
196
|
+
this.carryDynamicThroughTeleport()
|
|
197
|
+
this.snapKinematicToTargets(true)
|
|
198
|
+
this.savePrevState() // prev == curr so interpolation doesn't streak
|
|
199
|
+
}
|
|
106
200
|
|
|
107
201
|
// Fixed-timestep substeps. The maxSubSteps cap prevents runaway after
|
|
108
202
|
// a long stall (tab backgrounded, etc.). Snapshot the pose before each step so
|
|
109
203
|
// after the loop prevState is one substep behind the live (current) state.
|
|
204
|
+
// Kinematic bodies split the remaining gap evenly across this frame's
|
|
205
|
+
// substeps (f = 1/substeps-left) and land exactly on the frame's bone
|
|
206
|
+
// pose by the last one: at 60 Hz render that reproduces the classic
|
|
207
|
+
// sync-once-per-frame behavior exactly (no fractional lag trembling
|
|
208
|
+
// against the rendered mesh), while at lower rates the per-substep
|
|
209
|
+
// constraint error stays bounded to one fixed step of bone motion.
|
|
110
210
|
this.timeAccum += dt
|
|
111
|
-
let
|
|
112
|
-
|
|
211
|
+
let nSub = Math.floor(this.timeAccum / this.fixedTimeStep)
|
|
212
|
+
if (nSub > this.maxSubSteps) nSub = this.maxSubSteps
|
|
213
|
+
for (let k = 0; k < nSub; k++) {
|
|
113
214
|
this.savePrevState()
|
|
215
|
+
this.advanceKinematicToTargets(1 / (nSub - k))
|
|
114
216
|
this.world.step(this.store, this.constraints, this.contacts, this.fixedTimeStep)
|
|
217
|
+
this.restoreNonFiniteBodies()
|
|
115
218
|
this.timeAccum -= this.fixedTimeStep
|
|
116
|
-
sub++
|
|
117
219
|
}
|
|
118
|
-
if (
|
|
220
|
+
if (this.timeAccum >= this.fixedTimeStep) {
|
|
221
|
+
// Substep budget exhausted mid-catchup: drop the remaining time and
|
|
222
|
+
// snap kinematic bodies the rest of the way (zero velocity) so they
|
|
223
|
+
// don't start next frame lagging behind their bones.
|
|
224
|
+
this.timeAccum = 0
|
|
225
|
+
this.snapKinematicToTargets(false)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// MMD mode-2 bone alignment for pinned (depth-1) bodies: position
|
|
229
|
+
// re-pins to the animated bone, rotation stays simulated.
|
|
230
|
+
this.alignPinnedBodiesToBones(boneWorldMatrices)
|
|
119
231
|
|
|
120
232
|
// Fraction into the next (not-yet-taken) step; always in [0, 1).
|
|
121
233
|
const alpha = this.fixedTimeStep > 0 ? this.timeAccum / this.fixedTimeStep : 0
|
|
@@ -158,22 +270,34 @@ export class RezePhysics {
|
|
|
158
270
|
}
|
|
159
271
|
}
|
|
160
272
|
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
// the
|
|
164
|
-
//
|
|
165
|
-
private
|
|
273
|
+
// Fill kinTargetPos/kinTargetOri = boneWorld × bodyOffset for every bone-
|
|
274
|
+
// bound Static/Kinematic body. Returns true if any target is discontinuous
|
|
275
|
+
// with the current body pose — farther than continuous motion can carry it
|
|
276
|
+
// in one render frame, or rotated more than 90°.
|
|
277
|
+
private computeKinematicTargets(boneWorldMatrices: Mat4[], dt: number): boolean {
|
|
166
278
|
const N = this.store.count
|
|
167
279
|
const offsets = this.store.bodyOffsetMatrix
|
|
168
280
|
const positions = this.store.positions
|
|
169
281
|
const orientations = this.store.orientations
|
|
170
|
-
const lv = this.store.linearVelocities
|
|
171
|
-
const av = this.store.angularVelocities
|
|
172
282
|
const types = this.store.type
|
|
173
283
|
const boneIdx = this.store.boneIndex
|
|
284
|
+
const tp = this.kinTargetPos
|
|
285
|
+
const to = this.kinTargetOri
|
|
286
|
+
|
|
287
|
+
// 250 units/s scaled by frame time, floored at 4 units: far above the
|
|
288
|
+
// fastest limb motion (a false positive freezes that chain's momentum
|
|
289
|
+
// for a frame, which reads as stutter), far below any real scrub jump.
|
|
290
|
+
const maxJump = Math.max(4, 250 * dt)
|
|
291
|
+
const maxJumpSq = maxJump * maxJump
|
|
292
|
+
const flags = this.teleportFlags
|
|
293
|
+
let teleport = false
|
|
294
|
+
|
|
295
|
+
const tv = this.kinTargetVel
|
|
296
|
+
const tav = this.kinTargetAngVel
|
|
174
297
|
const invDt = dt > 0 ? 1 / dt : 0
|
|
175
298
|
|
|
176
299
|
for (let i = 0; i < N; i++) {
|
|
300
|
+
flags[i] = 0
|
|
177
301
|
const t = types[i]
|
|
178
302
|
if (t !== RigidbodyType.Static && t !== RigidbodyType.Kinematic) continue
|
|
179
303
|
const b = boneIdx[i]
|
|
@@ -183,57 +307,291 @@ export class RezePhysics {
|
|
|
183
307
|
|
|
184
308
|
const i3 = i * 3
|
|
185
309
|
const i4 = i * 4
|
|
310
|
+
// Previous frame's target — the reference for the trajectory velocity.
|
|
311
|
+
const oldTx = tp[i3 + 0], oldTy = tp[i3 + 1], oldTz = tp[i3 + 2]
|
|
312
|
+
const oldOx = to[i4 + 0], oldOy = to[i4 + 1], oldOz = to[i4 + 2], oldOw = to[i4 + 3]
|
|
313
|
+
|
|
314
|
+
tp[i3 + 0] = _bodyMat[12]
|
|
315
|
+
tp[i3 + 1] = _bodyMat[13]
|
|
316
|
+
tp[i3 + 2] = _bodyMat[14]
|
|
317
|
+
Mat4.toQuatFromArrayInto(_bodyMat, 0, _scratchQuat)
|
|
318
|
+
const nOx = _scratchQuat.x, nOy = _scratchQuat.y, nOz = _scratchQuat.z, nOw = _scratchQuat.w
|
|
319
|
+
to[i4 + 0] = nOx
|
|
320
|
+
to[i4 + 1] = nOy
|
|
321
|
+
to[i4 + 2] = nOz
|
|
322
|
+
to[i4 + 3] = nOw
|
|
323
|
+
|
|
324
|
+
const dx = tp[i3 + 0] - positions[i3 + 0]
|
|
325
|
+
const dy = tp[i3 + 1] - positions[i3 + 1]
|
|
326
|
+
const dz = tp[i3 + 2] - positions[i3 + 2]
|
|
327
|
+
// |q·q'| = cos(θ/2); below cos(45°) the body turned more than 90°.
|
|
328
|
+
const dot =
|
|
329
|
+
to[i4 + 0] * orientations[i4 + 0] +
|
|
330
|
+
to[i4 + 1] * orientations[i4 + 1] +
|
|
331
|
+
to[i4 + 2] * orientations[i4 + 2] +
|
|
332
|
+
to[i4 + 3] * orientations[i4 + 3]
|
|
333
|
+
if (dx * dx + dy * dy + dz * dz > maxJumpSq || Math.abs(dot) < 0.7071) {
|
|
334
|
+
flags[i] = 1
|
|
335
|
+
teleport = true
|
|
336
|
+
tv[i3 + 0] = 0; tv[i3 + 1] = 0; tv[i3 + 2] = 0
|
|
337
|
+
tav[i3 + 0] = 0; tav[i3 + 1] = 0; tav[i3 + 2] = 0
|
|
338
|
+
continue
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
tv[i3 + 0] = (tp[i3 + 0] - oldTx) * invDt
|
|
342
|
+
tv[i3 + 1] = (tp[i3 + 1] - oldTy) * invDt
|
|
343
|
+
tv[i3 + 2] = (tp[i3 + 2] - oldTz) * invDt
|
|
344
|
+
// ω ≈ 2 · qDiff.xyz / dt with qDiff = qNew · conj(qOld). Shortest-arc
|
|
345
|
+
// sign keeps qDiff and −qDiff (same rotation) from doubling ω.
|
|
346
|
+
const cox = -oldOx, coy = -oldOy, coz = -oldOz, cow = oldOw
|
|
347
|
+
const qdx = nOw * cox + nOx * cow + nOy * coz - nOz * coy
|
|
348
|
+
const qdy = nOw * coy - nOx * coz + nOy * cow + nOz * cox
|
|
349
|
+
const qdz = nOw * coz + nOx * coy - nOy * cox + nOz * cow
|
|
350
|
+
const qdw = nOw * cow - nOx * cox - nOy * coy - nOz * coz
|
|
351
|
+
const sign = qdw < 0 ? -1 : 1
|
|
352
|
+
tav[i3 + 0] = 2 * sign * qdx * invDt
|
|
353
|
+
tav[i3 + 1] = 2 * sign * qdy * invDt
|
|
354
|
+
tav[i3 + 2] = 2 * sign * qdz * invDt
|
|
355
|
+
}
|
|
356
|
+
return teleport
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Move kinematic bodies fraction f of the way to the frame target with the
|
|
360
|
+
// target-trajectory velocity, so joints see continuous anchor motion (and a
|
|
361
|
+
// smooth velocity signal) instead of a frame-sized jump on substep 1.
|
|
362
|
+
private advanceKinematicToTargets(f: number): void {
|
|
363
|
+
const N = this.store.count
|
|
364
|
+
const positions = this.store.positions
|
|
365
|
+
const orientations = this.store.orientations
|
|
366
|
+
const lv = this.store.linearVelocities
|
|
367
|
+
const av = this.store.angularVelocities
|
|
368
|
+
const types = this.store.type
|
|
369
|
+
const boneIdx = this.store.boneIndex
|
|
370
|
+
const tp = this.kinTargetPos
|
|
371
|
+
const to = this.kinTargetOri
|
|
372
|
+
const tv = this.kinTargetVel
|
|
373
|
+
const tav = this.kinTargetAngVel
|
|
374
|
+
|
|
375
|
+
for (let i = 0; i < N; i++) {
|
|
376
|
+
const t = types[i]
|
|
377
|
+
if (t !== RigidbodyType.Static && t !== RigidbodyType.Kinematic) continue
|
|
378
|
+
const b = boneIdx[i]
|
|
379
|
+
if (b < 0) continue
|
|
186
380
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
381
|
+
const i3 = i * 3
|
|
382
|
+
const i4 = i * 4
|
|
383
|
+
positions[i3 + 0] += (tp[i3 + 0] - positions[i3 + 0]) * f
|
|
384
|
+
positions[i3 + 1] += (tp[i3 + 1] - positions[i3 + 1]) * f
|
|
385
|
+
positions[i3 + 2] += (tp[i3 + 2] - positions[i3 + 2]) * f
|
|
386
|
+
lv[i3 + 0] = tv[i3 + 0]
|
|
387
|
+
lv[i3 + 1] = tv[i3 + 1]
|
|
388
|
+
lv[i3 + 2] = tv[i3 + 2]
|
|
389
|
+
av[i3 + 0] = tav[i3 + 0]
|
|
390
|
+
av[i3 + 1] = tav[i3 + 1]
|
|
391
|
+
av[i3 + 2] = tav[i3 + 2]
|
|
392
|
+
|
|
393
|
+
// Shortest-arc nlerp toward the target orientation.
|
|
192
394
|
const oldOx = orientations[i4 + 0],
|
|
193
395
|
oldOy = orientations[i4 + 1]
|
|
194
396
|
const oldOz = orientations[i4 + 2],
|
|
195
397
|
oldOw = orientations[i4 + 3]
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
398
|
+
let tx = to[i4 + 0],
|
|
399
|
+
ty = to[i4 + 1],
|
|
400
|
+
tz = to[i4 + 2],
|
|
401
|
+
tw = to[i4 + 3]
|
|
402
|
+
if (oldOx * tx + oldOy * ty + oldOz * tz + oldOw * tw < 0) {
|
|
403
|
+
tx = -tx; ty = -ty; tz = -tz; tw = -tw
|
|
404
|
+
}
|
|
405
|
+
let newOx = oldOx + (tx - oldOx) * f
|
|
406
|
+
let newOy = oldOy + (ty - oldOy) * f
|
|
407
|
+
let newOz = oldOz + (tz - oldOz) * f
|
|
408
|
+
let newOw = oldOw + (tw - oldOw) * f
|
|
409
|
+
const len2 = newOx * newOx + newOy * newOy + newOz * newOz + newOw * newOw
|
|
410
|
+
if (len2 > 1e-12) {
|
|
411
|
+
const inv = 1 / Math.sqrt(len2)
|
|
412
|
+
newOx *= inv; newOy *= inv; newOz *= inv; newOw *= inv
|
|
413
|
+
} else {
|
|
414
|
+
newOx = tx; newOy = ty; newOz = tz; newOw = tw
|
|
415
|
+
}
|
|
205
416
|
orientations[i4 + 0] = newOx
|
|
206
417
|
orientations[i4 + 1] = newOy
|
|
207
418
|
orientations[i4 + 2] = newOz
|
|
208
419
|
orientations[i4 + 3] = newOw
|
|
420
|
+
}
|
|
421
|
+
}
|
|
209
422
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
423
|
+
// Snap kinematic bodies straight to the frame target with zero velocity.
|
|
424
|
+
// Used on teleports (onlyFlagged) and when the substep budget runs out
|
|
425
|
+
// mid-catchup (all).
|
|
426
|
+
private snapKinematicToTargets(onlyFlagged: boolean): void {
|
|
427
|
+
const N = this.store.count
|
|
428
|
+
const positions = this.store.positions
|
|
429
|
+
const orientations = this.store.orientations
|
|
430
|
+
const lv = this.store.linearVelocities
|
|
431
|
+
const av = this.store.angularVelocities
|
|
432
|
+
const types = this.store.type
|
|
433
|
+
const boneIdx = this.store.boneIndex
|
|
434
|
+
const tp = this.kinTargetPos
|
|
435
|
+
const to = this.kinTargetOri
|
|
436
|
+
const flags = this.teleportFlags
|
|
437
|
+
|
|
438
|
+
for (let i = 0; i < N; i++) {
|
|
439
|
+
const t = types[i]
|
|
440
|
+
if (t !== RigidbodyType.Static && t !== RigidbodyType.Kinematic) continue
|
|
441
|
+
if (boneIdx[i] < 0) continue
|
|
442
|
+
if (onlyFlagged && !flags[i]) continue
|
|
443
|
+
const i3 = i * 3
|
|
444
|
+
const i4 = i * 4
|
|
445
|
+
positions[i3 + 0] = tp[i3 + 0]
|
|
446
|
+
positions[i3 + 1] = tp[i3 + 1]
|
|
447
|
+
positions[i3 + 2] = tp[i3 + 2]
|
|
448
|
+
orientations[i4 + 0] = to[i4 + 0]
|
|
449
|
+
orientations[i4 + 1] = to[i4 + 1]
|
|
450
|
+
orientations[i4 + 2] = to[i4 + 2]
|
|
451
|
+
orientations[i4 + 3] = to[i4 + 3]
|
|
452
|
+
lv[i3 + 0] = 0; lv[i3 + 1] = 0; lv[i3 + 2] = 0
|
|
453
|
+
av[i3 + 0] = 0; av[i3 + 1] = 0; av[i3 + 2] = 0
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Rigidly carry each dynamic body whose kinematic root teleported along
|
|
458
|
+
// with that root's current→target transform delta (velocity zeroed),
|
|
459
|
+
// preserving the cloth pose relative to the character across the jump.
|
|
460
|
+
// Must run before snapKinematicToTargets (it reads the pre-snap pose).
|
|
461
|
+
private carryDynamicThroughTeleport(): void {
|
|
462
|
+
const N = this.store.count
|
|
463
|
+
const positions = this.store.positions
|
|
464
|
+
const orientations = this.store.orientations
|
|
465
|
+
const lv = this.store.linearVelocities
|
|
466
|
+
const av = this.store.angularVelocities
|
|
467
|
+
const types = this.store.type
|
|
468
|
+
const tp = this.kinTargetPos
|
|
469
|
+
const to = this.kinTargetOri
|
|
470
|
+
const root = this.kinRoot
|
|
471
|
+
const flags = this.teleportFlags
|
|
472
|
+
|
|
473
|
+
for (let i = 0; i < N; i++) {
|
|
474
|
+
if (types[i] !== RigidbodyType.Dynamic) continue
|
|
475
|
+
const k = root[i]
|
|
476
|
+
if (k < 0 || !flags[k] || this.store.boneIndex[k] < 0) continue
|
|
477
|
+
const k3 = k * 3
|
|
478
|
+
const k4 = k * 4
|
|
479
|
+
// Root delta rotation R = qTarget · conj(qCurrent).
|
|
480
|
+
const cx = -orientations[k4 + 0],
|
|
481
|
+
cy = -orientations[k4 + 1],
|
|
482
|
+
cz = -orientations[k4 + 2],
|
|
483
|
+
cw = orientations[k4 + 3]
|
|
484
|
+
const txq = to[k4 + 0],
|
|
485
|
+
tyq = to[k4 + 1],
|
|
486
|
+
tzq = to[k4 + 2],
|
|
487
|
+
twq = to[k4 + 3]
|
|
488
|
+
const rx = twq * cx + txq * cw + tyq * cz - tzq * cy
|
|
489
|
+
const ry = twq * cy - txq * cz + tyq * cw + tzq * cx
|
|
490
|
+
const rz = twq * cz + txq * cy - tyq * cx + tzq * cw
|
|
491
|
+
const rw = twq * cw - txq * cx - tyq * cy - tzq * cz
|
|
492
|
+
|
|
493
|
+
const i3 = i * 3
|
|
494
|
+
const i4 = i * 4
|
|
495
|
+
// Position: rotate the offset from the root by R, re-anchor at target.
|
|
496
|
+
const ox = positions[i3 + 0] - positions[k3 + 0]
|
|
497
|
+
const oy = positions[i3 + 1] - positions[k3 + 1]
|
|
498
|
+
const oz = positions[i3 + 2] - positions[k3 + 2]
|
|
499
|
+
// v' = v + 2·rw·(r × v) + 2·(r × (r × v))
|
|
500
|
+
const c1x = ry * oz - rz * oy
|
|
501
|
+
const c1y = rz * ox - rx * oz
|
|
502
|
+
const c1z = rx * oy - ry * ox
|
|
503
|
+
const c2x = ry * c1z - rz * c1y
|
|
504
|
+
const c2y = rz * c1x - rx * c1z
|
|
505
|
+
const c2z = rx * c1y - ry * c1x
|
|
506
|
+
positions[i3 + 0] = tp[k3 + 0] + ox + 2 * (rw * c1x + c2x)
|
|
507
|
+
positions[i3 + 1] = tp[k3 + 1] + oy + 2 * (rw * c1y + c2y)
|
|
508
|
+
positions[i3 + 2] = tp[k3 + 2] + oz + 2 * (rw * c1z + c2z)
|
|
509
|
+
|
|
510
|
+
// Orientation: q' = R · q, renormalized.
|
|
511
|
+
const qx = orientations[i4 + 0],
|
|
512
|
+
qy = orientations[i4 + 1],
|
|
513
|
+
qz = orientations[i4 + 2],
|
|
514
|
+
qw = orientations[i4 + 3]
|
|
515
|
+
let nx = rw * qx + rx * qw + ry * qz - rz * qy
|
|
516
|
+
let ny = rw * qy - rx * qz + ry * qw + rz * qx
|
|
517
|
+
let nz = rw * qz + rx * qy - ry * qx + rz * qw
|
|
518
|
+
let nw = rw * qw - rx * qx - ry * qy - rz * qz
|
|
519
|
+
const len2 = nx * nx + ny * ny + nz * nz + nw * nw
|
|
520
|
+
if (len2 > 1e-12) {
|
|
521
|
+
const inv = 1 / Math.sqrt(len2)
|
|
522
|
+
nx *= inv; ny *= inv; nz *= inv; nw *= inv
|
|
523
|
+
orientations[i4 + 0] = nx
|
|
524
|
+
orientations[i4 + 1] = ny
|
|
525
|
+
orientations[i4 + 2] = nz
|
|
526
|
+
orientations[i4 + 3] = nw
|
|
236
527
|
}
|
|
528
|
+
|
|
529
|
+
// Momentum doesn't carry across a discontinuity.
|
|
530
|
+
lv[i3 + 0] = 0; lv[i3 + 1] = 0; lv[i3 + 2] = 0
|
|
531
|
+
av[i3 + 0] = 0; av[i3 + 1] = 0; av[i3 + 2] = 0
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Overwrite pinned mode-2 bodies' positions with boneWorld × bodyOffset
|
|
536
|
+
// from the animated bone pose, keeping simulated orientation and
|
|
537
|
+
// velocities. prevPositions follows so interpolation doesn't streak.
|
|
538
|
+
private alignPinnedBodiesToBones(boneWorldMatrices: Mat4[]): void {
|
|
539
|
+
const N = this.store.count
|
|
540
|
+
const pinned = this.alignPinned
|
|
541
|
+
const boneIdx = this.store.boneIndex
|
|
542
|
+
const offsets = this.store.bodyOffsetMatrix
|
|
543
|
+
const positions = this.store.positions
|
|
544
|
+
const prevPos = this.prevPositions
|
|
545
|
+
|
|
546
|
+
for (let i = 0; i < N; i++) {
|
|
547
|
+
if (!pinned[i]) continue
|
|
548
|
+
const b = boneIdx[i]
|
|
549
|
+
if (b < 0 || b >= boneWorldMatrices.length) continue
|
|
550
|
+
Mat4.multiplyArrays(boneWorldMatrices[b].values, 0, offsets, i * 16, _bodyMat, 0)
|
|
551
|
+
const i3 = i * 3
|
|
552
|
+
positions[i3 + 0] = _bodyMat[12]
|
|
553
|
+
positions[i3 + 1] = _bodyMat[13]
|
|
554
|
+
positions[i3 + 2] = _bodyMat[14]
|
|
555
|
+
prevPos[i3 + 0] = _bodyMat[12]
|
|
556
|
+
prevPos[i3 + 1] = _bodyMat[13]
|
|
557
|
+
prevPos[i3 + 2] = _bodyMat[14]
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Backstop: if a dynamic body's state went non-finite despite the velocity
|
|
562
|
+
// caps, restore its previous-substep pose with zero velocity instead of
|
|
563
|
+
// letting NaNs spread through constraints and contacts. Runs after every
|
|
564
|
+
// substep so prevState is always a finite pose.
|
|
565
|
+
private restoreNonFiniteBodies(): void {
|
|
566
|
+
const N = this.store.count
|
|
567
|
+
const positions = this.store.positions
|
|
568
|
+
const orientations = this.store.orientations
|
|
569
|
+
const lv = this.store.linearVelocities
|
|
570
|
+
const av = this.store.angularVelocities
|
|
571
|
+
const types = this.store.type
|
|
572
|
+
const prevPos = this.prevPositions
|
|
573
|
+
const prevOri = this.prevOrientations
|
|
574
|
+
|
|
575
|
+
for (let i = 0; i < N; i++) {
|
|
576
|
+
if (types[i] !== RigidbodyType.Dynamic) continue
|
|
577
|
+
const i3 = i * 3
|
|
578
|
+
const i4 = i * 4
|
|
579
|
+
// NaN/Inf propagates through the sum, so one check covers all 13 slots.
|
|
580
|
+
const s =
|
|
581
|
+
positions[i3 + 0] + positions[i3 + 1] + positions[i3 + 2] +
|
|
582
|
+
orientations[i4 + 0] + orientations[i4 + 1] + orientations[i4 + 2] + orientations[i4 + 3] +
|
|
583
|
+
lv[i3 + 0] + lv[i3 + 1] + lv[i3 + 2] +
|
|
584
|
+
av[i3 + 0] + av[i3 + 1] + av[i3 + 2]
|
|
585
|
+
if (Number.isFinite(s)) continue
|
|
586
|
+
positions[i3 + 0] = prevPos[i3 + 0]
|
|
587
|
+
positions[i3 + 1] = prevPos[i3 + 1]
|
|
588
|
+
positions[i3 + 2] = prevPos[i3 + 2]
|
|
589
|
+
orientations[i4 + 0] = prevOri[i4 + 0]
|
|
590
|
+
orientations[i4 + 1] = prevOri[i4 + 1]
|
|
591
|
+
orientations[i4 + 2] = prevOri[i4 + 2]
|
|
592
|
+
orientations[i4 + 3] = prevOri[i4 + 3]
|
|
593
|
+
lv[i3 + 0] = 0; lv[i3 + 1] = 0; lv[i3 + 2] = 0
|
|
594
|
+
av[i3 + 0] = 0; av[i3 + 1] = 0; av[i3 + 2] = 0
|
|
237
595
|
}
|
|
238
596
|
}
|
|
239
597
|
|
|
@@ -289,7 +647,16 @@ export class RezePhysics {
|
|
|
289
647
|
|
|
290
648
|
// Sanity gate against NaN / extreme values — silently drop the update.
|
|
291
649
|
if (Number.isFinite(_boneMat[0]) && Math.abs(_boneMat[0]) < 1e6) {
|
|
292
|
-
boneWorldMatrices[b].values
|
|
650
|
+
const v = boneWorldMatrices[b].values
|
|
651
|
+
if (this.alignPinned[i]) {
|
|
652
|
+
// Pinned mode-2: the bone keeps its animated position, physics
|
|
653
|
+
// drives rotation only.
|
|
654
|
+
v[0] = _boneMat[0]; v[1] = _boneMat[1]; v[2] = _boneMat[2]
|
|
655
|
+
v[4] = _boneMat[4]; v[5] = _boneMat[5]; v[6] = _boneMat[6]
|
|
656
|
+
v[8] = _boneMat[8]; v[9] = _boneMat[9]; v[10] = _boneMat[10]
|
|
657
|
+
} else {
|
|
658
|
+
v.set(_boneMat)
|
|
659
|
+
}
|
|
293
660
|
}
|
|
294
661
|
}
|
|
295
662
|
}
|