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
@@ -24,6 +24,32 @@ const MAX_ANGULAR_CORRECTION_VEL = 30; // rad/s
24
24
  // instead of as a hard velocity snap.
25
25
  const LIMIT_SOFTNESS_LINEAR = 0.7;
26
26
  const LIMIT_SOFTNESS_ANGULAR = 0.5;
27
+ // Bullet spring-motor velocity factor: target velocity = (fps/numIterations)
28
+ // × spring force, with the applied impulse clamped to the real spring force
29
+ // k·|err|·dt. The big target makes the row deliver its full physical force
30
+ // every substep (stiff, MMD-authored hold); the clamp keeps it from ever
31
+ // exceeding it (no energy pumping). Matches btGeneric6DofSpringConstraint
32
+ // with springDamping=1 and the default 10 solver iterations.
33
+ const SPRING_SOLVER_ITERATIONS = 10;
34
+ // Slack on the spring impulse clamp: MMD rigs are authored against Bullet's
35
+ // spring motors, which deliver several times the naive k·err·dt per substep
36
+ // through their solver-row impulse bounds. Bounded slack keeps the hold
37
+ // stiff (breasts/hair rest near the authored pose) while still capping the
38
+ // energy any spring can inject per substep (the slow-boil guard).
39
+ const SPRING_FORCE_SLACK = 4;
40
+ // ERP scale for loop-closing constraints (see buildConstraints: joints that
41
+ // close a cycle in the joint graph, e.g. the horizontal ring welds of
42
+ // cross-linked skirt lattices). A loop over-determines positions — when
43
+ // contacts push the lattice, the ring's errors cannot all reach zero, and
44
+ // full-rate corrections chase each other around the cycle as violent
45
+ // chatter. Loop edges keep shape at a fraction of the correction rate while
46
+ // the spanning-tree chains stay stiff.
47
+ const LOOP_ERP_SCALE = 1.0;
48
+ // Loop-edge LOCKED axes are converted to force-clamped springs instead of
49
+ // weld rows: an equality row on a cycle fights the other cycle edges at any
50
+ // ERP (the velocity system is over-determined too). A spring bounded by its
51
+ // real force k·|err|·dt holds the ring's shape elastically without fighting.
52
+ const LOOP_SPRING_K = 900;
27
53
  // Angular limit violations below this switch to per-axis euler rows; above
28
54
  // it, the single geodesic row takes over (see setupConstraint).
29
55
  const GEODESIC_THRESHOLD = 0.5; // rad
@@ -44,19 +70,21 @@ export function solveConstraints(store, constraints, contacts, dt, iterations) {
44
70
  const lv = store.linearVelocities;
45
71
  const av = store.angularVelocities;
46
72
  const invMass = store.invMass;
47
- const invInertia = store.invInertia;
73
+ // World-space inverse inertia tensors for this substep's poses.
74
+ store.updateInvInertiaWorld();
75
+ const W = store.invInertiaWorld;
48
76
  for (let c = 0; c < constraints.length; c++) {
49
77
  setupConstraint(constraints[c], store, dt, invDt);
50
78
  }
51
79
  for (let ci = 0; ci < contacts.count; ci++) {
52
- setupContactRow(contacts.get(ci), lv, av, invMass, invInertia);
80
+ setupContactRow(contacts.get(ci), lv, av, invMass, W);
53
81
  }
54
82
  for (let iter = 0; iter < iterations; iter++) {
55
83
  for (let c = 0; c < constraints.length; c++) {
56
- iterateConstraint(constraints[c], lv, av, invMass, invInertia);
84
+ iterateConstraint(constraints[c], lv, av, invMass);
57
85
  }
58
86
  for (let ci = 0; ci < contacts.count; ci++) {
59
- iterateContactRow(contacts.get(ci), lv, av, invMass, invInertia);
87
+ iterateContactRow(contacts.get(ci), lv, av, invMass);
60
88
  }
61
89
  }
62
90
  }
