reze-engine 0.16.1 → 0.16.2

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.
@@ -32,6 +32,25 @@ 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
+
35
54
  constructor(rigidbodies: Rigidbody[], joints: Joint[] = []) {
36
55
  this.rigidbodies = rigidbodies
37
56
  this.joints = joints
@@ -41,6 +60,41 @@ export class RezePhysics {
41
60
  this.contacts = new ContactPool()
42
61
  this.prevPositions = new Float32Array(this.store.count * 3)
43
62
  this.prevOrientations = new Float32Array(this.store.count * 4)
63
+ this.kinTargetPos = new Float32Array(this.store.count * 3)
64
+ this.kinTargetOri = new Float32Array(this.store.count * 4)
65
+ this.kinTargetVel = new Float32Array(this.store.count * 3)
66
+ this.kinTargetAngVel = new Float32Array(this.store.count * 3)
67
+ this.kinRoot = this.buildKinematicRoots()
68
+ this.teleportFlags = new Uint8Array(this.store.count)
69
+ }
70
+
71
+ // BFS over the joint graph from every kinematic body, assigning each
72
+ // reachable dynamic body the kinematic body it (transitively) hangs off.
73
+ private buildKinematicRoots(): Int32Array {
74
+ const N = this.store.count
75
+ const root = new Int32Array(N).fill(-1)
76
+ const types = this.store.type
77
+ const adj: number[][] = Array.from({ length: N }, () => [])
78
+ for (const c of this.constraints) {
79
+ adj[c.bodyA].push(c.bodyB)
80
+ adj[c.bodyB].push(c.bodyA)
81
+ }
82
+ const queue: number[] = []
83
+ for (let i = 0; i < N; i++) {
84
+ if (types[i] === RigidbodyType.Static || types[i] === RigidbodyType.Kinematic) {
85
+ root[i] = i
86
+ queue.push(i)
87
+ }
88
+ }
89
+ for (let h = 0; h < queue.length; h++) {
90
+ const i = queue[h]
91
+ for (const j of adj[i]) {
92
+ if (root[j] !== -1) continue
93
+ root[j] = root[i]
94
+ queue.push(j)
95
+ }
96
+ }
97
+ return root
44
98
  }
45
99
 
46
100
  // Snapshot the current body pose as the interpolation "previous" state.
@@ -96,26 +150,52 @@ export class RezePhysics {
96
150
  // that skip frame 0 don't pop bodies on first step.
97
151
  this.snapBodiesToBones(boneWorldMatrices)
98
152
  this.savePrevState()
153
+ // Seed kinematic targets so the first frame's target-delta velocity
154
+ // is zero instead of a jump from the origin.
155
+ this.kinTargetPos.set(this.store.positions)
156
+ this.kinTargetOri.set(this.store.orientations)
99
157
  this.firstFrame = false
100
158
  }
101
159
 
102
- // Sync once per render frame; kinematic targets don't change between
103
- // substeps. Render dt is used to derive kinematic velocities from the
104
- // bone-pose delta so joints feel the kinematic motion.
105
- this.syncFromBones(boneWorldMatrices, dt)
160
+ // Compute this frame's kinematic targets from the current bone pose. A
161
+ // target jump beyond what continuous motion can produce (timeline scrub)
162
+ // is a teleport, handled per kinematic root: rigidly carry that root's
163
+ // dynamic chains across the jump with zeroed momentum, snap the root,
164
+ // and keep simulating — dragging cloth through a discontinuity at the
165
+ // raw derived velocity is what used to explode the solver. Chains under
166
+ // unaffected roots keep their momentum untouched.
167
+ if (this.computeKinematicTargets(boneWorldMatrices, dt)) {
168
+ this.carryDynamicThroughTeleport()
169
+ this.snapKinematicToTargets(true)
170
+ this.savePrevState() // prev == curr so interpolation doesn't streak
171
+ }
106
172
 
107
173
  // Fixed-timestep substeps. The maxSubSteps cap prevents runaway after
108
174
  // a long stall (tab backgrounded, etc.). Snapshot the pose before each step so
109
175
  // after the loop prevState is one substep behind the live (current) state.
176
+ // Kinematic bodies split the remaining gap evenly across this frame's
177
+ // substeps (f = 1/substeps-left) and land exactly on the frame's bone
178
+ // pose by the last one: at 60 Hz render that reproduces the classic
179
+ // sync-once-per-frame behavior exactly (no fractional lag trembling
180
+ // against the rendered mesh), while at lower rates the per-substep
181
+ // constraint error stays bounded to one fixed step of bone motion.
110
182
  this.timeAccum += dt
111
- let sub = 0
112
- while (this.timeAccum >= this.fixedTimeStep && sub < this.maxSubSteps) {
183
+ let nSub = Math.floor(this.timeAccum / this.fixedTimeStep)
184
+ if (nSub > this.maxSubSteps) nSub = this.maxSubSteps
185
+ for (let k = 0; k < nSub; k++) {
113
186
  this.savePrevState()
187
+ this.advanceKinematicToTargets(1 / (nSub - k))
114
188
  this.world.step(this.store, this.constraints, this.contacts, this.fixedTimeStep)
189
+ this.restoreNonFiniteBodies()
115
190
  this.timeAccum -= this.fixedTimeStep
116
- sub++
117
191
  }
118
- if (sub === this.maxSubSteps) this.timeAccum = 0
192
+ if (this.timeAccum >= this.fixedTimeStep) {
193
+ // Substep budget exhausted mid-catchup: drop the remaining time and
194
+ // snap kinematic bodies the rest of the way (zero velocity) so they
195
+ // don't start next frame lagging behind their bones.
196
+ this.timeAccum = 0
197
+ this.snapKinematicToTargets(false)
198
+ }
119
199
 
120
200
  // Fraction into the next (not-yet-taken) step; always in [0, 1).
121
201
  const alpha = this.fixedTimeStep > 0 ? this.timeAccum / this.fixedTimeStep : 0
@@ -158,22 +238,34 @@ export class RezePhysics {
158
238
  }
159
239
  }
160
240
 
161
- // Pull Static / Kinematic bodies to their bones and derive velocities
162
- // from the bone-pose delta joints attached to fast limbs need to see
163
- // the kinematic motion, not just the position jump, or dependent cloth
164
- // bodies lag behind quick movement.
165
- private syncFromBones(boneWorldMatrices: Mat4[], dt: number): void {
241
+ // Fill kinTargetPos/kinTargetOri = boneWorld × bodyOffset for every bone-
242
+ // bound Static/Kinematic body. Returns true if any target is discontinuous
243
+ // with the current body pose farther than continuous motion can carry it
244
+ // in one render frame, or rotated more than 90°.
245
+ private computeKinematicTargets(boneWorldMatrices: Mat4[], dt: number): boolean {
166
246
  const N = this.store.count
167
247
  const offsets = this.store.bodyOffsetMatrix
168
248
  const positions = this.store.positions
169
249
  const orientations = this.store.orientations
170
- const lv = this.store.linearVelocities
171
- const av = this.store.angularVelocities
172
250
  const types = this.store.type
173
251
  const boneIdx = this.store.boneIndex
252
+ const tp = this.kinTargetPos
253
+ const to = this.kinTargetOri
254
+
255
+ // 250 units/s scaled by frame time, floored at 4 units: far above the
256
+ // fastest limb motion (a false positive freezes that chain's momentum
257
+ // for a frame, which reads as stutter), far below any real scrub jump.
258
+ const maxJump = Math.max(4, 250 * dt)
259
+ const maxJumpSq = maxJump * maxJump
260
+ const flags = this.teleportFlags
261
+ let teleport = false
262
+
263
+ const tv = this.kinTargetVel
264
+ const tav = this.kinTargetAngVel
174
265
  const invDt = dt > 0 ? 1 / dt : 0
175
266
 
176
267
  for (let i = 0; i < N; i++) {
268
+ flags[i] = 0
177
269
  const t = types[i]
178
270
  if (t !== RigidbodyType.Static && t !== RigidbodyType.Kinematic) continue
179
271
  const b = boneIdx[i]
@@ -183,57 +275,265 @@ export class RezePhysics {
183
275
 
184
276
  const i3 = i * 3
185
277
  const i4 = i * 4
278
+ // Previous frame's target — the reference for the trajectory velocity.
279
+ const oldTx = tp[i3 + 0], oldTy = tp[i3 + 1], oldTz = tp[i3 + 2]
280
+ const oldOx = to[i4 + 0], oldOy = to[i4 + 1], oldOz = to[i4 + 2], oldOw = to[i4 + 3]
281
+
282
+ tp[i3 + 0] = _bodyMat[12]
283
+ tp[i3 + 1] = _bodyMat[13]
284
+ tp[i3 + 2] = _bodyMat[14]
285
+ Mat4.toQuatFromArrayInto(_bodyMat, 0, _scratchQuat)
286
+ const nOx = _scratchQuat.x, nOy = _scratchQuat.y, nOz = _scratchQuat.z, nOw = _scratchQuat.w
287
+ to[i4 + 0] = nOx
288
+ to[i4 + 1] = nOy
289
+ to[i4 + 2] = nOz
290
+ to[i4 + 3] = nOw
291
+
292
+ const dx = tp[i3 + 0] - positions[i3 + 0]
293
+ const dy = tp[i3 + 1] - positions[i3 + 1]
294
+ const dz = tp[i3 + 2] - positions[i3 + 2]
295
+ // |q·q'| = cos(θ/2); below cos(45°) the body turned more than 90°.
296
+ const dot =
297
+ to[i4 + 0] * orientations[i4 + 0] +
298
+ to[i4 + 1] * orientations[i4 + 1] +
299
+ to[i4 + 2] * orientations[i4 + 2] +
300
+ to[i4 + 3] * orientations[i4 + 3]
301
+ if (dx * dx + dy * dy + dz * dz > maxJumpSq || Math.abs(dot) < 0.7071) {
302
+ flags[i] = 1
303
+ teleport = true
304
+ tv[i3 + 0] = 0; tv[i3 + 1] = 0; tv[i3 + 2] = 0
305
+ tav[i3 + 0] = 0; tav[i3 + 1] = 0; tav[i3 + 2] = 0
306
+ continue
307
+ }
308
+
309
+ tv[i3 + 0] = (tp[i3 + 0] - oldTx) * invDt
310
+ tv[i3 + 1] = (tp[i3 + 1] - oldTy) * invDt
311
+ tv[i3 + 2] = (tp[i3 + 2] - oldTz) * invDt
312
+ // ω ≈ 2 · qDiff.xyz / dt with qDiff = qNew · conj(qOld). Shortest-arc
313
+ // sign keeps qDiff and −qDiff (same rotation) from doubling ω.
314
+ const cox = -oldOx, coy = -oldOy, coz = -oldOz, cow = oldOw
315
+ const qdx = nOw * cox + nOx * cow + nOy * coz - nOz * coy
316
+ const qdy = nOw * coy - nOx * coz + nOy * cow + nOz * cox
317
+ const qdz = nOw * coz + nOx * coy - nOy * cox + nOz * cow
318
+ const qdw = nOw * cow - nOx * cox - nOy * coy - nOz * coz
319
+ const sign = qdw < 0 ? -1 : 1
320
+ tav[i3 + 0] = 2 * sign * qdx * invDt
321
+ tav[i3 + 1] = 2 * sign * qdy * invDt
322
+ tav[i3 + 2] = 2 * sign * qdz * invDt
323
+ }
324
+ return teleport
325
+ }
326
+
327
+ // Move kinematic bodies fraction f of the way to the frame target with the
328
+ // target-trajectory velocity, so joints see continuous anchor motion (and a
329
+ // smooth velocity signal) instead of a frame-sized jump on substep 1.
330
+ private advanceKinematicToTargets(f: number): void {
331
+ const N = this.store.count
332
+ const positions = this.store.positions
333
+ const orientations = this.store.orientations
334
+ const lv = this.store.linearVelocities
335
+ const av = this.store.angularVelocities
336
+ const types = this.store.type
337
+ const boneIdx = this.store.boneIndex
338
+ const tp = this.kinTargetPos
339
+ const to = this.kinTargetOri
340
+ const tv = this.kinTargetVel
341
+ const tav = this.kinTargetAngVel
186
342
 
187
- // Save previous transform for the velocity diff. invDt = 0 (first
188
- // frame / reset) skips the diff and zeros velocities.
189
- const oldPx = positions[i3 + 0],
190
- oldPy = positions[i3 + 1],
191
- oldPz = positions[i3 + 2]
343
+ for (let i = 0; i < N; i++) {
344
+ const t = types[i]
345
+ if (t !== RigidbodyType.Static && t !== RigidbodyType.Kinematic) continue
346
+ const b = boneIdx[i]
347
+ if (b < 0) continue
348
+
349
+ const i3 = i * 3
350
+ const i4 = i * 4
351
+ positions[i3 + 0] += (tp[i3 + 0] - positions[i3 + 0]) * f
352
+ positions[i3 + 1] += (tp[i3 + 1] - positions[i3 + 1]) * f
353
+ positions[i3 + 2] += (tp[i3 + 2] - positions[i3 + 2]) * f
354
+ lv[i3 + 0] = tv[i3 + 0]
355
+ lv[i3 + 1] = tv[i3 + 1]
356
+ lv[i3 + 2] = tv[i3 + 2]
357
+ av[i3 + 0] = tav[i3 + 0]
358
+ av[i3 + 1] = tav[i3 + 1]
359
+ av[i3 + 2] = tav[i3 + 2]
360
+
361
+ // Shortest-arc nlerp toward the target orientation.
192
362
  const oldOx = orientations[i4 + 0],
193
363
  oldOy = orientations[i4 + 1]
194
364
  const oldOz = orientations[i4 + 2],
195
365
  oldOw = orientations[i4 + 3]
196
-
197
- positions[i3 + 0] = _bodyMat[12]
198
- positions[i3 + 1] = _bodyMat[13]
199
- positions[i3 + 2] = _bodyMat[14]
200
- Mat4.toQuatFromArrayInto(_bodyMat, 0, _scratchQuat)
201
- const newOx = _scratchQuat.x,
202
- newOy = _scratchQuat.y
203
- const newOz = _scratchQuat.z,
204
- newOw = _scratchQuat.w
366
+ let tx = to[i4 + 0],
367
+ ty = to[i4 + 1],
368
+ tz = to[i4 + 2],
369
+ tw = to[i4 + 3]
370
+ if (oldOx * tx + oldOy * ty + oldOz * tz + oldOw * tw < 0) {
371
+ tx = -tx; ty = -ty; tz = -tz; tw = -tw
372
+ }
373
+ let newOx = oldOx + (tx - oldOx) * f
374
+ let newOy = oldOy + (ty - oldOy) * f
375
+ let newOz = oldOz + (tz - oldOz) * f
376
+ let newOw = oldOw + (tw - oldOw) * f
377
+ const len2 = newOx * newOx + newOy * newOy + newOz * newOz + newOw * newOw
378
+ if (len2 > 1e-12) {
379
+ const inv = 1 / Math.sqrt(len2)
380
+ newOx *= inv; newOy *= inv; newOz *= inv; newOw *= inv
381
+ } else {
382
+ newOx = tx; newOy = ty; newOz = tz; newOw = tw
383
+ }
205
384
  orientations[i4 + 0] = newOx
206
385
  orientations[i4 + 1] = newOy
207
386
  orientations[i4 + 2] = newOz
208
387
  orientations[i4 + 3] = newOw
388
+ }
389
+ }
209
390
 
210
- if (invDt === 0) {
211
- lv[i3 + 0] = 0
212
- lv[i3 + 1] = 0
213
- lv[i3 + 2] = 0
214
- av[i3 + 0] = 0
215
- av[i3 + 1] = 0
216
- av[i3 + 2] = 0
217
- } else {
218
- lv[i3 + 0] = (_bodyMat[12] - oldPx) * invDt
219
- lv[i3 + 1] = (_bodyMat[13] - oldPy) * invDt
220
- lv[i3 + 2] = (_bodyMat[14] - oldPz) * invDt
221
-
222
- // ω ≈ 2 · qDiff.xyz / dt with qDiff = qNew · conj(qOld). Shortest-
223
- // arc sign keeps qDiff and −qDiff (same rotation) from doubling ω.
224
- const cox = -oldOx,
225
- coy = -oldOy,
226
- coz = -oldOz,
227
- cow = oldOw
228
- const dx = newOw * cox + newOx * cow + newOy * coz - newOz * coy
229
- const dy = newOw * coy - newOx * coz + newOy * cow + newOz * cox
230
- const dz = newOw * coz + newOx * coy - newOy * cox + newOz * cow
231
- const dw = newOw * cow - newOx * cox - newOy * coy - newOz * coz
232
- const sign = dw < 0 ? -1 : 1
233
- av[i3 + 0] = 2 * sign * dx * invDt
234
- av[i3 + 1] = 2 * sign * dy * invDt
235
- av[i3 + 2] = 2 * sign * dz * invDt
391
+ // Snap kinematic bodies straight to the frame target with zero velocity.
392
+ // Used on teleports (onlyFlagged) and when the substep budget runs out
393
+ // mid-catchup (all).
394
+ private snapKinematicToTargets(onlyFlagged: boolean): void {
395
+ const N = this.store.count
396
+ const positions = this.store.positions
397
+ const orientations = this.store.orientations
398
+ const lv = this.store.linearVelocities
399
+ const av = this.store.angularVelocities
400
+ const types = this.store.type
401
+ const boneIdx = this.store.boneIndex
402
+ const tp = this.kinTargetPos
403
+ const to = this.kinTargetOri
404
+ const flags = this.teleportFlags
405
+
406
+ for (let i = 0; i < N; i++) {
407
+ const t = types[i]
408
+ if (t !== RigidbodyType.Static && t !== RigidbodyType.Kinematic) continue
409
+ if (boneIdx[i] < 0) continue
410
+ if (onlyFlagged && !flags[i]) continue
411
+ const i3 = i * 3
412
+ const i4 = i * 4
413
+ positions[i3 + 0] = tp[i3 + 0]
414
+ positions[i3 + 1] = tp[i3 + 1]
415
+ positions[i3 + 2] = tp[i3 + 2]
416
+ orientations[i4 + 0] = to[i4 + 0]
417
+ orientations[i4 + 1] = to[i4 + 1]
418
+ orientations[i4 + 2] = to[i4 + 2]
419
+ orientations[i4 + 3] = to[i4 + 3]
420
+ lv[i3 + 0] = 0; lv[i3 + 1] = 0; lv[i3 + 2] = 0
421
+ av[i3 + 0] = 0; av[i3 + 1] = 0; av[i3 + 2] = 0
422
+ }
423
+ }
424
+
425
+ // Rigidly carry each dynamic body whose kinematic root teleported along
426
+ // with that root's current→target transform delta (velocity zeroed),
427
+ // preserving the cloth pose relative to the character across the jump.
428
+ // Must run before snapKinematicToTargets (it reads the pre-snap pose).
429
+ private carryDynamicThroughTeleport(): void {
430
+ const N = this.store.count
431
+ const positions = this.store.positions
432
+ const orientations = this.store.orientations
433
+ const lv = this.store.linearVelocities
434
+ const av = this.store.angularVelocities
435
+ const types = this.store.type
436
+ const tp = this.kinTargetPos
437
+ const to = this.kinTargetOri
438
+ const root = this.kinRoot
439
+ const flags = this.teleportFlags
440
+
441
+ for (let i = 0; i < N; i++) {
442
+ if (types[i] !== RigidbodyType.Dynamic) continue
443
+ const k = root[i]
444
+ if (k < 0 || !flags[k] || this.store.boneIndex[k] < 0) continue
445
+ const k3 = k * 3
446
+ const k4 = k * 4
447
+ // Root delta rotation R = qTarget · conj(qCurrent).
448
+ const cx = -orientations[k4 + 0],
449
+ cy = -orientations[k4 + 1],
450
+ cz = -orientations[k4 + 2],
451
+ cw = orientations[k4 + 3]
452
+ const txq = to[k4 + 0],
453
+ tyq = to[k4 + 1],
454
+ tzq = to[k4 + 2],
455
+ twq = to[k4 + 3]
456
+ const rx = twq * cx + txq * cw + tyq * cz - tzq * cy
457
+ const ry = twq * cy - txq * cz + tyq * cw + tzq * cx
458
+ const rz = twq * cz + txq * cy - tyq * cx + tzq * cw
459
+ const rw = twq * cw - txq * cx - tyq * cy - tzq * cz
460
+
461
+ const i3 = i * 3
462
+ const i4 = i * 4
463
+ // Position: rotate the offset from the root by R, re-anchor at target.
464
+ const ox = positions[i3 + 0] - positions[k3 + 0]
465
+ const oy = positions[i3 + 1] - positions[k3 + 1]
466
+ const oz = positions[i3 + 2] - positions[k3 + 2]
467
+ // v' = v + 2·rw·(r × v) + 2·(r × (r × v))
468
+ const c1x = ry * oz - rz * oy
469
+ const c1y = rz * ox - rx * oz
470
+ const c1z = rx * oy - ry * ox
471
+ const c2x = ry * c1z - rz * c1y
472
+ const c2y = rz * c1x - rx * c1z
473
+ const c2z = rx * c1y - ry * c1x
474
+ positions[i3 + 0] = tp[k3 + 0] + ox + 2 * (rw * c1x + c2x)
475
+ positions[i3 + 1] = tp[k3 + 1] + oy + 2 * (rw * c1y + c2y)
476
+ positions[i3 + 2] = tp[k3 + 2] + oz + 2 * (rw * c1z + c2z)
477
+
478
+ // Orientation: q' = R · q, renormalized.
479
+ const qx = orientations[i4 + 0],
480
+ qy = orientations[i4 + 1],
481
+ qz = orientations[i4 + 2],
482
+ qw = orientations[i4 + 3]
483
+ let nx = rw * qx + rx * qw + ry * qz - rz * qy
484
+ let ny = rw * qy - rx * qz + ry * qw + rz * qx
485
+ let nz = rw * qz + rx * qy - ry * qx + rz * qw
486
+ let nw = rw * qw - rx * qx - ry * qy - rz * qz
487
+ const len2 = nx * nx + ny * ny + nz * nz + nw * nw
488
+ if (len2 > 1e-12) {
489
+ const inv = 1 / Math.sqrt(len2)
490
+ nx *= inv; ny *= inv; nz *= inv; nw *= inv
491
+ orientations[i4 + 0] = nx
492
+ orientations[i4 + 1] = ny
493
+ orientations[i4 + 2] = nz
494
+ orientations[i4 + 3] = nw
236
495
  }
496
+
497
+ // Momentum doesn't carry across a discontinuity.
498
+ lv[i3 + 0] = 0; lv[i3 + 1] = 0; lv[i3 + 2] = 0
499
+ av[i3 + 0] = 0; av[i3 + 1] = 0; av[i3 + 2] = 0
500
+ }
501
+ }
502
+
503
+ // Backstop: if a dynamic body's state went non-finite despite the velocity
504
+ // caps, restore its previous-substep pose with zero velocity instead of
505
+ // letting NaNs spread through constraints and contacts. Runs after every
506
+ // substep so prevState is always a finite pose.
507
+ private restoreNonFiniteBodies(): void {
508
+ const N = this.store.count
509
+ const positions = this.store.positions
510
+ const orientations = this.store.orientations
511
+ const lv = this.store.linearVelocities
512
+ const av = this.store.angularVelocities
513
+ const types = this.store.type
514
+ const prevPos = this.prevPositions
515
+ const prevOri = this.prevOrientations
516
+
517
+ for (let i = 0; i < N; i++) {
518
+ if (types[i] !== RigidbodyType.Dynamic) continue
519
+ const i3 = i * 3
520
+ const i4 = i * 4
521
+ // NaN/Inf propagates through the sum, so one check covers all 13 slots.
522
+ const s =
523
+ positions[i3 + 0] + positions[i3 + 1] + positions[i3 + 2] +
524
+ orientations[i4 + 0] + orientations[i4 + 1] + orientations[i4 + 2] + orientations[i4 + 3] +
525
+ lv[i3 + 0] + lv[i3 + 1] + lv[i3 + 2] +
526
+ av[i3 + 0] + av[i3 + 1] + av[i3 + 2]
527
+ if (Number.isFinite(s)) continue
528
+ positions[i3 + 0] = prevPos[i3 + 0]
529
+ positions[i3 + 1] = prevPos[i3 + 1]
530
+ positions[i3 + 2] = prevPos[i3 + 2]
531
+ orientations[i4 + 0] = prevOri[i4 + 0]
532
+ orientations[i4 + 1] = prevOri[i4 + 1]
533
+ orientations[i4 + 2] = prevOri[i4 + 2]
534
+ orientations[i4 + 3] = prevOri[i4 + 3]
535
+ lv[i3 + 0] = 0; lv[i3 + 1] = 0; lv[i3 + 2] = 0
536
+ av[i3 + 0] = 0; av[i3 + 1] = 0; av[i3 + 2] = 0
237
537
  }
238
538
  }
239
539