reze-engine 0.16.2 → 0.17.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/dist/engine.d.ts +3 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +165 -26
- package/dist/model.d.ts +5 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +14 -1
- package/dist/physics/body.d.ts +4 -1
- package/dist/physics/body.d.ts.map +1 -1
- package/dist/physics/body.js +86 -20
- package/dist/physics/constraint.d.ts +15 -2
- package/dist/physics/constraint.d.ts.map +1 -1
- package/dist/physics/constraint.js +49 -2
- package/dist/physics/physics.d.ts +3 -0
- package/dist/physics/physics.d.ts.map +1 -1
- package/dist/physics/physics.js +69 -1
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +289 -113
- 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 +19 -3
- package/dist/pmx-loader.d.ts +2 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +38 -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 +137 -112
- 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 +54 -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/mmd_classic.d.ts +2 -0
- package/dist/shaders/materials/mmd_classic.d.ts.map +1 -0
- package/dist/shaders/materials/mmd_classic.js +66 -0
- 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/package.json +1 -1
- package/src/engine.ts +4198 -4030
- package/src/model.ts +21 -1
- package/src/physics/body.ts +87 -24
- package/src/physics/constraint.ts +80 -6
- package/src/physics/physics.ts +68 -1
- package/src/physics/solver.ts +915 -744
- package/src/physics/types.ts +10 -2
- package/src/physics/world.ts +20 -3
- package/src/pmx-loader.ts +39 -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 +204 -179
- package/src/shaders/materials/default.ts +45 -40
- package/src/shaders/materials/eye.ts +62 -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/mmd_classic.ts +68 -0
- package/src/shaders/materials/stockings.ts +1 -1
package/src/model.ts
CHANGED
|
@@ -43,6 +43,9 @@ export interface Material {
|
|
|
43
43
|
sphereTextureIndex: number
|
|
44
44
|
sphereMode: number
|
|
45
45
|
toonTextureIndex: number
|
|
46
|
+
// True when toonTextureIndex refers to the shared toon set (toon01–10)
|
|
47
|
+
// instead of the model's texture table.
|
|
48
|
+
sharedToon: boolean
|
|
46
49
|
edgeFlag: number
|
|
47
50
|
edgeColor: [number, number, number, number]
|
|
48
51
|
edgeSize: number
|
|
@@ -235,6 +238,9 @@ export class Model {
|
|
|
235
238
|
private rigidbodies: Rigidbody[] = []
|
|
236
239
|
private joints: Joint[] = []
|
|
237
240
|
|
|
241
|
+
// Non-fatal problems collected while parsing the PMX (see PmxLoader.warn).
|
|
242
|
+
private loadWarnings: string[] = []
|
|
243
|
+
|
|
238
244
|
// Runtime skeleton pose state (updated each frame)
|
|
239
245
|
private runtimeSkeleton!: SkeletonRuntime
|
|
240
246
|
|
|
@@ -293,7 +299,8 @@ export class Model {
|
|
|
293
299
|
skinning: Skinning,
|
|
294
300
|
morphing: Morphing,
|
|
295
301
|
rigidbodies: Rigidbody[] = [],
|
|
296
|
-
joints: Joint[] = []
|
|
302
|
+
joints: Joint[] = [],
|
|
303
|
+
loadWarnings: string[] = []
|
|
297
304
|
) {
|
|
298
305
|
// Store base vertex data (original positions before morphing)
|
|
299
306
|
this.baseVertexData = new Float32Array(vertexData)
|
|
@@ -307,6 +314,7 @@ export class Model {
|
|
|
307
314
|
this.morphing = morphing
|
|
308
315
|
this.rigidbodies = rigidbodies
|
|
309
316
|
this.joints = joints
|
|
317
|
+
this.loadWarnings = loadWarnings
|
|
310
318
|
|
|
311
319
|
if (this.skeleton.bones.length == 0) {
|
|
312
320
|
throw new Error("Model has no bones")
|
|
@@ -631,6 +639,18 @@ export class Model {
|
|
|
631
639
|
return this.skinning
|
|
632
640
|
}
|
|
633
641
|
|
|
642
|
+
// True when the PMX carried a usable rigidbody section. False means the
|
|
643
|
+
// model renders but has no physics — surface this in the UI instead of
|
|
644
|
+
// letting it read as "physics silently broken".
|
|
645
|
+
hasPhysicsData(): boolean {
|
|
646
|
+
return this.rigidbodies.length > 0
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Non-fatal PMX parse problems (truncated sections, suspicious counts…).
|
|
650
|
+
getLoadWarnings(): readonly string[] {
|
|
651
|
+
return this.loadWarnings
|
|
652
|
+
}
|
|
653
|
+
|
|
634
654
|
getRigidbodies(): Rigidbody[] {
|
|
635
655
|
return this.rigidbodies
|
|
636
656
|
}
|
package/src/physics/body.ts
CHANGED
|
@@ -12,13 +12,20 @@ export class RigidBodyStore {
|
|
|
12
12
|
readonly angularVelocities: Float32Array // 3*N
|
|
13
13
|
|
|
14
14
|
readonly invMass: Float32Array // N (0 for static / kinematic)
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
15
|
+
// Full anisotropic inertia. Local diagonal (body frame, Bullet's shape
|
|
16
|
+
// formulas) plus the per-substep world tensor I⁻¹ = R·diag·Rᵀ (9 floats
|
|
17
|
+
// row-major, symmetric). The old scalar approximation deposited constraint
|
|
18
|
+
// impulses into rotational modes at the wrong rates on elongated capsules
|
|
19
|
+
// (5:1 skirt/hair bodies are ~25× anisotropic), leaving cloth with several
|
|
20
|
+
// times the kinetic energy real Bullet retains — visible as perpetual boil.
|
|
21
|
+
readonly invInertiaLocal: Float32Array // 3*N
|
|
22
|
+
readonly invInertiaWorld: Float32Array // 9*N
|
|
19
23
|
readonly linearDamping: Float32Array
|
|
20
24
|
readonly angularDamping: Float32Array
|
|
21
25
|
readonly type: Uint8Array
|
|
26
|
+
// PMX mode-2 bodies: dynamic, but the bone takes rotation only and the
|
|
27
|
+
// body position re-pins to the animated bone each frame.
|
|
28
|
+
readonly aligned: Uint8Array
|
|
22
29
|
readonly boneIndex: Int32Array
|
|
23
30
|
readonly friction: Float32Array
|
|
24
31
|
readonly restitution: Float32Array
|
|
@@ -55,10 +62,12 @@ export class RigidBodyStore {
|
|
|
55
62
|
this.linearVelocities = new Float32Array(N * 3)
|
|
56
63
|
this.angularVelocities = new Float32Array(N * 3)
|
|
57
64
|
this.invMass = new Float32Array(N)
|
|
58
|
-
this.
|
|
65
|
+
this.invInertiaLocal = new Float32Array(N * 3)
|
|
66
|
+
this.invInertiaWorld = new Float32Array(N * 9)
|
|
59
67
|
this.linearDamping = new Float32Array(N)
|
|
60
68
|
this.angularDamping = new Float32Array(N)
|
|
61
69
|
this.type = new Uint8Array(N)
|
|
70
|
+
this.aligned = new Uint8Array(N)
|
|
62
71
|
this.boneIndex = new Int32Array(N)
|
|
63
72
|
this.bodyOffsetMatrix = new Float32Array(N * 16)
|
|
64
73
|
this.bodyOffsetInverse = new Float32Array(N * 16)
|
|
@@ -88,10 +97,11 @@ export class RigidBodyStore {
|
|
|
88
97
|
|
|
89
98
|
const dynamic = rb.type === RigidbodyType.Dynamic && rb.mass > 0
|
|
90
99
|
this.invMass[i] = dynamic ? 1 / rb.mass : 0
|
|
91
|
-
|
|
100
|
+
if (dynamic) computeLocalInvInertia(rb, this.invInertiaLocal, i * 3)
|
|
92
101
|
this.linearDamping[i] = rb.linearDamping
|
|
93
102
|
this.angularDamping[i] = rb.angularDamping
|
|
94
103
|
this.type[i] = rb.type
|
|
104
|
+
this.aligned[i] = rb.aligned ? 1 : 0
|
|
95
105
|
this.boneIndex[i] = rb.boneIndex
|
|
96
106
|
this.friction[i] = rb.friction
|
|
97
107
|
this.restitution[i] = rb.restitution
|
|
@@ -104,6 +114,47 @@ export class RigidBodyStore {
|
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
116
|
|
|
117
|
+
// Refresh I⁻¹_world = R·diag(invInertiaLocal)·Rᵀ for every dynamic body.
|
|
118
|
+
// Called once per substep before constraint setup (orientations are
|
|
119
|
+
// constant during a solve).
|
|
120
|
+
updateInvInertiaWorld(): void {
|
|
121
|
+
const N = this.count
|
|
122
|
+
const ori = this.orientations
|
|
123
|
+
const local = this.invInertiaLocal
|
|
124
|
+
const W = this.invInertiaWorld
|
|
125
|
+
const invMass = this.invMass
|
|
126
|
+
|
|
127
|
+
for (let i = 0; i < N; i++) {
|
|
128
|
+
if (invMass[i] <= 0) continue
|
|
129
|
+
const i3 = i * 3
|
|
130
|
+
const i4 = i * 4
|
|
131
|
+
const i9 = i * 9
|
|
132
|
+
const qx = ori[i4 + 0], qy = ori[i4 + 1], qz = ori[i4 + 2], qw = ori[i4 + 3]
|
|
133
|
+
const x2 = qx + qx, y2 = qy + qy, z2 = qz + qz
|
|
134
|
+
const xx = qx * x2, yy = qy * y2, zz = qz * z2
|
|
135
|
+
const xy = qx * y2, xz = qx * z2, yz = qy * z2
|
|
136
|
+
const wx = qw * x2, wy = qw * y2, wz = qw * z2
|
|
137
|
+
// R columns (column-major rotation matrix)
|
|
138
|
+
const r00 = 1 - (yy + zz), r01 = xy - wz, r02 = xz + wy
|
|
139
|
+
const r10 = xy + wz, r11 = 1 - (xx + zz), r12 = yz - wx
|
|
140
|
+
const r20 = xz - wy, r21 = yz + wx, r22 = 1 - (xx + yy)
|
|
141
|
+
const d0 = local[i3 + 0], d1 = local[i3 + 1], d2 = local[i3 + 2]
|
|
142
|
+
// W = R·diag·Rᵀ (symmetric)
|
|
143
|
+
const a0 = r00 * d0, a1 = r01 * d1, a2 = r02 * d2
|
|
144
|
+
const b0 = r10 * d0, b1 = r11 * d1, b2 = r12 * d2
|
|
145
|
+
const c0 = r20 * d0, c1 = r21 * d1, c2 = r22 * d2
|
|
146
|
+
const w00 = a0 * r00 + a1 * r01 + a2 * r02
|
|
147
|
+
const w01 = a0 * r10 + a1 * r11 + a2 * r12
|
|
148
|
+
const w02 = a0 * r20 + a1 * r21 + a2 * r22
|
|
149
|
+
const w11 = b0 * r10 + b1 * r11 + b2 * r12
|
|
150
|
+
const w12 = b0 * r20 + b1 * r21 + b2 * r22
|
|
151
|
+
const w22 = c0 * r20 + c1 * r21 + c2 * r22
|
|
152
|
+
W[i9 + 0] = w00; W[i9 + 1] = w01; W[i9 + 2] = w02
|
|
153
|
+
W[i9 + 3] = w01; W[i9 + 4] = w11; W[i9 + 5] = w12
|
|
154
|
+
W[i9 + 6] = w02; W[i9 + 7] = w12; W[i9 + 8] = w22
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
107
158
|
// World-space AABBs for every body. Inflated by margin so contacts stay
|
|
108
159
|
// paired across small velocity jitter without recomputing per iteration.
|
|
109
160
|
updateAabbs(margin = 0.5): void {
|
|
@@ -282,36 +333,48 @@ const _scratchA = new Float32Array(16)
|
|
|
282
333
|
const _scratchB = new Float32Array(16)
|
|
283
334
|
const _scratchC = new Float32Array(16)
|
|
284
335
|
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
|
|
336
|
+
// Diagonal local inverse inertia, matching Bullet's calculateLocalInertia
|
|
337
|
+
// per shape (so behavior tracks Ammo-based MMD engines):
|
|
338
|
+
// Sphere: I = (2/5)·m·r² on every axis
|
|
339
|
+
// Box: Ix = m/12·(ly²+lz²), … with l = full extents (2·half)
|
|
340
|
+
// Capsule: Bullet's bounding-box approximation of the Y-axis capsule
|
|
341
|
+
// (lx = lz = 2r, ly = h + 2r)
|
|
342
|
+
function computeLocalInvInertia(rb: Rigidbody, out: Float32Array, o: number): void {
|
|
290
343
|
const m = rb.mass
|
|
291
|
-
if (m <= 0) return
|
|
292
|
-
let
|
|
344
|
+
if (m <= 0) return
|
|
345
|
+
let Ix: number, Iy: number, Iz: number
|
|
293
346
|
switch (rb.shape) {
|
|
294
347
|
case RigidbodyShape.Sphere: {
|
|
295
|
-
const
|
|
296
|
-
|
|
348
|
+
const I = 0.4 * m * rb.size.x * rb.size.x
|
|
349
|
+
Ix = I; Iy = I; Iz = I
|
|
297
350
|
break
|
|
298
351
|
}
|
|
299
352
|
case RigidbodyShape.Box: {
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
353
|
+
const lx2 = 4 * rb.size.x * rb.size.x
|
|
354
|
+
const ly2 = 4 * rb.size.y * rb.size.y
|
|
355
|
+
const lz2 = 4 * rb.size.z * rb.size.z
|
|
356
|
+
Ix = (m / 12) * (ly2 + lz2)
|
|
357
|
+
Iy = (m / 12) * (lx2 + lz2)
|
|
358
|
+
Iz = (m / 12) * (lx2 + ly2)
|
|
303
359
|
break
|
|
304
360
|
}
|
|
305
361
|
case RigidbodyShape.Capsule: {
|
|
306
|
-
const
|
|
307
|
-
const
|
|
308
|
-
|
|
362
|
+
const lx = 2 * rb.size.x
|
|
363
|
+
const ly = rb.size.y + 2 * rb.size.x
|
|
364
|
+
const lx2 = lx * lx
|
|
365
|
+
const ly2 = ly * ly
|
|
366
|
+
Ix = (m / 12) * (ly2 + lx2)
|
|
367
|
+
Iy = (m / 12) * (lx2 + lx2)
|
|
368
|
+
Iz = (m / 12) * (lx2 + ly2)
|
|
309
369
|
break
|
|
310
370
|
}
|
|
311
|
-
default:
|
|
312
|
-
|
|
371
|
+
default: {
|
|
372
|
+
Ix = m; Iy = m; Iz = m
|
|
373
|
+
}
|
|
313
374
|
}
|
|
314
|
-
|
|
375
|
+
out[o + 0] = Ix > 0 ? 1 / Ix : 0
|
|
376
|
+
out[o + 1] = Iy > 0 ? 1 / Iy : 0
|
|
377
|
+
out[o + 2] = Iz > 0 ? 1 / Iz : 0
|
|
315
378
|
}
|
|
316
379
|
|
|
317
380
|
function identity16(out: Float32Array, offset: number): void {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Mat4 } from "../math"
|
|
2
2
|
import type { Joint, Rigidbody } from "./types"
|
|
3
|
+
import { RigidbodyType } from "./types"
|
|
3
4
|
|
|
4
5
|
// 6DOF spring constraint trimmed to what MMD uses. Connects bodyA and bodyB
|
|
5
6
|
// via local-space anchor frames; at simulate time the world frames are
|
|
@@ -26,6 +27,11 @@ export interface SixDofSpringConstraint {
|
|
|
26
27
|
springEnabled: Uint8Array // length 6
|
|
27
28
|
springStiffness: Float32Array // length 6 (k)
|
|
28
29
|
equilibriumPoint: Float32Array// length 6, baked at setup time
|
|
30
|
+
// True for joints that close a cycle in the joint graph (e.g. the
|
|
31
|
+
// horizontal ring welds of cross-linked skirt lattices). Loop edges get
|
|
32
|
+
// reduced limit-correction rates in the solver — a cycle over-determines
|
|
33
|
+
// positions and full-rate corrections chatter (see LOOP_ERP_SCALE).
|
|
34
|
+
isLoop: boolean
|
|
29
35
|
|
|
30
36
|
// Per-substep cache. Filled by solver's setup pass once before SI iters,
|
|
31
37
|
// read by the velocity-only iter loop. None of these depend on lv/av — only
|
|
@@ -34,8 +40,8 @@ export interface SixDofSpringConstraint {
|
|
|
34
40
|
cacheLeverA: Float32Array // 3: rA = anchor − posA (world-space)
|
|
35
41
|
cacheLeverB: Float32Array // 3
|
|
36
42
|
cacheLinAxes: Float32Array // 9: 3 linear axes × xyz, world-space
|
|
37
|
-
cacheLinCrossA: Float32Array // 9: (rA × ax) per axis
|
|
38
|
-
cacheLinCrossB: Float32Array // 9
|
|
43
|
+
cacheLinCrossA: Float32Array // 9: I⁻¹A·(rA × ax) per axis
|
|
44
|
+
cacheLinCrossB: Float32Array // 9: I⁻¹B·(rB × ax) per axis
|
|
39
45
|
cacheLinJacInv: Float32Array // 3: 1/(im+im+cA²·ii+cB²·ii) per axis
|
|
40
46
|
// Limit rows are unilateral (Bullet-style): the per-substep accumulated
|
|
41
47
|
// impulse is clamped to the corrective sign, so a limit can push a body
|
|
@@ -45,24 +51,54 @@ export interface SixDofSpringConstraint {
|
|
|
45
51
|
cacheLinActive: Uint8Array // 3
|
|
46
52
|
cacheLinLimitImp: Float32Array // 3: accumulated limit impulse (per substep)
|
|
47
53
|
// Spring rows are velocity-target drives; setup clamps k to the deadbeat
|
|
48
|
-
// stability bound so they cannot pump energy.
|
|
54
|
+
// stability bound so they cannot pump energy. maxImp bounds the per-substep
|
|
55
|
+
// accumulated impulse by the real spring force (k·|err|·dt) for rows that
|
|
56
|
+
// must stay force-limited (loop-edge welds); Infinity for authored springs.
|
|
49
57
|
cacheLinSpringTarget: Float32Array // 3
|
|
58
|
+
cacheLinSpringMaxImp: Float32Array // 3
|
|
59
|
+
cacheLinSpringImp: Float32Array // 3
|
|
50
60
|
cacheLinSpringActive: Uint8Array // 3
|
|
51
61
|
cacheAngAxes: Float32Array // 9 (spring rows)
|
|
52
62
|
cacheAngTargetVel: Float32Array // 3 (spring rows)
|
|
53
63
|
cacheAngActive: Uint8Array // 3 (spring rows)
|
|
54
|
-
|
|
64
|
+
// Per-axis angular Jacobians and tensor-multiplied axes: with full inertia
|
|
65
|
+
// tensors the effective mass differs per axis, and impulse application
|
|
66
|
+
// needs I⁻¹·axis per body.
|
|
67
|
+
cacheAngJacInv: Float32Array // 3: 1/(axᵀ(I⁻¹A+I⁻¹B)ax) per axis
|
|
68
|
+
// Angular spring force clamp (|k·err|·dt) + per-substep accumulator —
|
|
69
|
+
// unclamped spring velocity-drives pump energy slowly (the restoring
|
|
70
|
+
// magnitude on derived euler axes is orientation-dependent by up to ~25%,
|
|
71
|
+
// and a mis-scaled oscillator restoring force injects per cycle).
|
|
72
|
+
cacheAngSpringMaxImp: Float32Array // 3
|
|
73
|
+
cacheAngSpringImp: Float32Array // 3
|
|
74
|
+
cacheAngWA: Float32Array // 9: I⁻¹A·axis per axis
|
|
75
|
+
cacheAngWB: Float32Array // 9
|
|
55
76
|
// Single geodesic limit row: shortest rotation from the current relative
|
|
56
77
|
// orientation to the euler-clamped target. Per-axis euler limit rows are
|
|
57
78
|
// geometrically inconsistent for large violations (asin singularity) and
|
|
58
79
|
// pump energy instead of converging. Unilateral like the linear limits.
|
|
59
80
|
cacheAngLimAxis: Float32Array // 3, world-space unit axis
|
|
81
|
+
cacheAngLimWA: Float32Array // 3: I⁻¹A·axis
|
|
82
|
+
cacheAngLimWB: Float32Array // 3
|
|
83
|
+
cacheAngLimJacInv: number
|
|
60
84
|
cacheAngLimTarget: number // target relative angular velocity along axis
|
|
61
85
|
cacheAngLimActive: number
|
|
62
86
|
cacheAngLimImp: number // accumulated impulse (per substep)
|
|
87
|
+
// Per-axis angular limit rows for the small-violation regime. act:
|
|
88
|
+
// 1 = bilateral (locked axis), 2 = unilateral stop (ranged axis) — a
|
|
89
|
+
// bilateral row on a ranged axis brakes natural recovery and pumps
|
|
90
|
+
// energy into swinging cloth.
|
|
91
|
+
cacheAngPATarget: Float32Array // 3
|
|
92
|
+
cacheAngPAActive: Uint8Array // 3
|
|
93
|
+
cacheAngPAImp: Float32Array // 3
|
|
63
94
|
}
|
|
64
95
|
|
|
65
|
-
|
|
96
|
+
// Stop-limit ERP. PMX rigs are tuned against MMD's stiff limit response;
|
|
97
|
+
// lowering this makes cloth resting against its limits sink visibly deeper
|
|
98
|
+
// (equilibrium penetration scales with 1/ERP). Rest chatter at this
|
|
99
|
+
// stiffness was fixed at the source (spring double-drive, unilateral
|
|
100
|
+
// stops) — don't lower ERP to paper over jitter.
|
|
101
|
+
export const STOP_ERP = 0.45
|
|
66
102
|
|
|
67
103
|
// Build per-joint constraints from PMX data:
|
|
68
104
|
// frameA = (bodyA_worldBind)^-1 · jointWorldBind
|
|
@@ -136,6 +172,7 @@ export function buildConstraints(
|
|
|
136
172
|
springEnabled,
|
|
137
173
|
springStiffness,
|
|
138
174
|
equilibriumPoint: new Float32Array(6),
|
|
175
|
+
isLoop: false,
|
|
139
176
|
cacheSkip: false,
|
|
140
177
|
cacheLeverA: new Float32Array(3),
|
|
141
178
|
cacheLeverB: new Float32Array(3),
|
|
@@ -147,18 +184,55 @@ export function buildConstraints(
|
|
|
147
184
|
cacheLinActive: new Uint8Array(3),
|
|
148
185
|
cacheLinLimitImp: new Float32Array(3),
|
|
149
186
|
cacheLinSpringTarget: new Float32Array(3),
|
|
187
|
+
cacheLinSpringMaxImp: new Float32Array(3),
|
|
188
|
+
cacheLinSpringImp: new Float32Array(3),
|
|
150
189
|
cacheLinSpringActive: new Uint8Array(3),
|
|
151
190
|
cacheAngAxes: new Float32Array(9),
|
|
152
191
|
cacheAngTargetVel: new Float32Array(3),
|
|
153
192
|
cacheAngActive: new Uint8Array(3),
|
|
154
|
-
cacheAngJacInv:
|
|
193
|
+
cacheAngJacInv: new Float32Array(3),
|
|
194
|
+
cacheAngSpringMaxImp: new Float32Array(3),
|
|
195
|
+
cacheAngSpringImp: new Float32Array(3),
|
|
196
|
+
cacheAngWA: new Float32Array(9),
|
|
197
|
+
cacheAngWB: new Float32Array(9),
|
|
155
198
|
cacheAngLimAxis: new Float32Array(3),
|
|
199
|
+
cacheAngLimWA: new Float32Array(3),
|
|
200
|
+
cacheAngLimWB: new Float32Array(3),
|
|
201
|
+
cacheAngLimJacInv: 0,
|
|
156
202
|
cacheAngLimTarget: 0,
|
|
157
203
|
cacheAngLimActive: 0,
|
|
158
204
|
cacheAngLimImp: 0,
|
|
205
|
+
cacheAngPATarget: new Float32Array(3),
|
|
206
|
+
cacheAngPAActive: new Uint8Array(3),
|
|
207
|
+
cacheAngPAImp: new Float32Array(3),
|
|
159
208
|
})
|
|
160
209
|
}
|
|
161
210
|
|
|
211
|
+
// Mark loop-closing joints via union-find over the joint graph. All
|
|
212
|
+
// bone-follow bodies count as one "world" component (they're each anchored
|
|
213
|
+
// to the skeleton), so a joint bridging two separately anchored subtrees
|
|
214
|
+
// also closes a kinematic cycle. One edge per cycle gets softened, which
|
|
215
|
+
// releases the over-determination regardless of which edge it is.
|
|
216
|
+
const parent = new Int32Array(rigidbodies.length + 1)
|
|
217
|
+
for (let i = 0; i < parent.length; i++) parent[i] = i
|
|
218
|
+
const find = (x: number): number => {
|
|
219
|
+
while (parent[x] !== x) {
|
|
220
|
+
parent[x] = parent[parent[x]]
|
|
221
|
+
x = parent[x]
|
|
222
|
+
}
|
|
223
|
+
return x
|
|
224
|
+
}
|
|
225
|
+
const world = rigidbodies.length
|
|
226
|
+
for (let i = 0; i < rigidbodies.length; i++) {
|
|
227
|
+
if (rigidbodies[i].type !== RigidbodyType.Dynamic) parent[find(i)] = find(world)
|
|
228
|
+
}
|
|
229
|
+
for (const con of out) {
|
|
230
|
+
const ra = find(con.bodyA)
|
|
231
|
+
const rb = find(con.bodyB)
|
|
232
|
+
if (ra === rb) con.isLoop = true
|
|
233
|
+
else parent[ra] = rb
|
|
234
|
+
}
|
|
235
|
+
|
|
162
236
|
return out
|
|
163
237
|
}
|
|
164
238
|
|
package/src/physics/physics.ts
CHANGED
|
@@ -50,6 +50,15 @@ export class RezePhysics {
|
|
|
50
50
|
private kinRoot: Int32Array
|
|
51
51
|
// Kinematic bodies whose target jumped discontinuously this frame.
|
|
52
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
|
|
53
62
|
|
|
54
63
|
constructor(rigidbodies: Rigidbody[], joints: Joint[] = []) {
|
|
55
64
|
this.rigidbodies = rigidbodies
|
|
@@ -66,6 +75,17 @@ export class RezePhysics {
|
|
|
66
75
|
this.kinTargetAngVel = new Float32Array(this.store.count * 3)
|
|
67
76
|
this.kinRoot = this.buildKinematicRoots()
|
|
68
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
|
+
}
|
|
69
89
|
}
|
|
70
90
|
|
|
71
91
|
// BFS over the joint graph from every kinematic body, assigning each
|
|
@@ -140,6 +160,13 @@ export class RezePhysics {
|
|
|
140
160
|
if (this.firstFrame) return
|
|
141
161
|
this.snapBodiesToBones(boneWorldMatrices)
|
|
142
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)
|
|
143
170
|
this.timeAccum = 0
|
|
144
171
|
}
|
|
145
172
|
|
|
@@ -165,6 +192,7 @@ export class RezePhysics {
|
|
|
165
192
|
// raw derived velocity is what used to explode the solver. Chains under
|
|
166
193
|
// unaffected roots keep their momentum untouched.
|
|
167
194
|
if (this.computeKinematicTargets(boneWorldMatrices, dt)) {
|
|
195
|
+
this.teleportCount++
|
|
168
196
|
this.carryDynamicThroughTeleport()
|
|
169
197
|
this.snapKinematicToTargets(true)
|
|
170
198
|
this.savePrevState() // prev == curr so interpolation doesn't streak
|
|
@@ -197,6 +225,10 @@ export class RezePhysics {
|
|
|
197
225
|
this.snapKinematicToTargets(false)
|
|
198
226
|
}
|
|
199
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)
|
|
231
|
+
|
|
200
232
|
// Fraction into the next (not-yet-taken) step; always in [0, 1).
|
|
201
233
|
const alpha = this.fixedTimeStep > 0 ? this.timeAccum / this.fixedTimeStep : 0
|
|
202
234
|
this.applyDynamicsToBones(boneWorldMatrices, alpha)
|
|
@@ -500,6 +532,32 @@ export class RezePhysics {
|
|
|
500
532
|
}
|
|
501
533
|
}
|
|
502
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
|
+
|
|
503
561
|
// Backstop: if a dynamic body's state went non-finite despite the velocity
|
|
504
562
|
// caps, restore its previous-substep pose with zero velocity instead of
|
|
505
563
|
// letting NaNs spread through constraints and contacts. Runs after every
|
|
@@ -589,7 +647,16 @@ export class RezePhysics {
|
|
|
589
647
|
|
|
590
648
|
// Sanity gate against NaN / extreme values — silently drop the update.
|
|
591
649
|
if (Number.isFinite(_boneMat[0]) && Math.abs(_boneMat[0]) < 1e6) {
|
|
592
|
-
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
|
+
}
|
|
593
660
|
}
|
|
594
661
|
}
|
|
595
662
|
}
|