@@ -67,11 +95,13 @@ function setupConstraint(con, store, dt, invDt) {
67
95
  const b = con.bodyB;
68
96
  const imA = store.invMass[a];
69
97
  const imB = store.invMass[b];
70
- const iiA = store.invInertia[a];
71
- const iiB = store.invInertia[b];
98
+ const W = store.invInertiaWorld;
99
+ const a9 = a * 9;
100
+ const b9 = b * 9;
72
101
  con.cacheSkip = imA === 0 && imB === 0;
73
102
  if (con.cacheSkip)
74
103
  return;
104
+ const erpScale = con.isLoop ? LOOP_ERP_SCALE : 1.0;
75
105
  buildBodyMat(store, a, _bodyMatA);
76
106
  buildBodyMat(store, b, _bodyMatB);
77
107
  Mat4.multiplyArrays(_bodyMatA, 0, con.frameA, 0, _TA, 0);
@@ -128,15 +158,23 @@ function setupConstraint(con, store, dt, invDt) {
128
158
  const cBx = rBy * axz - rBz * axy;
129
159
  const cBy = rBz * axx - rBx * axz;
130
160
  const cBz = rBx * axy - rBy * axx;
131
- cA[o + 0] = cAx;
132
- cA[o + 1] = cAy;
133
- cA[o + 2] = cAz;
134
- cB[o + 0] = cBx;
135
- cB[o + 1] = cBy;
136
- cB[o + 2] = cBz;
161
+ // Tensor-multiplied lever crosses: cache I⁻¹·(r×ax) for application;
162
+ // denominator = (r×ax)ᵀ·I⁻¹·(r×ax).
163
+ const wAx = W[a9 + 0] * cAx + W[a9 + 1] * cAy + W[a9 + 2] * cAz;
164
+ const wAy = W[a9 + 3] * cAx + W[a9 + 4] * cAy + W[a9 + 5] * cAz;
165
+ const wAz = W[a9 + 6] * cAx + W[a9 + 7] * cAy + W[a9 + 8] * cAz;
166
+ const wBx = W[b9 + 0] * cBx + W[b9 + 1] * cBy + W[b9 + 2] * cBz;
167
+ const wBy = W[b9 + 3] * cBx + W[b9 + 4] * cBy + W[b9 + 5] * cBz;
168
+ const wBz = W[b9 + 6] * cBx + W[b9 + 7] * cBy + W[b9 + 8] * cBz;
169
+ cA[o + 0] = wAx;
170
+ cA[o + 1] = wAy;
171
+ cA[o + 2] = wAz;
172
+ cB[o + 0] = wBx;
173
+ cB[o + 1] = wBy;
174
+ cB[o + 2] = wBz;
137
175
  const denom = imA + imB +
138
- (cAx * cAx + cAy * cAy + cAz * cAz) * iiA +
139
- (cBx * cBx + cBy * cBy + cBz * cBz) * iiB;
176
+ (cAx * wAx + cAy * wAy + cAz * wAz) +
177
+ (cBx * wBx + cBy * wBy + cBz * wBz);
140
178
  jac[i] = denom > 0 ? 1 / denom : 0;
141
179
  const lo = con.linearMin[i];
142
180
  const hi = con.linearMax[i];
@@ -156,7 +194,7 @@ function setupConstraint(con, store, dt, invDt) {
156
194
  else if (err !== 0)
157
195
  active = 2;
158
196
  if (err !== 0) {
159
- target = -err * STOP_ERP * invDt;
197
+ target = -err * STOP_ERP * erpScale * invDt;
160
198
  if (target > MAX_LINEAR_CORRECTION_VEL)
161
199
  target = MAX_LINEAR_CORRECTION_VEL;
162
200
  else if (target < -MAX_LINEAR_CORRECTION_VEL)
@@ -177,9 +215,14 @@ function setupConstraint(con, store, dt, invDt) {
177
215
  const k = Math.min(con.springStiffness[i], invDt * invDt);
178
216
  const serr = curr - con.equilibriumPoint[i];
179
217
  con.cacheLinSpringTarget[i] = -k * serr * dt;
218
+ // Boil guard: per-substep spring impulse bounded by (a slack over) the
219
+ // physical spring force — unbounded spring drives pump energy through
220
+ // the euler-axis mis-scale and slowly boil resting cloth.
221
+ con.cacheLinSpringMaxImp[i] = SPRING_FORCE_SLACK * Math.abs(k * serr) * dt;
222
+ con.cacheLinSpringImp[i] = 0;
180
223
  con.cacheLinSpringActive[i] = 1;
181
224
  }
182
- else {
225
+ else if (!(lo === hi && con.isLoop)) {
183
226
  con.cacheLinSpringActive[i] = 0;
184
227
  }
185
228
  }
@@ -234,8 +277,29 @@ function setupConstraint(con, store, dt, invDt) {
234
277
  angAxes[6] = zx;
235
278
  angAxes[7] = zy;
236
279
  angAxes[8] = zz;
237
- const angDenom = iiA + iiB;
238
- con.cacheAngJacInv = angDenom > 0 ? 1 / angDenom : 0;
280
+ // Per-axis angular Jacobians with the full tensors: cache I⁻¹·axis per
281
+ // body plus 1/(axᵀ(I⁻¹A+I⁻¹B)ax) per axis.
282
+ const angJac = con.cacheAngJacInv;
283
+ const angWAs = con.cacheAngWA;
284
+ const angWBs = con.cacheAngWB;
285
+ for (let i = 0; i < 3; i++) {
286
+ const o = i * 3;
287
+ const axx = angAxes[o + 0], axy = angAxes[o + 1], axz = angAxes[o + 2];
288
+ const wAx = W[a9 + 0] * axx + W[a9 + 1] * axy + W[a9 + 2] * axz;
289
+ const wAy = W[a9 + 3] * axx + W[a9 + 4] * axy + W[a9 + 5] * axz;
290
+ const wAz = W[a9 + 6] * axx + W[a9 + 7] * axy + W[a9 + 8] * axz;
291
+ const wBx = W[b9 + 0] * axx + W[b9 + 1] * axy + W[b9 + 2] * axz;
292
+ const wBy = W[b9 + 3] * axx + W[b9 + 4] * axy + W[b9 + 5] * axz;
293
+ const wBz = W[b9 + 6] * axx + W[b9 + 7] * axy + W[b9 + 8] * axz;
294
+ angWAs[o + 0] = wAx;
295
+ angWAs[o + 1] = wAy;
296
+ angWAs[o + 2] = wAz;
297
+ angWBs[o + 0] = wBx;
298
+ angWBs[o + 1] = wBy;
299
+ angWBs[o + 2] = wBz;
300
+ const denom = axx * (wAx + wBx) + axy * (wAy + wBy) + axz * (wAz + wBz);
301
+ angJac[i] = denom > 0 ? 1 / denom : 0;
302
+ }
239
303
  // Per-axis rows carry only the springs. Sign flip vs linear:
240
304
  // d(angDiff)/dt = −(ω_B − ω_A)·ax.
241
305
  const angTgt = con.cacheAngTargetVel;
@@ -244,17 +308,18 @@ function setupConstraint(con, store, dt, invDt) {
244
308
  const idx = i + 3;
245
309
  // Springs on locked axes are skipped — the limit row welds those, and
246
310
  // double-driving a DOF overshoots every iteration (see the linear loop).
247
- if (con.springEnabled[idx] && angDenom > 0 && con.angularMin[i] !== con.angularMax[i]) {
248
- // Same deadbeat clamp as the linear springs.
311
+ if (con.springEnabled[idx] && angJac[i] > 0 && con.angularMin[i] !== con.angularMax[i]) {
249
312
  const k = Math.min(con.springStiffness[idx], invDt * invDt);
250
313
  const serr = _angDiffScratch[i] - con.equilibriumPoint[idx];
251
314
  angTgt[i] = k * serr * dt;
315
+ con.cacheAngSpringMaxImp[i] = SPRING_FORCE_SLACK * Math.abs(k * serr) * dt;
252
316
  angAct[i] = 1;
253
317
  }
254
318
  else {
255
319
  angTgt[i] = 0;
256
320
  angAct[i] = 0;
257
321
  }
322
+ con.cacheAngSpringImp[i] = 0;
258
323
  }
259
324
  // Angular limit handling is hybrid. Small violations (the resting-cloth
260
325
  // regime) use per-axis euler rows — they converge cleanly and keep resting
@@ -264,7 +329,10 @@ function setupConstraint(con, store, dt, invDt) {
264
329
  // the asin singularity they chase phantom errors and pump angular velocity
265
330
  // into the chain instead of converging.
266
331
  con.cacheAngLimActive = 0;
267
- if (angDenom > 0) {
332
+ con.cacheAngPAActive[0] = 0;
333
+ con.cacheAngPAActive[1] = 0;
334
+ con.cacheAngPAActive[2] = 0;
335
+ if (imA > 0 || imB > 0) {
268
336
  const ex = _angDiffScratch[0], ey = _angDiffScratch[1], ez = _angDiffScratch[2];
269
337
  // Free axes (min > max) follow the current angle, i.e. no correction.
270
338
  let tx = ex, ty = ey, tz = ez;
@@ -277,19 +345,25 @@ function setupConstraint(con, store, dt, invDt) {
277
345
  const errX = ex - tx, errY = ey - ty, errZ = ez - tz;
278
346
  const maxErr = Math.max(Math.abs(errX), Math.abs(errY), Math.abs(errZ));
279
347
  if (maxErr > 0 && maxErr < GEODESIC_THRESHOLD) {
280
- // Per-axis euler limit rows, folded into the per-axis row set (the
281
- // spring impulse clamp must not bound a limit row lift it).
348
+ // Per-axis euler limit rows. Locked axes are bilateral joints; ranged
349
+ // axes are unilateral stops (sign-clamped accumulation)a bilateral
350
+ // row on a ranged axis brakes natural recovery every substep and
351
+ // pumps energy into swinging cloth, and the pump grows WITH solver
352
+ // convergence (more iterations enforce the brake harder).
282
353
  for (let i = 0; i < 3; i++) {
283
354
  const err = i === 0 ? errX : i === 1 ? errY : errZ;
284
- if (err === 0)
355
+ con.cacheAngPAImp[i] = 0;
356
+ if (err === 0) {
357
+ con.cacheAngPAActive[i] = 0;
285
358
  continue;
286
- let target = err * STOP_ERP * invDt;
359
+ }
360
+ let target = err * STOP_ERP * erpScale * invDt;
287
361
  if (target > MAX_ANGULAR_CORRECTION_VEL)
288
362
  target = MAX_ANGULAR_CORRECTION_VEL;
289
363
  else if (target < -MAX_ANGULAR_CORRECTION_VEL)
290
364
  target = -MAX_ANGULAR_CORRECTION_VEL;
291
- angTgt[i] += target;
292
- angAct[i] = 1;
365
+ con.cacheAngPATarget[i] = target;
366
+ con.cacheAngPAActive[i] = con.angularMin[i] === con.angularMax[i] ? 1 : 2;
293
367
  }
294
368
  }
295
369
  else if (maxErr > 0) {
@@ -326,7 +400,17 @@ function setupConstraint(con, store, dt, invDt) {
326
400
  lim[0] = _TA[0] * axx + _TA[4] * axy + _TA[8] * axz;
327
401
  lim[1] = _TA[1] * axx + _TA[5] * axy + _TA[9] * axz;
328
402
  lim[2] = _TA[2] * axx + _TA[6] * axy + _TA[10] * axz;
329
- let target = angle * STOP_ERP * invDt;
403
+ const gWA = con.cacheAngLimWA;
404
+ const gWB = con.cacheAngLimWB;
405
+ gWA[0] = W[a9 + 0] * lim[0] + W[a9 + 1] * lim[1] + W[a9 + 2] * lim[2];
406
+ gWA[1] = W[a9 + 3] * lim[0] + W[a9 + 4] * lim[1] + W[a9 + 5] * lim[2];
407
+ gWA[2] = W[a9 + 6] * lim[0] + W[a9 + 7] * lim[1] + W[a9 + 8] * lim[2];
408
+ gWB[0] = W[b9 + 0] * lim[0] + W[b9 + 1] * lim[1] + W[b9 + 2] * lim[2];
409
+ gWB[1] = W[b9 + 3] * lim[0] + W[b9 + 4] * lim[1] + W[b9 + 5] * lim[2];
410
+ gWB[2] = W[b9 + 6] * lim[0] + W[b9 + 7] * lim[1] + W[b9 + 8] * lim[2];
411
+ const gDenom = lim[0] * (gWA[0] + gWB[0]) + lim[1] * (gWA[1] + gWB[1]) + lim[2] * (gWA[2] + gWB[2]);
412
+ con.cacheAngLimJacInv = gDenom > 0 ? 1 / gDenom : 0;
413
+ let target = angle * STOP_ERP * erpScale * invDt;
330
414
  if (target > MAX_ANGULAR_CORRECTION_VEL)
331
415
  target = MAX_ANGULAR_CORRECTION_VEL;
332
416
  con.cacheAngLimTarget = target;
@@ -337,7 +421,7 @@ function setupConstraint(con, store, dt, invDt) {
337
421
  con.cacheAngLimImp = 0;
338
422
  }
339
423
  // ITER: read cache, compute relVel from current lv/av, apply impulse.
340
- function iterateConstraint(con, lv, av, invMass, invInertia) {
424
+ function iterateConstraint(con, lv, av, invMass) {
341
425
  if (con.cacheSkip)
342
426
  return;
343
427
  const a = con.bodyA;
@@ -346,8 +430,6 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
346
430
  const bi = b * 3;
347
431
  const imA = invMass[a];
348
432
  const imB = invMass[b];
349
- const iiA = invInertia[a];
350
- const iiB = invInertia[b];
351
433
  // Linear axes — relVel at the offset point: v_pivot = v_CG + ω × r.
352
434
  const lA = con.cacheLeverA;
353
435
  const lB = con.cacheLeverB;
@@ -370,6 +452,8 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
370
452
  const dvz = vBz - vAz;
371
453
  const sprAct = con.cacheLinSpringActive;
372
454
  const sprTgt = con.cacheLinSpringTarget;
455
+ const sprMax = con.cacheLinSpringMaxImp;
456
+ const sprImp = con.cacheLinSpringImp;
373
457
  const limImp = con.cacheLinLimitImp;
374
458
  for (let i = 0; i < 3; i++) {
375
459
  if (!act[i] && !sprAct[i])
@@ -397,12 +481,25 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
397
481
  j += dImp;
398
482
  }
399
483
  // Spring row: velocity-target drive; the deadbeat k-clamp at setup
400
- // bounds its aggression. relVel is refreshed with the limit impulse
401
- // applied just above (j·denom = j / jac) driving the spring off the
402
- // stale value double-corrects the DOF and overshoots every iteration.
484
+ // bounds its aggression, and maxImp bounds loop-edge springs by their
485
+ // real force. relVel is refreshed with the limit impulse applied just
486
+ // above (j·denom = j / jac) — driving the spring off the stale value
487
+ // double-corrects the DOF and overshoots every iteration.
403
488
  if (sprAct[i]) {
404
489
  const relVelNow = j !== 0 ? relVel + j / jac[i] : relVel;
405
- j += (sprTgt[i] - relVelNow) * jac[i];
490
+ let dImp = (sprTgt[i] - relVelNow) * jac[i];
491
+ const max = sprMax[i];
492
+ if (max !== Infinity) {
493
+ const old = sprImp[i];
494
+ let next = old + dImp;
495
+ if (next > max)
496
+ next = max;
497
+ else if (next < -max)
498
+ next = -max;
499
+ dImp = next - old;
500
+ sprImp[i] = next;
501
+ }
502
+ j += dImp;
406
503
  }
407
504
  if (j === 0)
408
505
  continue;
@@ -410,49 +507,102 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
410
507
  lv[ai + 0] -= j * imA * axx;
411
508
  lv[ai + 1] -= j * imA * axy;
412
509
  lv[ai + 2] -= j * imA * axz;
413
- av[ai + 0] -= j * iiA * cA[o + 0];
414
- av[ai + 1] -= j * iiA * cA[o + 1];
415
- av[ai + 2] -= j * iiA * cA[o + 2];
510
+ av[ai + 0] -= j * cA[o + 0];
511
+ av[ai + 1] -= j * cA[o + 1];
512
+ av[ai + 2] -= j * cA[o + 2];
416
513
  }
417
514
  if (imB > 0) {
418
515
  lv[bi + 0] += j * imB * axx;
419
516
  lv[bi + 1] += j * imB * axy;
420
517
  lv[bi + 2] += j * imB * axz;
421
- av[bi + 0] += j * iiB * cB[o + 0];
422
- av[bi + 1] += j * iiB * cB[o + 1];
423
- av[bi + 2] += j * iiB * cB[o + 2];
518
+ av[bi + 0] += j * cB[o + 0];
519
+ av[bi + 1] += j * cB[o + 1];
520
+ av[bi + 2] += j * cB[o + 2];
424
521
  }
425
522
  }
426
523
  // Angular axes — relAv = ω_B − ω_A.
427
- const angJacInv = con.cacheAngJacInv;
428
- if (angJacInv === 0)
429
- return;
430
524
  const angAxes = con.cacheAngAxes;
525
+ const angJac = con.cacheAngJacInv;
526
+ const angWAs = con.cacheAngWA;
527
+ const angWBs = con.cacheAngWB;
431
528
  const angTgt = con.cacheAngTargetVel;
432
529
  const angAct = con.cacheAngActive;
433
530
  const dax = av[bi + 0] - av[ai + 0];
434
531
  const day = av[bi + 1] - av[ai + 1];
435
532
  const daz = av[bi + 2] - av[ai + 2];
533
+ const angSprMax = con.cacheAngSpringMaxImp;
534
+ const angSprImp = con.cacheAngSpringImp;
436
535
  for (let i = 0; i < 3; i++) {
437
536
  if (!angAct[i])
438
537
  continue;
439
538
  const o = i * 3;
440
539
  const axx = angAxes[o + 0], axy = angAxes[o + 1], axz = angAxes[o + 2];
441
540
  const relAv = dax * axx + day * axy + daz * axz;
442
- // Spring drive (deadbeat-clamped at setup) plus, for small violations,
443
- // the folded per-axis limit correction.
444
- const j = (angTgt[i] - relAv) * angJacInv;
541
+ let j = (angTgt[i] - relAv) * angJac[i];
542
+ {
543
+ const max = angSprMax[i];
544
+ const old = angSprImp[i];
545
+ let next = old + j;
546
+ if (next > max)
547
+ next = max;
548
+ else if (next < -max)
549
+ next = -max;
550
+ j = next - old;
551
+ angSprImp[i] = next;
552
+ }
445
553
  if (j === 0)
446
554
  continue;
447
- if (iiA > 0) {
448
- av[ai + 0] -= j * iiA * axx;
449
- av[ai + 1] -= j * iiA * axy;
450
- av[ai + 2] -= j * iiA * axz;
555
+ if (imA > 0) {
556
+ av[ai + 0] -= j * angWAs[o + 0];
557
+ av[ai + 1] -= j * angWAs[o + 1];
558
+ av[ai + 2] -= j * angWAs[o + 2];
451
559
  }
452
- if (iiB > 0) {
453
- av[bi + 0] += j * iiB * axx;
454
- av[bi + 1] += j * iiB * axy;
455
- av[bi + 2] += j * iiB * axz;
560
+ if (imB > 0) {
561
+ av[bi + 0] += j * angWBs[o + 0];
562
+ av[bi + 1] += j * angWBs[o + 1];
563
+ av[bi + 2] += j * angWBs[o + 2];
564
+ }
565
+ }
566
+ // Per-axis angular limit rows (small-violation regime), on the derived
567
+ // euler axes. Sign convention matches the springs: positive target reduces
568
+ // positive error via d(angDiff)/dt = −(ω_B − ω_A)·ax.
569
+ const paAct = con.cacheAngPAActive;
570
+ if (paAct[0] || paAct[1] || paAct[2]) {
571
+ const paTgt = con.cacheAngPATarget;
572
+ const paImp = con.cacheAngPAImp;
573
+ for (let i = 0; i < 3; i++) {
574
+ if (!paAct[i])
575
+ continue;
576
+ const o = i * 3;
577
+ const axx = angAxes[o + 0], axy = angAxes[o + 1], axz = angAxes[o + 2];
578
+ const relAv = (av[bi + 0] - av[ai + 0]) * axx +
579
+ (av[bi + 1] - av[ai + 1]) * axy +
580
+ (av[bi + 2] - av[ai + 2]) * axz;
581
+ const target = paTgt[i];
582
+ // Locked axes (act 1) are welds — full gain, like the 0.16.3 fold;
583
+ // softness only tempers the unilateral stops.
584
+ const soft = paAct[i] === 2 ? LIMIT_SOFTNESS_ANGULAR : 1.0;
585
+ let j = soft * (target - relAv) * angJac[i];
586
+ if (paAct[i] === 2) {
587
+ const old = paImp[i];
588
+ let next = old + j;
589
+ if (target > 0 ? next < 0 : next > 0)
590
+ next = 0;
591
+ j = next - old;
592
+ paImp[i] = next;
593
+ }
594
+ if (j === 0)
595
+ continue;
596
+ if (imA > 0) {
597
+ av[ai + 0] -= j * angWAs[o + 0];
598
+ av[ai + 1] -= j * angWAs[o + 1];
599
+ av[ai + 2] -= j * angWAs[o + 2];
600
+ }
601
+ if (imB > 0) {
602
+ av[bi + 0] += j * angWBs[o + 0];
603
+ av[bi + 1] += j * angWBs[o + 1];
604
+ av[bi + 2] += j * angWBs[o + 2];
605
+ }
456
606
  }
457
607
  }
458
608
  // Geodesic limit row: drive (ω_B − ω_A)·axis toward the correction target.
@@ -465,7 +615,7 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
465
615
  const relAv = (av[bi + 0] - av[ai + 0]) * nx +
466
616
  (av[bi + 1] - av[ai + 1]) * ny +
467
617
  (av[bi + 2] - av[ai + 2]) * nz;
468
- let j = LIMIT_SOFTNESS_ANGULAR * (con.cacheAngLimTarget - relAv) * angJacInv;
618
+ let j = LIMIT_SOFTNESS_ANGULAR * (con.cacheAngLimTarget - relAv) * con.cacheAngLimJacInv;
469
619
  if (con.cacheAngLimActive === 2) {
470
620
  const old = con.cacheAngLimImp;
471
621
  let next = old + j;
@@ -475,15 +625,17 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
475
625
  con.cacheAngLimImp = next;
476
626
  }
477
627
  if (j !== 0) {
478
- if (iiA > 0) {
479
- av[ai + 0] -= j * iiA * nx;
480
- av[ai + 1] -= j * iiA * ny;
481
- av[ai + 2] -= j * iiA * nz;
628
+ const gWA = con.cacheAngLimWA;
629
+ const gWB = con.cacheAngLimWB;
630
+ if (imA > 0) {
631
+ av[ai + 0] -= j * gWA[0];
632
+ av[ai + 1] -= j * gWA[1];
633
+ av[ai + 2] -= j * gWA[2];
482
634
  }
483
- if (iiB > 0) {
484
- av[bi + 0] += j * iiB * nx;
485
- av[bi + 1] += j * iiB * ny;
486
- av[bi + 2] += j * iiB * nz;
635
+ if (imB > 0) {
636
+ av[bi + 0] += j * gWB[0];
637
+ av[bi + 1] += j * gWB[1];
638
+ av[bi + 2] += j * gWB[2];
487
639
  }
488
640
  }
489
641
  }
@@ -491,32 +643,38 @@ function iterateConstraint(con, lv, av, invMass, invInertia) {
491
643
  // SETUP: pre-compute Jacobians, friction basis, and the bounce reference
492
644
  // from the *initial* closing velocity (Bullet's pattern — captures restitution
493
645
  // before iter 1 zeroes out the approach).
494
- function setupContactRow(c, lv, av, invMass, invInertia) {
646
+ function setupContactRow(c, lv, av, invMass, W) {
495
647
  const ai = c.bodyA * 3;
496
648
  const bi = c.bodyB * 3;
649
+ const a9 = c.bodyA * 9;
650
+ const b9 = c.bodyB * 9;
497
651
  const imA = invMass[c.bodyA];
498
652
  const imB = invMass[c.bodyB];
499
- const iiA = invInertia[c.bodyA];
500
- const iiB = invInertia[c.bodyB];
501
653
  const rAx = c.rAx, rAy = c.rAy, rAz = c.rAz;
502
654
  const rBx = c.rBx, rBy = c.rBy, rBz = c.rBz;
503
655
  const nx = c.nx, ny = c.ny, nz = c.nz;
504
- // Normal Jacobian.
656
+ // Normal Jacobian. Cached vectors are tensor-multiplied I⁻¹·(r×n).
505
657
  const cAxN = rAy * nz - rAz * ny;
506
658
  const cAyN = rAz * nx - rAx * nz;
507
659
  const cAzN = rAx * ny - rAy * nx;
508
660
  const cBxN = rBy * nz - rBz * ny;
509
661
  const cByN = rBz * nx - rBx * nz;
510
662
  const cBzN = rBx * ny - rBy * nx;
663
+ const wAxN = W[a9 + 0] * cAxN + W[a9 + 1] * cAyN + W[a9 + 2] * cAzN;
664
+ const wAyN = W[a9 + 3] * cAxN + W[a9 + 4] * cAyN + W[a9 + 5] * cAzN;
665
+ const wAzN = W[a9 + 6] * cAxN + W[a9 + 7] * cAyN + W[a9 + 8] * cAzN;
666
+ const wBxN = W[b9 + 0] * cBxN + W[b9 + 1] * cByN + W[b9 + 2] * cBzN;
667
+ const wByN = W[b9 + 3] * cBxN + W[b9 + 4] * cByN + W[b9 + 5] * cBzN;
668
+ const wBzN = W[b9 + 6] * cBxN + W[b9 + 7] * cByN + W[b9 + 8] * cBzN;
511
669
  const denomN = imA + imB +
512
- (cAxN * cAxN + cAyN * cAyN + cAzN * cAzN) * iiA +
513
- (cBxN * cBxN + cByN * cByN + cBzN * cBzN) * iiB;
514
- c.cAxN = cAxN;
515
- c.cAyN = cAyN;
516
- c.cAzN = cAzN;
517
- c.cBxN = cBxN;
518
- c.cByN = cByN;
519
- c.cBzN = cBzN;
670
+ (cAxN * wAxN + cAyN * wAyN + cAzN * wAzN) +
671
+ (cBxN * wBxN + cByN * wByN + cBzN * wBzN);
672
+ c.cAxN = wAxN;
673
+ c.cAyN = wAyN;
674
+ c.cAzN = wAzN;
675
+ c.cBxN = wBxN;
676
+ c.cByN = wByN;
677
+ c.cBzN = wBzN;
520
678
  c.jacInvN = denomN > 0 ? 1 / denomN : 0;
521
679
  // Restitution reference, captured from initial relVelN.
522
680
  const vAx = lv[ai + 0] + av[ai + 1] * rAz - av[ai + 2] * rAy;
@@ -569,15 +727,21 @@ function setupContactRow(c, lv, av, invMass, invInertia) {
569
727
  const cBxT1 = rBy * t1z - rBz * t1y;
570
728
  const cByT1 = rBz * t1x - rBx * t1z;
571
729
  const cBzT1 = rBx * t1y - rBy * t1x;
730
+ const wAxT1 = W[a9 + 0] * cAxT1 + W[a9 + 1] * cAyT1 + W[a9 + 2] * cAzT1;
731
+ const wAyT1 = W[a9 + 3] * cAxT1 + W[a9 + 4] * cAyT1 + W[a9 + 5] * cAzT1;
732
+ const wAzT1 = W[a9 + 6] * cAxT1 + W[a9 + 7] * cAyT1 + W[a9 + 8] * cAzT1;
733
+ const wBxT1 = W[b9 + 0] * cBxT1 + W[b9 + 1] * cByT1 + W[b9 + 2] * cBzT1;
734
+ const wByT1 = W[b9 + 3] * cBxT1 + W[b9 + 4] * cByT1 + W[b9 + 5] * cBzT1;
735
+ const wBzT1 = W[b9 + 6] * cBxT1 + W[b9 + 7] * cByT1 + W[b9 + 8] * cBzT1;
572
736
  const denomT1 = imA + imB +
573
- (cAxT1 * cAxT1 + cAyT1 * cAyT1 + cAzT1 * cAzT1) * iiA +
574
- (cBxT1 * cBxT1 + cByT1 * cByT1 + cBzT1 * cBzT1) * iiB;
575
- c.cAxT1 = cAxT1;
576
- c.cAyT1 = cAyT1;
577
- c.cAzT1 = cAzT1;
578
- c.cBxT1 = cBxT1;
579
- c.cByT1 = cByT1;
580
- c.cBzT1 = cBzT1;
737
+ (cAxT1 * wAxT1 + cAyT1 * wAyT1 + cAzT1 * wAzT1) +
738
+ (cBxT1 * wBxT1 + cByT1 * wByT1 + cBzT1 * wBzT1);
739
+ c.cAxT1 = wAxT1;
740
+ c.cAyT1 = wAyT1;
741
+ c.cAzT1 = wAzT1;
742
+ c.cBxT1 = wBxT1;
743
+ c.cByT1 = wByT1;
744
+ c.cBzT1 = wBzT1;
581
745
  c.jacInvT1 = denomT1 > 0 ? 1 / denomT1 : 0;
582
746
  const cAxT2 = rAy * t2z - rAz * t2y;
583
747
  const cAyT2 = rAz * t2x - rAx * t2z;
@@ -585,27 +749,31 @@ function setupContactRow(c, lv, av, invMass, invInertia) {
585
749
  const cBxT2 = rBy * t2z - rBz * t2y;
586
750
  const cByT2 = rBz * t2x - rBx * t2z;
587
751
  const cBzT2 = rBx * t2y - rBy * t2x;
752
+ const wAxT2 = W[a9 + 0] * cAxT2 + W[a9 + 1] * cAyT2 + W[a9 + 2] * cAzT2;
753
+ const wAyT2 = W[a9 + 3] * cAxT2 + W[a9 + 4] * cAyT2 + W[a9 + 5] * cAzT2;
754
+ const wAzT2 = W[a9 + 6] * cAxT2 + W[a9 + 7] * cAyT2 + W[a9 + 8] * cAzT2;
755
+ const wBxT2 = W[b9 + 0] * cBxT2 + W[b9 + 1] * cByT2 + W[b9 + 2] * cBzT2;
756
+ const wByT2 = W[b9 + 3] * cBxT2 + W[b9 + 4] * cByT2 + W[b9 + 5] * cBzT2;
757
+ const wBzT2 = W[b9 + 6] * cBxT2 + W[b9 + 7] * cByT2 + W[b9 + 8] * cBzT2;
588
758
  const denomT2 = imA + imB +
589
- (cAxT2 * cAxT2 + cAyT2 * cAyT2 + cAzT2 * cAzT2) * iiA +
590
- (cBxT2 * cBxT2 + cByT2 * cByT2 + cBzT2 * cBzT2) * iiB;
591
- c.cAxT2 = cAxT2;
592
- c.cAyT2 = cAyT2;
593
- c.cAzT2 = cAzT2;
594
- c.cBxT2 = cBxT2;
595
- c.cByT2 = cByT2;
596
- c.cBzT2 = cBzT2;
759
+ (cAxT2 * wAxT2 + cAyT2 * wAyT2 + cAzT2 * wAzT2) +
760
+ (cBxT2 * wBxT2 + cByT2 * wByT2 + cBzT2 * wBzT2);
761
+ c.cAxT2 = wAxT2;
762
+ c.cAyT2 = wAyT2;
763
+ c.cAzT2 = wAzT2;
764
+ c.cBxT2 = wBxT2;
765
+ c.cByT2 = wByT2;
766
+ c.cBzT2 = wBzT2;
597
767
  c.jacInvT2 = denomT2 > 0 ? 1 / denomT2 : 0;
598
768
  }
599
769
  // ITER: one push-only normal row + two Coulomb friction rows. Friction
600
770
  // bound depends on the *current* applied normal impulse, so it tightens
601
771
  // as the normal row converges.
602
- function iterateContactRow(c, lv, av, invMass, invInertia) {
772
+ function iterateContactRow(c, lv, av, invMass) {
603
773
  const imA = invMass[c.bodyA];
604
774
  const imB = invMass[c.bodyB];
605
775
  if (imA === 0 && imB === 0)
606
776
  return;
607
- const iiA = invInertia[c.bodyA];
608
- const iiB = invInertia[c.bodyB];
609
777
  const ai = c.bodyA * 3, bi = c.bodyB * 3;
610
778
  const rAx = c.rAx, rAy = c.rAy, rAz = c.rAz;
611
779
  const rBx = c.rBx, rBy = c.rBy, rBz = c.rBz;
@@ -638,17 +806,17 @@ function iterateContactRow(c, lv, av, invMass, invInertia) {
638
806
  lv[ai + 0] -= dImpN * imA * nx;
639
807
  lv[ai + 1] -= dImpN * imA * ny;
640
808
  lv[ai + 2] -= dImpN * imA * nz;
641
- av[ai + 0] -= dImpN * iiA * cAxN;
642
- av[ai + 1] -= dImpN * iiA * cAyN;
643
- av[ai + 2] -= dImpN * iiA * cAzN;
809
+ av[ai + 0] -= dImpN * cAxN;
810
+ av[ai + 1] -= dImpN * cAyN;
811
+ av[ai + 2] -= dImpN * cAzN;
644
812
  }
645
813
  if (imB > 0) {
646
814
  lv[bi + 0] += dImpN * imB * nx;
647
815
  lv[bi + 1] += dImpN * imB * ny;
648
816
  lv[bi + 2] += dImpN * imB * nz;
649
- av[bi + 0] += dImpN * iiB * cBxN;
650
- av[bi + 1] += dImpN * iiB * cByN;
651
- av[bi + 2] += dImpN * iiB * cBzN;
817
+ av[bi + 0] += dImpN * cBxN;
818
+ av[bi + 1] += dImpN * cByN;
819
+ av[bi + 2] += dImpN * cBzN;
652
820
  }
653
821
  }
654
822
  }
@@ -666,10 +834,10 @@ function iterateContactRow(c, lv, av, invMass, invInertia) {
666
834
  const dvx2 = vBx2 - vAx2;
667
835
  const dvy2 = vBy2 - vAy2;
668
836
  const dvz2 = vBz2 - vAz2;
669
- applyFrictionTangent(c, ai, bi, dvx2, dvy2, dvz2, c.t1x, c.t1y, c.t1z, c.cAxT1, c.cAyT1, c.cAzT1, c.cBxT1, c.cByT1, c.cBzT1, c.jacInvT1, muNormal, imA, imB, iiA, iiB, lv, av, 1);
670
- applyFrictionTangent(c, ai, bi, dvx2, dvy2, dvz2, c.t2x, c.t2y, c.t2z, c.cAxT2, c.cAyT2, c.cAzT2, c.cBxT2, c.cByT2, c.cBzT2, c.jacInvT2, muNormal, imA, imB, iiA, iiB, lv, av, 2);
837
+ applyFrictionTangent(c, ai, bi, dvx2, dvy2, dvz2, c.t1x, c.t1y, c.t1z, c.cAxT1, c.cAyT1, c.cAzT1, c.cBxT1, c.cByT1, c.cBzT1, c.jacInvT1, muNormal, imA, imB, lv, av, 1);
838
+ applyFrictionTangent(c, ai, bi, dvx2, dvy2, dvz2, c.t2x, c.t2y, c.t2z, c.cAxT2, c.cAyT2, c.cAzT2, c.cBxT2, c.cByT2, c.cBzT2, c.jacInvT2, muNormal, imA, imB, lv, av, 2);
671
839
  }
672
- function applyFrictionTangent(c, ai, bi, dvx, dvy, dvz, tx, ty, tz, cAx, cAy, cAz, cBx, cBy, cBz, jacInv, muNormal, imA, imB, iiA, iiB, lv, av, slot) {
840
+ function applyFrictionTangent(c, ai, bi, dvx, dvy, dvz, tx, ty, tz, cAx, cAy, cAz, cBx, cBy, cBz, jacInv, muNormal, imA, imB, lv, av, slot) {
673
841
  if (jacInv <= 0)
674
842
  return;
675
843
  const relVel = dvx * tx + dvy * ty + dvz * tz;
@@ -694,17 +862,17 @@ function applyFrictionTangent(c, ai, bi, dvx, dvy, dvz, tx, ty, tz, cAx, cAy, cA
694
862
  lv[ai + 0] -= dImp * imA * tx;
695
863
  lv[ai + 1] -= dImp * imA * ty;
696
864
  lv[ai + 2] -= dImp * imA * tz;
697
- av[ai + 0] -= dImp * iiA * cAx;
698
- av[ai + 1] -= dImp * iiA * cAy;
699
- av[ai + 2] -= dImp * iiA * cAz;
865
+ av[ai + 0] -= dImp * cAx;
866
+ av[ai + 1] -= dImp * cAy;
867
+ av[ai + 2] -= dImp * cAz;
700
868
  }
701
869
  if (imB > 0) {
702
870
  lv[bi + 0] += dImp * imB * tx;
703
871
  lv[bi + 1] += dImp * imB * ty;
704
872
  lv[bi + 2] += dImp * imB * tz;
705
- av[bi + 0] += dImp * iiB * cBx;
706
- av[bi + 1] += dImp * iiB * cBy;
707
- av[bi + 2] += dImp * iiB * cBz;
873
+ av[bi + 0] += dImp * cBx;
874
+ av[bi + 1] += dImp * cBy;
875
+ av[bi + 2] += dImp * cBz;
708
876
  }
709
877
  }
710
878
  function buildBodyMat(store, i, out) {
@@ -1 +1 @@
1
- {"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAWN,MAAM,SAAS,CAAA;AAGhB,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEzE,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,kBAAkB,CAAI;IAC9B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAe;IAE/B,OAAO;IAQP,OAAO,CAAC,IAAI;WAMC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAI9C,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK;WAIpC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAKxF,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,WAAW;IA+CnB,OAAO,CAAC,aAAa;IA+FrB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,cAAc;IAoEtB,OAAO,CAAC,UAAU;IAsKlB,OAAO,CAAC,WAAW;IA2JnB,OAAO,CAAC,iBAAiB;IAgDzB,OAAO,CAAC,gBAAgB;IA+FxB,OAAO,CAAC,WAAW;IAmGnB,OAAO,CAAC,kBAAkB;IAmC1B,OAAO,CAAC,OAAO;IAkJf,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,QAAQ;CAIjB"}
1
+ {"version":3,"file":"pmx-loader.d.ts","sourceRoot":"","sources":["../src/pmx-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAWN,MAAM,SAAS,CAAA;AAGhB,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEzE,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,mBAAmB,CAAI;IAC/B,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,gBAAgB,CAAI;IAC5B,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,aAAa,CAAI;IACzB,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,kBAAkB,CAAI;IAC9B,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAiB;IAClC,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,QAAQ,CAAe;IAE/B,OAAO;IAQP,OAAO,CAAC,IAAI;WAMC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAI9C,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK;WAIpC,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAKxF,OAAO,CAAC,KAAK;IAiBb,OAAO,CAAC,WAAW;IA+CnB,OAAO,CAAC,aAAa;IA+FrB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,cAAc;IAqEtB,OAAO,CAAC,UAAU;IAsKlB,OAAO,CAAC,WAAW;IA2JnB,OAAO,CAAC,iBAAiB;IAgDzB,OAAO,CAAC,gBAAgB;IA+FxB,OAAO,CAAC,WAAW;IAmGnB,OAAO,CAAC,kBAAkB;IAmC1B,OAAO,CAAC,OAAO;IAkJf,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,SAAS;IAUjB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,QAAQ;CAIjB"}
@@ -258,6 +258,7 @@ export class PmxLoader {
258
258
  sphereTextureIndex,
259
259
  sphereMode: sphereTextureMode,
260
260
  toonTextureIndex,
261
+ sharedToon: isSharedToonTexture,
261
262
  edgeFlag: flag,
262
263
  edgeColor,
263
264
  edgeSize,