reze-engine 0.16.3 → 0.17.1

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 (54) hide show
  1. package/README.md +252 -250
  2. package/dist/engine.d.ts +3 -0
  3. package/dist/engine.d.ts.map +1 -1
  4. package/dist/engine.js +160 -21
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/model.d.ts +1 -0
  8. package/dist/model.d.ts.map +1 -1
  9. package/dist/physics/body.d.ts +3 -1
  10. package/dist/physics/body.d.ts.map +1 -1
  11. package/dist/physics/body.js +84 -20
  12. package/dist/physics/constraint.d.ts +14 -1
  13. package/dist/physics/constraint.d.ts.map +1 -1
  14. package/dist/physics/constraint.js +43 -1
  15. package/dist/physics/solver.d.ts.map +1 -1
  16. package/dist/physics/solver.js +278 -110
  17. package/dist/pmx-loader.d.ts.map +1 -1
  18. package/dist/pmx-loader.js +1 -0
  19. package/dist/shaders/materials/body.d.ts +1 -1
  20. package/dist/shaders/materials/body.d.ts.map +1 -1
  21. package/dist/shaders/materials/cloth_rough.d.ts +1 -1
  22. package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
  23. package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
  24. package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
  25. package/dist/shaders/materials/common.d.ts +1 -1
  26. package/dist/shaders/materials/common.d.ts.map +1 -1
  27. package/dist/shaders/materials/common.js +137 -122
  28. package/dist/shaders/materials/default.d.ts +1 -1
  29. package/dist/shaders/materials/default.d.ts.map +1 -1
  30. package/dist/shaders/materials/eye.d.ts +1 -1
  31. package/dist/shaders/materials/eye.d.ts.map +1 -1
  32. package/dist/shaders/materials/eye.js +14 -0
  33. package/dist/shaders/materials/face.d.ts +1 -1
  34. package/dist/shaders/materials/face.d.ts.map +1 -1
  35. package/dist/shaders/materials/hair.d.ts +1 -1
  36. package/dist/shaders/materials/hair.d.ts.map +1 -1
  37. package/dist/shaders/materials/metal.d.ts +1 -1
  38. package/dist/shaders/materials/metal.d.ts.map +1 -1
  39. package/dist/shaders/materials/mmd_classic.d.ts +2 -0
  40. package/dist/shaders/materials/mmd_classic.d.ts.map +1 -0
  41. package/dist/shaders/materials/mmd_classic.js +66 -0
  42. package/dist/shaders/materials/stockings.d.ts +1 -1
  43. package/dist/shaders/materials/stockings.d.ts.map +1 -1
  44. package/package.json +42 -42
  45. package/src/engine.ts +4204 -4045
  46. package/src/index.ts +29 -28
  47. package/src/model.ts +3 -0
  48. package/src/physics/body.ts +82 -24
  49. package/src/physics/constraint.ts +74 -5
  50. package/src/physics/solver.ts +915 -752
  51. package/src/pmx-loader.ts +1 -0
  52. package/src/shaders/materials/common.ts +204 -189
  53. package/src/shaders/materials/eye.ts +14 -0
  54. package/src/shaders/materials/mmd_classic.ts +68 -0
package/src/index.ts CHANGED
@@ -1,29 +1,30 @@
1
- export {
2
- Engine,
3
- DEFAULT_BLOOM_OPTIONS,
4
- DEFAULT_VIEW_TRANSFORM,
5
- type EngineStats,
6
- type EngineOptions,
7
- type BloomOptions,
8
- type ViewTransformOptions,
9
- type LoadModelFromFilesOptions,
10
- type MaterialPreset,
11
- type MaterialPresetMap,
12
- type GizmoDragEvent,
13
- type GizmoDragCallback,
14
- type GizmoDragKind,
15
- } from "./engine"
16
- export { parsePmxFolderInput, pmxFileAtRelativePath, type PmxFolderInputResult } from "./folder-upload"
17
- export { Model } from "./model"
18
- export { Vec3, Quat, Mat4 } from "./math"
19
- export type {
20
- AnimationClip,
21
- AnimationPlayOptions,
22
- AnimationProgress,
23
- BoneKeyframe,
24
- MorphKeyframe,
25
- BoneInterpolation,
26
- ControlPoint,
27
- } from "./animation"
28
- export { FPS } from "./animation"
1
+ export {
2
+ Engine,
3
+ DEFAULT_BLOOM_OPTIONS,
4
+ DEFAULT_VIEW_TRANSFORM,
5
+ type EngineStats,
6
+ type EngineOptions,
7
+ type BloomOptions,
8
+ type ViewTransformOptions,
9
+ type LoadModelFromFilesOptions,
10
+ type MaterialPreset,
11
+ type ResolvedMaterialPreset,
12
+ type MaterialPresetMap,
13
+ type GizmoDragEvent,
14
+ type GizmoDragCallback,
15
+ type GizmoDragKind,
16
+ } from "./engine"
17
+ export { parsePmxFolderInput, pmxFileAtRelativePath, type PmxFolderInputResult } from "./folder-upload"
18
+ export { Model } from "./model"
19
+ export { Vec3, Quat, Mat4 } from "./math"
20
+ export type {
21
+ AnimationClip,
22
+ AnimationPlayOptions,
23
+ AnimationProgress,
24
+ BoneKeyframe,
25
+ MorphKeyframe,
26
+ BoneInterpolation,
27
+ ControlPoint,
28
+ } from "./animation"
29
+ export { FPS } from "./animation"
29
30
  export { RezePhysics } from "./physics"
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
@@ -12,10 +12,14 @@ export class RigidBodyStore {
12
12
  readonly angularVelocities: Float32Array // 3*N
13
13
 
14
14
  readonly invMass: Float32Array // N (0 for static / kinematic)
15
- // Scalar isotropic inverse inertia. The full 3×3 tensor would be more
16
- // accurate but PMX shapes are roughly compact, and a single I⁻¹ is far
17
- // better than reusing invMass (which under-rotates by 100–1000×).
18
- readonly invInertia: Float32Array
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
@@ -58,7 +62,8 @@ export class RigidBodyStore {
58
62
  this.linearVelocities = new Float32Array(N * 3)
59
63
  this.angularVelocities = new Float32Array(N * 3)
60
64
  this.invMass = new Float32Array(N)
61
- this.invInertia = new Float32Array(N)
65
+ this.invInertiaLocal = new Float32Array(N * 3)
66
+ this.invInertiaWorld = new Float32Array(N * 9)
62
67
  this.linearDamping = new Float32Array(N)
63
68
  this.angularDamping = new Float32Array(N)
64
69
  this.type = new Uint8Array(N)
@@ -92,7 +97,7 @@ export class RigidBodyStore {
92
97
 
93
98
  const dynamic = rb.type === RigidbodyType.Dynamic && rb.mass > 0
94
99
  this.invMass[i] = dynamic ? 1 / rb.mass : 0
95
- this.invInertia[i] = dynamic ? computeInvInertia(rb) : 0
100
+ if (dynamic) computeLocalInvInertia(rb, this.invInertiaLocal, i * 3)
96
101
  this.linearDamping[i] = rb.linearDamping
97
102
  this.angularDamping[i] = rb.angularDamping
98
103
  this.type[i] = rb.type
@@ -109,6 +114,47 @@ export class RigidBodyStore {
109
114
  }
110
115
  }
111
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
+
112
158
  // World-space AABBs for every body. Inflated by margin so contacts stay
113
159
  // paired across small velocity jitter without recomputing per iteration.
114
160
  updateAabbs(margin = 0.5): void {
@@ -287,36 +333,48 @@ const _scratchA = new Float32Array(16)
287
333
  const _scratchB = new Float32Array(16)
288
334
  const _scratchC = new Float32Array(16)
289
335
 
290
- // Scalar isotropic inverse inertia. Returns 0 for static bodies (mass = 0).
291
- // Sphere: I = (2/5)·m·r²
292
- // Box: I = (1/3)·m·max(a,b,c)²
293
- // Capsule: I = (1/12)·(3r² + h²) (cylinder transverse axis)
294
- function computeInvInertia(rb: Rigidbody): number {
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 {
295
343
  const m = rb.mass
296
- if (m <= 0) return 0
297
- let I: number
344
+ if (m <= 0) return
345
+ let Ix: number, Iy: number, Iz: number
298
346
  switch (rb.shape) {
299
347
  case RigidbodyShape.Sphere: {
300
- const r = rb.size.x
301
- I = 0.4 * m * r * r
348
+ const I = 0.4 * m * rb.size.x * rb.size.x
349
+ Ix = I; Iy = I; Iz = I
302
350
  break
303
351
  }
304
352
  case RigidbodyShape.Box: {
305
- // Largest half-extent so the long axis isn't under-rotated.
306
- const a = Math.max(rb.size.x, rb.size.y, rb.size.z)
307
- I = (1 / 3) * m * a * a
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)
308
359
  break
309
360
  }
310
361
  case RigidbodyShape.Capsule: {
311
- const r = rb.size.x
312
- const h = rb.size.y
313
- I = (1 / 12) * m * (3 * r * r + h * h)
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)
314
369
  break
315
370
  }
316
- default:
317
- I = m
371
+ default: {
372
+ Ix = m; Iy = m; Iz = m
373
+ }
318
374
  }
319
- return I > 0 ? 1 / I : 0
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
320
378
  }
321
379
 
322
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,21 +51,46 @@ 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
- cacheAngJacInv: number // 1 same for all angular rows
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;
@@ -141,6 +172,7 @@ export function buildConstraints(
141
172
  springEnabled,
142
173
  springStiffness,
143
174
  equilibriumPoint: new Float32Array(6),
175
+ isLoop: false,
144
176
  cacheSkip: false,
145
177
  cacheLeverA: new Float32Array(3),
146
178
  cacheLeverB: new Float32Array(3),
@@ -152,18 +184,55 @@ export function buildConstraints(
152
184
  cacheLinActive: new Uint8Array(3),
153
185
  cacheLinLimitImp: new Float32Array(3),
154
186
  cacheLinSpringTarget: new Float32Array(3),
187
+ cacheLinSpringMaxImp: new Float32Array(3),
188
+ cacheLinSpringImp: new Float32Array(3),
155
189
  cacheLinSpringActive: new Uint8Array(3),
156
190
  cacheAngAxes: new Float32Array(9),
157
191
  cacheAngTargetVel: new Float32Array(3),
158
192
  cacheAngActive: new Uint8Array(3),
159
- cacheAngJacInv: 0,
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),
160
198
  cacheAngLimAxis: new Float32Array(3),
199
+ cacheAngLimWA: new Float32Array(3),
200
+ cacheAngLimWB: new Float32Array(3),
201
+ cacheAngLimJacInv: 0,
161
202
  cacheAngLimTarget: 0,
162
203
  cacheAngLimActive: 0,
163
204
  cacheAngLimImp: 0,
205
+ cacheAngPATarget: new Float32Array(3),
206
+ cacheAngPAActive: new Uint8Array(3),
207
+ cacheAngPAImp: new Float32Array(3),
164
208
  })
165
209
  }
166
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
+
167
236
  return out
168
237
  }
169
238