reze-engine 0.15.0 → 0.16.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.
Files changed (93) hide show
  1. package/README.md +3 -3
  2. package/dist/camera.d.ts +2 -0
  3. package/dist/camera.d.ts.map +1 -1
  4. package/dist/camera.js +8 -3
  5. package/dist/engine.d.ts +19 -4
  6. package/dist/engine.d.ts.map +1 -1
  7. package/dist/engine.js +294 -27
  8. package/dist/gpu-profile.d.ts +19 -0
  9. package/dist/gpu-profile.d.ts.map +1 -0
  10. package/dist/gpu-profile.js +120 -0
  11. package/dist/ik-solver.d.ts.map +1 -1
  12. package/dist/ik-solver.js +13 -7
  13. package/dist/math.d.ts +2 -0
  14. package/dist/math.d.ts.map +1 -1
  15. package/dist/math.js +65 -46
  16. package/dist/model.d.ts +28 -1
  17. package/dist/model.d.ts.map +1 -1
  18. package/dist/model.js +259 -75
  19. package/dist/physics/body.d.ts +2 -0
  20. package/dist/physics/body.d.ts.map +1 -1
  21. package/dist/physics/body.js +30 -0
  22. package/dist/physics/constraint.d.ts +13 -0
  23. package/dist/physics/constraint.d.ts.map +1 -1
  24. package/dist/physics/constraint.js +13 -0
  25. package/dist/physics/contact.d.ts +28 -0
  26. package/dist/physics/contact.d.ts.map +1 -1
  27. package/dist/physics/contact.js +27 -32
  28. package/dist/physics/physics.d.ts +3 -0
  29. package/dist/physics/physics.d.ts.map +1 -1
  30. package/dist/physics/physics.js +57 -8
  31. package/dist/physics/profile.d.ts +18 -0
  32. package/dist/physics/profile.d.ts.map +1 -0
  33. package/dist/physics/profile.js +44 -0
  34. package/dist/physics/solver.d.ts.map +1 -1
  35. package/dist/physics/solver.js +436 -293
  36. package/dist/pmx-loader.d.ts.map +1 -1
  37. package/dist/pmx-loader.js +2 -30
  38. package/dist/shaders/materials/body.d.ts +1 -1
  39. package/dist/shaders/materials/body.d.ts.map +1 -1
  40. package/dist/shaders/materials/body.js +3 -1
  41. package/dist/shaders/materials/cloth_rough.d.ts +1 -1
  42. package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
  43. package/dist/shaders/materials/cloth_rough.js +3 -1
  44. package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
  45. package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
  46. package/dist/shaders/materials/common.d.ts +2 -2
  47. package/dist/shaders/materials/common.d.ts.map +1 -1
  48. package/dist/shaders/materials/common.js +8 -0
  49. package/dist/shaders/materials/default.d.ts +1 -1
  50. package/dist/shaders/materials/default.d.ts.map +1 -1
  51. package/dist/shaders/materials/eye.d.ts +1 -1
  52. package/dist/shaders/materials/eye.d.ts.map +1 -1
  53. package/dist/shaders/materials/face.d.ts +1 -1
  54. package/dist/shaders/materials/face.d.ts.map +1 -1
  55. package/dist/shaders/materials/face.js +4 -1
  56. package/dist/shaders/materials/hair.d.ts +1 -1
  57. package/dist/shaders/materials/hair.d.ts.map +1 -1
  58. package/dist/shaders/materials/metal.d.ts +1 -1
  59. package/dist/shaders/materials/metal.d.ts.map +1 -1
  60. package/dist/shaders/materials/stockings.d.ts +1 -1
  61. package/dist/shaders/materials/stockings.d.ts.map +1 -1
  62. package/dist/shaders/materials/stockings.js +13 -9
  63. package/dist/shaders/passes/composite.d.ts +1 -1
  64. package/dist/shaders/passes/composite.d.ts.map +1 -1
  65. package/dist/shaders/passes/composite.js +18 -12
  66. package/dist/shaders/passes/morph.d.ts +2 -0
  67. package/dist/shaders/passes/morph.d.ts.map +1 -0
  68. package/dist/shaders/passes/morph.js +49 -0
  69. package/package.json +1 -1
  70. package/src/camera.ts +9 -3
  71. package/src/engine.ts +336 -29
  72. package/src/ik-solver.ts +14 -7
  73. package/src/math.ts +41 -51
  74. package/src/model.ts +288 -91
  75. package/src/physics/body.ts +29 -0
  76. package/src/physics/constraint.ts +30 -0
  77. package/src/physics/contact.ts +43 -30
  78. package/src/physics/physics.ts +58 -17
  79. package/src/physics/solver.ts +431 -284
  80. package/src/pmx-loader.ts +2 -35
  81. package/src/shaders/materials/body.ts +3 -1
  82. package/src/shaders/materials/cloth_rough.ts +3 -1
  83. package/src/shaders/materials/common.ts +8 -0
  84. package/src/shaders/materials/face.ts +4 -1
  85. package/src/shaders/materials/stockings.ts +13 -9
  86. package/src/shaders/passes/composite.ts +18 -12
  87. package/src/shaders/passes/morph.ts +50 -0
  88. package/dist/physics-debug.d.ts +0 -30
  89. package/dist/physics-debug.d.ts.map +0 -1
  90. package/dist/physics-debug.js +0 -526
  91. package/dist/shaders/passes/physics-debug.d.ts +0 -2
  92. package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
  93. package/dist/shaders/passes/physics-debug.js +0 -69
@@ -2,6 +2,14 @@
2
2
  // Gauss-Seidel: per axis, target a relative velocity (limit correction +
3
3
  // spring), apply the impulse needed to reach it. Friction is two Coulomb
4
4
  // rows per contact, normal is push-only.
5
+ //
6
+ // Two passes per substep:
7
+ // 1. SETUP — for each constraint and contact, compute every quantity that
8
+ // doesn't depend on lv/av (world axes, lever arms, Jacobian denominators,
9
+ // target velocities, friction tangent bases, restitution reference).
10
+ // These are constant during solve since pos/ori/inertia don't change.
11
+ // 2. ITERATE — `iterations` passes that read the cache and apply impulses
12
+ // based on the current lv/av. ~2× faster than recomputing per iter.
5
13
 
6
14
  import { Mat4 } from "../math"
7
15
  import type { RigidBodyStore } from "./body"
@@ -16,12 +24,7 @@ const _TA = new Float32Array(16)
16
24
  const _TB = new Float32Array(16)
17
25
  const _bodyMatA = new Float32Array(16)
18
26
  const _bodyMatB = new Float32Array(16)
19
- const _linAxes = new Float32Array(9) // 3 linear axes × xyz
20
- const _angAxes = new Float32Array(9) // 3 angular axes × xyz
21
- const _linDiff = new Float32Array(3)
22
- const _angDiff = new Float32Array(3)
23
- const _rA = new Float32Array(3)
24
- const _rB = new Float32Array(3)
27
+ const _angDiffScratch = new Float32Array(3)
25
28
 
26
29
  export function solveConstraints(
27
30
  store: RigidBodyStore,
@@ -36,234 +39,222 @@ export function solveConstraints(
36
39
  const invDt = 1 / dt
37
40
  const lv = store.linearVelocities
38
41
  const av = store.angularVelocities
39
- const pos = store.positions
40
42
  const invMass = store.invMass
41
43
  const invInertia = store.invInertia
42
44
 
45
+ for (let c = 0; c < constraints.length; c++) {
46
+ setupConstraint(constraints[c], store, dt, invDt)
47
+ }
48
+ for (let ci = 0; ci < contacts.count; ci++) {
49
+ setupContactRow(contacts.get(ci), lv, av, invMass, invInertia)
50
+ }
51
+
43
52
  for (let iter = 0; iter < iterations; iter++) {
44
53
  for (let c = 0; c < constraints.length; c++) {
45
- const con = constraints[c]
46
- const a = con.bodyA
47
- const b = con.bodyB
48
- const imA = invMass[a]
49
- const imB = invMass[b]
50
- const iiA = invInertia[a]
51
- const iiB = invInertia[b]
52
- if (imA === 0 && imB === 0) continue
53
-
54
- buildBodyMat(store, a, _bodyMatA)
55
- buildBodyMat(store, b, _bodyMatB)
56
- Mat4.multiplyArrays(_bodyMatA, 0, con.frameA, 0, _TA, 0)
57
- Mat4.multiplyArrays(_bodyMatB, 0, con.frameB, 0, _TB, 0)
58
-
59
- // Mass-weighted shared anchor: kinematic partner gets weight 1 so
60
- // the anchor sits exactly on it; two dynamic bodies blend by inverse
61
- // mass. Lever arms are measured from each CG to that shared anchor.
62
- const ai = a * 3
63
- const bi = b * 3
64
- const weightA = imB === 0 ? 1 : imA / (imA + imB)
65
- const weightB = 1 - weightA
66
- const anchorX = _TA[12] * weightA + _TB[12] * weightB
67
- const anchorY = _TA[13] * weightA + _TB[13] * weightB
68
- const anchorZ = _TA[14] * weightA + _TB[14] * weightB
69
- _rA[0] = anchorX - pos[ai + 0]
70
- _rA[1] = anchorY - pos[ai + 1]
71
- _rA[2] = anchorZ - pos[ai + 2]
72
- _rB[0] = anchorX - pos[bi + 0]
73
- _rB[1] = anchorY - pos[bi + 1]
74
- _rB[2] = anchorZ - pos[bi + 2]
75
-
76
- // Linear part: linearDiff = TA.basis^T · (TB.origin − TA.origin),
77
- // axes = TA's columns 0/1/2 in world space.
78
- const dxw = _TB[12] - _TA[12]
79
- const dyw = _TB[13] - _TA[13]
80
- const dzw = _TB[14] - _TA[14]
81
- _linDiff[0] = _TA[0] * dxw + _TA[1] * dyw + _TA[2] * dzw
82
- _linDiff[1] = _TA[4] * dxw + _TA[5] * dyw + _TA[6] * dzw
83
- _linDiff[2] = _TA[8] * dxw + _TA[9] * dyw + _TA[10] * dzw
84
-
85
- _linAxes[0] = _TA[0]; _linAxes[1] = _TA[1]; _linAxes[2] = _TA[2]
86
- _linAxes[3] = _TA[4]; _linAxes[4] = _TA[5]; _linAxes[5] = _TA[6]
87
- _linAxes[6] = _TA[8]; _linAxes[7] = _TA[9]; _linAxes[8] = _TA[10]
88
-
89
- for (let i = 0; i < 3; i++) {
90
- const lo = con.linearMin[i]
91
- const hi = con.linearMax[i]
92
- const curr = _linDiff[i]
93
- const off = i * 3
94
- const axx = _linAxes[off + 0]
95
- const axy = _linAxes[off + 1]
96
- const axz = _linAxes[off + 2]
97
-
98
- // (rA × axis), (rB × axis): angular components of the linear Jacobian.
99
- const cAx = _rA[1] * axz - _rA[2] * axy
100
- const cAy = _rA[2] * axx - _rA[0] * axz
101
- const cAz = _rA[0] * axy - _rA[1] * axx
102
- const cBx = _rB[1] * axz - _rB[2] * axy
103
- const cBy = _rB[2] * axx - _rB[0] * axz
104
- const cBz = _rB[0] * axy - _rB[1] * axx
105
- const cA2 = cAx * cAx + cAy * cAy + cAz * cAz
106
- const cB2 = cBx * cBx + cBy * cBy + cBz * cBz
107
- const denom = imA + imB + cA2 * iiA + cB2 * iiB
108
- if (denom <= 0) continue
109
- const jacInv = 1 / denom
110
-
111
- // v_pivot = v_CG + ω × r.
112
- const vAx = lv[ai + 0] + av[ai + 1] * _rA[2] - av[ai + 2] * _rA[1]
113
- const vAy = lv[ai + 1] + av[ai + 2] * _rA[0] - av[ai + 0] * _rA[2]
114
- const vAz = lv[ai + 2] + av[ai + 0] * _rA[1] - av[ai + 1] * _rA[0]
115
- const vBx = lv[bi + 0] + av[bi + 1] * _rB[2] - av[bi + 2] * _rB[1]
116
- const vBy = lv[bi + 1] + av[bi + 2] * _rB[0] - av[bi + 0] * _rB[2]
117
- const vBz = lv[bi + 2] + av[bi + 0] * _rB[1] - av[bi + 1] * _rB[0]
118
- const relVel = (vBx - vAx) * axx + (vBy - vAy) * axy + (vBz - vAz) * axz
119
-
120
- let targetVel = 0
121
- let active = false
122
-
123
- if (lo <= hi) {
124
- let err = 0
125
- if (curr < lo) err = curr - lo
126
- else if (curr > hi) err = curr - hi
127
- if (err !== 0) {
128
- targetVel = -err * STOP_ERP * invDt
129
- active = true
130
- }
131
- }
132
- if (con.springEnabled[i]) {
133
- targetVel += -con.springStiffness[i] * (curr - con.equilibriumPoint[i]) * dt
134
- active = true
135
- }
136
-
137
- if (active) {
138
- const j = (targetVel - relVel) * jacInv
139
- if (imA > 0) {
140
- lv[ai + 0] -= j * imA * axx
141
- lv[ai + 1] -= j * imA * axy
142
- lv[ai + 2] -= j * imA * axz
143
- av[ai + 0] -= j * iiA * cAx
144
- av[ai + 1] -= j * iiA * cAy
145
- av[ai + 2] -= j * iiA * cAz
146
- }
147
- if (imB > 0) {
148
- lv[bi + 0] += j * imB * axx
149
- lv[bi + 1] += j * imB * axy
150
- lv[bi + 2] += j * imB * axz
151
- av[bi + 0] += j * iiB * cBx
152
- av[bi + 1] += j * iiB * cBy
153
- av[bi + 2] += j * iiB * cBz
154
- }
155
- }
156
- }
54
+ iterateConstraint(constraints[c], lv, av, invMass, invInertia)
55
+ }
56
+ for (let ci = 0; ci < contacts.count; ci++) {
57
+ iterateContactRow(contacts.get(ci), lv, av, invMass, invInertia)
58
+ }
59
+ }
60
+ }
157
61
 
158
- // Angular part: relative rotation TA^T·TB Euler XYZ; axes from
159
- // TA.col2 × TB.col0 (as Bullet's calculatedAxis derivation).
160
- const r00 = _TA[0]*_TB[0] + _TA[1]*_TB[1] + _TA[2]*_TB[2]
161
- const r01 = _TA[0]*_TB[4] + _TA[1]*_TB[5] + _TA[2]*_TB[6]
162
- const r10 = _TA[4]*_TB[0] + _TA[5]*_TB[1] + _TA[6]*_TB[2]
163
- const r11 = _TA[4]*_TB[4] + _TA[5]*_TB[5] + _TA[6]*_TB[6]
164
- const r20 = _TA[8]*_TB[0] + _TA[9]*_TB[1] + _TA[10]*_TB[2]
165
- const r21 = _TA[8]*_TB[4] + _TA[9]*_TB[5] + _TA[10]*_TB[6]
166
- const r22 = _TA[8]*_TB[8] + _TA[9]*_TB[9] + _TA[10]*_TB[10]
167
- matrixToEulerXYZ(r00, r01, r10, r11, r20, r21, r22, _angDiff)
168
-
169
- const a2x = _TA[8], a2y = _TA[9], a2z = _TA[10]
170
- const b0x = _TB[0], b0y = _TB[1], b0z = _TB[2]
171
- // ax[1] = a2 × b0; ax[0] = ax[1] × a2; ax[2] = b0 × ax[1].
172
- let yx = a2y * b0z - a2z * b0y
173
- let yy = a2z * b0x - a2x * b0z
174
- let yz = a2x * b0y - a2y * b0x
175
- let l = Math.hypot(yx, yy, yz)
176
- if (l > 1e-8) { const inv = 1/l; yx*=inv; yy*=inv; yz*=inv }
177
- _angAxes[3] = yx; _angAxes[4] = yy; _angAxes[5] = yz
178
- let xx = yy * a2z - yz * a2y
179
- let xy = yz * a2x - yx * a2z
180
- let xz = yx * a2y - yy * a2x
181
- l = Math.hypot(xx, xy, xz)
182
- if (l > 1e-8) { const inv = 1/l; xx*=inv; xy*=inv; xz*=inv }
183
- _angAxes[0] = xx; _angAxes[1] = xy; _angAxes[2] = xz
184
- let zx = b0y * yz - b0z * yy
185
- let zy = b0z * yx - b0x * yz
186
- let zz = b0x * yy - b0y * yx
187
- l = Math.hypot(zx, zy, zz)
188
- if (l > 1e-8) { const inv = 1/l; zx*=inv; zy*=inv; zz*=inv }
189
- _angAxes[6] = zx; _angAxes[7] = zy; _angAxes[8] = zz
190
-
191
- const angDenom = iiA + iiB
192
- if (angDenom > 0) {
193
- const angJacInv = 1 / angDenom
194
-
195
- for (let i = 0; i < 3; i++) {
196
- const idx = i + 3
197
- const lo = con.angularMin[i]
198
- const hi = con.angularMax[i]
199
- const curr = _angDiff[i]
200
- const off = i * 3
201
- const axx = _angAxes[off + 0]
202
- const axy = _angAxes[off + 1]
203
- const axz = _angAxes[off + 2]
204
-
205
- const relAv =
206
- (av[bi + 0] - av[ai + 0]) * axx +
207
- (av[bi + 1] - av[ai + 1]) * axy +
208
- (av[bi + 2] - av[ai + 2]) * axz
209
-
210
- // Sign flip vs linear: d(angDiff)/dt = −(ω_B − ω_A)·ax.
211
- let targetVel = 0
212
- let active = false
213
-
214
- if (lo <= hi) {
215
- let err = 0
216
- if (curr < lo) err = curr - lo
217
- else if (curr > hi) err = curr - hi
218
- if (err !== 0) {
219
- targetVel = err * STOP_ERP * invDt
220
- active = true
221
- }
222
- }
223
- if (con.springEnabled[idx]) {
224
- targetVel += con.springStiffness[idx] * (curr - con.equilibriumPoint[idx]) * dt
225
- active = true
226
- }
227
-
228
- if (active) {
229
- const j = (targetVel - relAv) * angJacInv
230
- if (iiA > 0) {
231
- av[ai + 0] -= j * iiA * axx
232
- av[ai + 1] -= j * iiA * axy
233
- av[ai + 2] -= j * iiA * axz
234
- }
235
- if (iiB > 0) {
236
- av[bi + 0] += j * iiB * axx
237
- av[bi + 1] += j * iiB * axy
238
- av[bi + 2] += j * iiB * axz
239
- }
240
- }
241
- }
62
+ // SETUP: compute everything that doesn't depend on velocities. Caller
63
+ // guarantees pos/ori don't change between this and the iter loop.
64
+ function setupConstraint(
65
+ con: SixDofSpringConstraint,
66
+ store: RigidBodyStore,
67
+ dt: number,
68
+ invDt: number,
69
+ ): void {
70
+ const a = con.bodyA
71
+ const b = con.bodyB
72
+ const imA = store.invMass[a]
73
+ const imB = store.invMass[b]
74
+ const iiA = store.invInertia[a]
75
+ const iiB = store.invInertia[b]
76
+
77
+ con.cacheSkip = imA === 0 && imB === 0
78
+ if (con.cacheSkip) return
79
+
80
+ buildBodyMat(store, a, _bodyMatA)
81
+ buildBodyMat(store, b, _bodyMatB)
82
+ Mat4.multiplyArrays(_bodyMatA, 0, con.frameA, 0, _TA, 0)
83
+ Mat4.multiplyArrays(_bodyMatB, 0, con.frameB, 0, _TB, 0)
84
+
85
+ // Mass-weighted shared anchor (Bullet 2.75 m_AnchorPos).
86
+ const pos = store.positions
87
+ const ai = a * 3
88
+ const bi = b * 3
89
+ const weightA = imB === 0 ? 1 : imA / (imA + imB)
90
+ const weightB = 1 - weightA
91
+ const anchorX = _TA[12] * weightA + _TB[12] * weightB
92
+ const anchorY = _TA[13] * weightA + _TB[13] * weightB
93
+ const anchorZ = _TA[14] * weightA + _TB[14] * weightB
94
+ const rAx = anchorX - pos[ai + 0]
95
+ const rAy = anchorY - pos[ai + 1]
96
+ const rAz = anchorZ - pos[ai + 2]
97
+ const rBx = anchorX - pos[bi + 0]
98
+ const rBy = anchorY - pos[bi + 1]
99
+ const rBz = anchorZ - pos[bi + 2]
100
+ const lA = con.cacheLeverA
101
+ const lB = con.cacheLeverB
102
+ lA[0] = rAx; lA[1] = rAy; lA[2] = rAz
103
+ lB[0] = rBx; lB[1] = rBy; lB[2] = rBz
104
+
105
+ // linearDiff = TA.basis^T · (TB.origin − TA.origin); axes = TA columns 0/1/2.
106
+ const dxw = _TB[12] - _TA[12]
107
+ const dyw = _TB[13] - _TA[13]
108
+ const dzw = _TB[14] - _TA[14]
109
+ const linDiff0 = _TA[0] * dxw + _TA[1] * dyw + _TA[2] * dzw
110
+ const linDiff1 = _TA[4] * dxw + _TA[5] * dyw + _TA[6] * dzw
111
+ const linDiff2 = _TA[8] * dxw + _TA[9] * dyw + _TA[10] * dzw
112
+
113
+ const axes = con.cacheLinAxes
114
+ const cA = con.cacheLinCrossA
115
+ const cB = con.cacheLinCrossB
116
+ const jac = con.cacheLinJacInv
117
+ const tgt = con.cacheLinTargetVel
118
+ const act = con.cacheLinActive
119
+
120
+ for (let i = 0; i < 3; i++) {
121
+ const o = i * 3
122
+ const axx = i === 0 ? _TA[0] : i === 1 ? _TA[4] : _TA[8]
123
+ const axy = i === 0 ? _TA[1] : i === 1 ? _TA[5] : _TA[9]
124
+ const axz = i === 0 ? _TA[2] : i === 1 ? _TA[6] : _TA[10]
125
+ axes[o + 0] = axx
126
+ axes[o + 1] = axy
127
+ axes[o + 2] = axz
128
+
129
+ const cAx = rAy * axz - rAz * axy
130
+ const cAy = rAz * axx - rAx * axz
131
+ const cAz = rAx * axy - rAy * axx
132
+ const cBx = rBy * axz - rBz * axy
133
+ const cBy = rBz * axx - rBx * axz
134
+ const cBz = rBx * axy - rBy * axx
135
+ cA[o + 0] = cAx; cA[o + 1] = cAy; cA[o + 2] = cAz
136
+ cB[o + 0] = cBx; cB[o + 1] = cBy; cB[o + 2] = cBz
137
+
138
+ const denom = imA + imB +
139
+ (cAx * cAx + cAy * cAy + cAz * cAz) * iiA +
140
+ (cBx * cBx + cBy * cBy + cBz * cBz) * iiB
141
+ jac[i] = denom > 0 ? 1 / denom : 0
142
+
143
+ const lo = con.linearMin[i]
144
+ const hi = con.linearMax[i]
145
+ const curr = i === 0 ? linDiff0 : i === 1 ? linDiff1 : linDiff2
146
+ let target = 0
147
+ let active = 0
148
+ if (lo <= hi) {
149
+ let err = 0
150
+ if (curr < lo) err = curr - lo
151
+ else if (curr > hi) err = curr - hi
152
+ if (err !== 0) {
153
+ target = -err * STOP_ERP * invDt
154
+ active = 1
242
155
  }
243
156
  }
157
+ if (con.springEnabled[i]) {
158
+ target += -con.springStiffness[i] * (curr - con.equilibriumPoint[i]) * dt
159
+ active = 1
160
+ }
161
+ tgt[i] = target
162
+ act[i] = denom > 0 ? active : 0
163
+ }
244
164
 
245
- for (let ci = 0; ci < contacts.count; ci++) {
246
- solveContactRow(contacts.get(ci), lv, av, invMass, invInertia)
165
+ // Angular: TA^T · TB → Euler XYZ; axes from TA.col2 × TB.col0.
166
+ const r00 = _TA[0]*_TB[0] + _TA[1]*_TB[1] + _TA[2]*_TB[2]
167
+ const r01 = _TA[0]*_TB[4] + _TA[1]*_TB[5] + _TA[2]*_TB[6]
168
+ const r10 = _TA[4]*_TB[0] + _TA[5]*_TB[1] + _TA[6]*_TB[2]
169
+ const r11 = _TA[4]*_TB[4] + _TA[5]*_TB[5] + _TA[6]*_TB[6]
170
+ const r20 = _TA[8]*_TB[0] + _TA[9]*_TB[1] + _TA[10]*_TB[2]
171
+ const r21 = _TA[8]*_TB[4] + _TA[9]*_TB[5] + _TA[10]*_TB[6]
172
+ const r22 = _TA[8]*_TB[8] + _TA[9]*_TB[9] + _TA[10]*_TB[10]
173
+ matrixToEulerXYZ(r00, r01, r10, r11, r20, r21, r22, _angDiffScratch)
174
+
175
+ const a2x = _TA[8], a2y = _TA[9], a2z = _TA[10]
176
+ const b0x = _TB[0], b0y = _TB[1], b0z = _TB[2]
177
+ let yx = a2y * b0z - a2z * b0y
178
+ let yy = a2z * b0x - a2x * b0z
179
+ let yz = a2x * b0y - a2y * b0x
180
+ let l = Math.hypot(yx, yy, yz)
181
+ if (l > 1e-8) { const inv = 1/l; yx*=inv; yy*=inv; yz*=inv }
182
+ let xx = yy * a2z - yz * a2y
183
+ let xy = yz * a2x - yx * a2z
184
+ let xz = yx * a2y - yy * a2x
185
+ l = Math.hypot(xx, xy, xz)
186
+ if (l > 1e-8) { const inv = 1/l; xx*=inv; xy*=inv; xz*=inv }
187
+ let zx = b0y * yz - b0z * yy
188
+ let zy = b0z * yx - b0x * yz
189
+ let zz = b0x * yy - b0y * yx
190
+ l = Math.hypot(zx, zy, zz)
191
+ if (l > 1e-8) { const inv = 1/l; zx*=inv; zy*=inv; zz*=inv }
192
+
193
+ const angAxes = con.cacheAngAxes
194
+ angAxes[0] = xx; angAxes[1] = xy; angAxes[2] = xz
195
+ angAxes[3] = yx; angAxes[4] = yy; angAxes[5] = yz
196
+ angAxes[6] = zx; angAxes[7] = zy; angAxes[8] = zz
197
+
198
+ const angDenom = iiA + iiB
199
+ con.cacheAngJacInv = angDenom > 0 ? 1 / angDenom : 0
200
+
201
+ const angTgt = con.cacheAngTargetVel
202
+ const angAct = con.cacheAngActive
203
+ for (let i = 0; i < 3; i++) {
204
+ const idx = i + 3
205
+ const lo = con.angularMin[i]
206
+ const hi = con.angularMax[i]
207
+ const curr = _angDiffScratch[i]
208
+ let target = 0
209
+ let active = 0
210
+ if (lo <= hi) {
211
+ let err = 0
212
+ if (curr < lo) err = curr - lo
213
+ else if (curr > hi) err = curr - hi
214
+ // Sign flip vs linear: d(angDiff)/dt = −(ω_B − ω_A)·ax.
215
+ if (err !== 0) {
216
+ target = err * STOP_ERP * invDt
217
+ active = 1
218
+ }
247
219
  }
220
+ if (con.springEnabled[idx]) {
221
+ target += con.springStiffness[idx] * (curr - con.equilibriumPoint[idx]) * dt
222
+ active = 1
223
+ }
224
+ angTgt[i] = target
225
+ angAct[i] = angDenom > 0 ? active : 0
248
226
  }
249
227
  }
250
228
 
251
- // Per-contact: one push-only normal row + two Coulomb friction rows
252
- // (impulse bound = ±μ·appliedNormalImpulse).
253
- function solveContactRow(
254
- c: Contact,
229
+ // ITER: read cache, compute relVel from current lv/av, apply impulse.
230
+ function iterateConstraint(
231
+ con: SixDofSpringConstraint,
255
232
  lv: Float32Array,
256
233
  av: Float32Array,
257
234
  invMass: Float32Array,
258
235
  invInertia: Float32Array,
259
236
  ): void {
260
- const ai = c.bodyA * 3, bi = c.bodyB * 3
261
- const imA = invMass[c.bodyA], imB = invMass[c.bodyB]
262
- const iiA = invInertia[c.bodyA], iiB = invInertia[c.bodyB]
263
- if (imA === 0 && imB === 0) return
264
- const rAx = c.rAx, rAy = c.rAy, rAz = c.rAz
265
- const rBx = c.rBx, rBy = c.rBy, rBz = c.rBz
266
- const nx = c.nx, ny = c.ny, nz = c.nz
237
+ if (con.cacheSkip) return
238
+ const a = con.bodyA
239
+ const b = con.bodyB
240
+ const ai = a * 3
241
+ const bi = b * 3
242
+ const imA = invMass[a]
243
+ const imB = invMass[b]
244
+ const iiA = invInertia[a]
245
+ const iiB = invInertia[b]
246
+
247
+ // Linear axes — relVel at the offset point: v_pivot = v_CG + ω × r.
248
+ const lA = con.cacheLeverA
249
+ const lB = con.cacheLeverB
250
+ const rAx = lA[0], rAy = lA[1], rAz = lA[2]
251
+ const rBx = lB[0], rBy = lB[1], rBz = lB[2]
252
+ const axes = con.cacheLinAxes
253
+ const cA = con.cacheLinCrossA
254
+ const cB = con.cacheLinCrossB
255
+ const jac = con.cacheLinJacInv
256
+ const tgt = con.cacheLinTargetVel
257
+ const act = con.cacheLinActive
267
258
 
268
259
  const vAx = lv[ai + 0] + av[ai + 1] * rAz - av[ai + 2] * rAy
269
260
  const vAy = lv[ai + 1] + av[ai + 2] * rAx - av[ai + 0] * rAz
@@ -271,11 +262,85 @@ function solveContactRow(
271
262
  const vBx = lv[bi + 0] + av[bi + 1] * rBz - av[bi + 2] * rBy
272
263
  const vBy = lv[bi + 1] + av[bi + 2] * rBx - av[bi + 0] * rBz
273
264
  const vBz = lv[bi + 2] + av[bi + 0] * rBy - av[bi + 1] * rBx
274
- const dvX = vBx - vAx
275
- const dvY = vBy - vAy
276
- const dvZ = vBz - vAz
265
+ const dvx = vBx - vAx
266
+ const dvy = vBy - vAy
267
+ const dvz = vBz - vAz
268
+
269
+ for (let i = 0; i < 3; i++) {
270
+ if (!act[i]) continue
271
+ const o = i * 3
272
+ const axx = axes[o + 0], axy = axes[o + 1], axz = axes[o + 2]
273
+ const relVel = dvx * axx + dvy * axy + dvz * axz
274
+ const j = (tgt[i] - relVel) * jac[i]
275
+ if (j === 0) continue
276
+ if (imA > 0) {
277
+ lv[ai + 0] -= j * imA * axx
278
+ lv[ai + 1] -= j * imA * axy
279
+ lv[ai + 2] -= j * imA * axz
280
+ av[ai + 0] -= j * iiA * cA[o + 0]
281
+ av[ai + 1] -= j * iiA * cA[o + 1]
282
+ av[ai + 2] -= j * iiA * cA[o + 2]
283
+ }
284
+ if (imB > 0) {
285
+ lv[bi + 0] += j * imB * axx
286
+ lv[bi + 1] += j * imB * axy
287
+ lv[bi + 2] += j * imB * axz
288
+ av[bi + 0] += j * iiB * cB[o + 0]
289
+ av[bi + 1] += j * iiB * cB[o + 1]
290
+ av[bi + 2] += j * iiB * cB[o + 2]
291
+ }
292
+ }
277
293
 
278
- // Normal row.
294
+ // Angular axes — relAv = ω_B − ω_A.
295
+ const angJacInv = con.cacheAngJacInv
296
+ if (angJacInv === 0) return
297
+ const angAxes = con.cacheAngAxes
298
+ const angTgt = con.cacheAngTargetVel
299
+ const angAct = con.cacheAngActive
300
+ const dax = av[bi + 0] - av[ai + 0]
301
+ const day = av[bi + 1] - av[ai + 1]
302
+ const daz = av[bi + 2] - av[ai + 2]
303
+ for (let i = 0; i < 3; i++) {
304
+ if (!angAct[i]) continue
305
+ const o = i * 3
306
+ const axx = angAxes[o + 0], axy = angAxes[o + 1], axz = angAxes[o + 2]
307
+ const relAv = dax * axx + day * axy + daz * axz
308
+ const j = (angTgt[i] - relAv) * angJacInv
309
+ if (j === 0) continue
310
+ if (iiA > 0) {
311
+ av[ai + 0] -= j * iiA * axx
312
+ av[ai + 1] -= j * iiA * axy
313
+ av[ai + 2] -= j * iiA * axz
314
+ }
315
+ if (iiB > 0) {
316
+ av[bi + 0] += j * iiB * axx
317
+ av[bi + 1] += j * iiB * axy
318
+ av[bi + 2] += j * iiB * axz
319
+ }
320
+ }
321
+ }
322
+
323
+ // SETUP: pre-compute Jacobians, friction basis, and the bounce reference
324
+ // from the *initial* closing velocity (Bullet's pattern — captures restitution
325
+ // before iter 1 zeroes out the approach).
326
+ function setupContactRow(
327
+ c: Contact,
328
+ lv: Float32Array,
329
+ av: Float32Array,
330
+ invMass: Float32Array,
331
+ invInertia: Float32Array,
332
+ ): void {
333
+ const ai = c.bodyA * 3
334
+ const bi = c.bodyB * 3
335
+ const imA = invMass[c.bodyA]
336
+ const imB = invMass[c.bodyB]
337
+ const iiA = invInertia[c.bodyA]
338
+ const iiB = invInertia[c.bodyB]
339
+ const rAx = c.rAx, rAy = c.rAy, rAz = c.rAz
340
+ const rBx = c.rBx, rBy = c.rBy, rBz = c.rBz
341
+ const nx = c.nx, ny = c.ny, nz = c.nz
342
+
343
+ // Normal Jacobian.
279
344
  const cAxN = rAy * nz - rAz * ny
280
345
  const cAyN = rAz * nx - rAx * nz
281
346
  const cAzN = rAx * ny - rAy * nx
@@ -285,90 +350,172 @@ function solveContactRow(
285
350
  const denomN = imA + imB +
286
351
  (cAxN * cAxN + cAyN * cAyN + cAzN * cAzN) * iiA +
287
352
  (cBxN * cBxN + cByN * cByN + cBzN * cBzN) * iiB
288
- if (denomN <= 0) return
289
- const jacInvN = 1 / denomN
290
-
291
- const relVelN = dvX * nx + dvY * ny + dvZ * nz
292
- // Position correction is handled directly in world.ts (split impulse).
293
- // Velocity row only removes approach + applies restitution above bounce.
294
- let bounce = 0
295
- if (c.restitution > 0 && relVelN < -BOUNCE_THRESHOLD) {
296
- bounce = -c.restitution * relVelN
297
- }
298
- let dImpN = (bounce - relVelN) * jacInvN
299
- const oldN = c.appliedNormalImpulse
300
- let newN = oldN + dImpN
301
- if (newN < 0) { newN = 0; dImpN = -oldN }
302
- c.appliedNormalImpulse = newN
353
+ c.cAxN = cAxN; c.cAyN = cAyN; c.cAzN = cAzN
354
+ c.cBxN = cBxN; c.cByN = cByN; c.cBzN = cBzN
355
+ c.jacInvN = denomN > 0 ? 1 / denomN : 0
303
356
 
304
- if (dImpN !== 0) {
305
- if (imA > 0) {
306
- lv[ai + 0] -= dImpN * imA * nx
307
- lv[ai + 1] -= dImpN * imA * ny
308
- lv[ai + 2] -= dImpN * imA * nz
309
- av[ai + 0] -= dImpN * iiA * cAxN
310
- av[ai + 1] -= dImpN * iiA * cAyN
311
- av[ai + 2] -= dImpN * iiA * cAzN
312
- }
313
- if (imB > 0) {
314
- lv[bi + 0] += dImpN * imB * nx
315
- lv[bi + 1] += dImpN * imB * ny
316
- lv[bi + 2] += dImpN * imB * nz
317
- av[bi + 0] += dImpN * iiB * cBxN
318
- av[bi + 1] += dImpN * iiB * cByN
319
- av[bi + 2] += dImpN * iiB * cBzN
320
- }
321
- }
357
+ // Restitution reference, captured from initial relVelN.
358
+ const vAx = lv[ai + 0] + av[ai + 1] * rAz - av[ai + 2] * rAy
359
+ const vAy = lv[ai + 1] + av[ai + 2] * rAx - av[ai + 0] * rAz
360
+ const vAz = lv[ai + 2] + av[ai + 0] * rAy - av[ai + 1] * rAx
361
+ const vBx = lv[bi + 0] + av[bi + 1] * rBz - av[bi + 2] * rBy
362
+ const vBy = lv[bi + 1] + av[bi + 2] * rBx - av[bi + 0] * rBz
363
+ const vBz = lv[bi + 2] + av[bi + 0] * rBy - av[bi + 1] * rBx
364
+ const relVelN0 = (vBx - vAx) * nx + (vBy - vAy) * ny + (vBz - vAz) * nz
365
+ c.bounceVel = c.restitution > 0 && relVelN0 < -BOUNCE_THRESHOLD
366
+ ? -c.restitution * relVelN0
367
+ : 0
322
368
 
323
- // Friction tangent basis. Pick the axis least aligned with n to avoid a
324
- // near-zero cross product.
369
+ // Friction tangent basis. Pick the axis least aligned with n.
325
370
  let t1x: number, t1y: number, t1z: number
326
371
  if (Math.abs(nx) < 0.7071) { t1x = 0; t1y = -nz; t1z = ny }
327
372
  else { t1x = nz; t1y = 0; t1z = -nx }
328
- const l = Math.hypot(t1x, t1y, t1z)
329
- if (l < 1e-8) return
330
- const tInv = 1 / l
331
- t1x *= tInv; t1y *= tInv; t1z *= tInv
373
+ const tl = Math.hypot(t1x, t1y, t1z)
374
+ if (tl > 1e-8) {
375
+ const tInv = 1 / tl
376
+ t1x *= tInv; t1y *= tInv; t1z *= tInv
377
+ } else {
378
+ c.jacInvT1 = 0; c.jacInvT2 = 0
379
+ return
380
+ }
332
381
  const t2x = ny * t1z - nz * t1y
333
382
  const t2y = nz * t1x - nx * t1z
334
383
  const t2z = nx * t1y - ny * t1x
384
+ c.t1x = t1x; c.t1y = t1y; c.t1z = t1z
385
+ c.t2x = t2x; c.t2y = t2y; c.t2z = t2z
386
+
387
+ // Friction Jacobians.
388
+ const cAxT1 = rAy * t1z - rAz * t1y
389
+ const cAyT1 = rAz * t1x - rAx * t1z
390
+ const cAzT1 = rAx * t1y - rAy * t1x
391
+ const cBxT1 = rBy * t1z - rBz * t1y
392
+ const cByT1 = rBz * t1x - rBx * t1z
393
+ const cBzT1 = rBx * t1y - rBy * t1x
394
+ const denomT1 = imA + imB +
395
+ (cAxT1 * cAxT1 + cAyT1 * cAyT1 + cAzT1 * cAzT1) * iiA +
396
+ (cBxT1 * cBxT1 + cByT1 * cByT1 + cBzT1 * cBzT1) * iiB
397
+ c.cAxT1 = cAxT1; c.cAyT1 = cAyT1; c.cAzT1 = cAzT1
398
+ c.cBxT1 = cBxT1; c.cByT1 = cByT1; c.cBzT1 = cBzT1
399
+ c.jacInvT1 = denomT1 > 0 ? 1 / denomT1 : 0
400
+
401
+ const cAxT2 = rAy * t2z - rAz * t2y
402
+ const cAyT2 = rAz * t2x - rAx * t2z
403
+ const cAzT2 = rAx * t2y - rAy * t2x
404
+ const cBxT2 = rBy * t2z - rBz * t2y
405
+ const cByT2 = rBz * t2x - rBx * t2z
406
+ const cBzT2 = rBx * t2y - rBy * t2x
407
+ const denomT2 = imA + imB +
408
+ (cAxT2 * cAxT2 + cAyT2 * cAyT2 + cAzT2 * cAzT2) * iiA +
409
+ (cBxT2 * cBxT2 + cByT2 * cByT2 + cBzT2 * cBzT2) * iiB
410
+ c.cAxT2 = cAxT2; c.cAyT2 = cAyT2; c.cAzT2 = cAzT2
411
+ c.cBxT2 = cBxT2; c.cByT2 = cByT2; c.cBzT2 = cBzT2
412
+ c.jacInvT2 = denomT2 > 0 ? 1 / denomT2 : 0
413
+ }
414
+
415
+ // ITER: one push-only normal row + two Coulomb friction rows. Friction
416
+ // bound depends on the *current* applied normal impulse, so it tightens
417
+ // as the normal row converges.
418
+ function iterateContactRow(
419
+ c: Contact,
420
+ lv: Float32Array,
421
+ av: Float32Array,
422
+ invMass: Float32Array,
423
+ invInertia: Float32Array,
424
+ ): void {
425
+ const imA = invMass[c.bodyA]
426
+ const imB = invMass[c.bodyB]
427
+ if (imA === 0 && imB === 0) return
428
+ const iiA = invInertia[c.bodyA]
429
+ const iiB = invInertia[c.bodyB]
430
+ const ai = c.bodyA * 3, bi = c.bodyB * 3
431
+ const rAx = c.rAx, rAy = c.rAy, rAz = c.rAz
432
+ const rBx = c.rBx, rBy = c.rBy, rBz = c.rBz
433
+
434
+ const vAx = lv[ai + 0] + av[ai + 1] * rAz - av[ai + 2] * rAy
435
+ const vAy = lv[ai + 1] + av[ai + 2] * rAx - av[ai + 0] * rAz
436
+ const vAz = lv[ai + 2] + av[ai + 0] * rAy - av[ai + 1] * rAx
437
+ const vBx = lv[bi + 0] + av[bi + 1] * rBz - av[bi + 2] * rBy
438
+ const vBy = lv[bi + 1] + av[bi + 2] * rBx - av[bi + 0] * rBz
439
+ const vBz = lv[bi + 2] + av[bi + 0] * rBy - av[bi + 1] * rBx
440
+ const dvx = vBx - vAx
441
+ const dvy = vBy - vAy
442
+ const dvz = vBz - vAz
335
443
 
444
+ // Normal row.
445
+ const jacInvN = c.jacInvN
446
+ if (jacInvN > 0) {
447
+ const nx = c.nx, ny = c.ny, nz = c.nz
448
+ const relVelN = dvx * nx + dvy * ny + dvz * nz
449
+ let dImpN = (c.bounceVel - relVelN) * jacInvN
450
+ const oldN = c.appliedNormalImpulse
451
+ let newN = oldN + dImpN
452
+ if (newN < 0) { newN = 0; dImpN = -oldN }
453
+ c.appliedNormalImpulse = newN
454
+ if (dImpN !== 0) {
455
+ const cAxN = c.cAxN, cAyN = c.cAyN, cAzN = c.cAzN
456
+ const cBxN = c.cBxN, cByN = c.cByN, cBzN = c.cBzN
457
+ if (imA > 0) {
458
+ lv[ai + 0] -= dImpN * imA * nx
459
+ lv[ai + 1] -= dImpN * imA * ny
460
+ lv[ai + 2] -= dImpN * imA * nz
461
+ av[ai + 0] -= dImpN * iiA * cAxN
462
+ av[ai + 1] -= dImpN * iiA * cAyN
463
+ av[ai + 2] -= dImpN * iiA * cAzN
464
+ }
465
+ if (imB > 0) {
466
+ lv[bi + 0] += dImpN * imB * nx
467
+ lv[bi + 1] += dImpN * imB * ny
468
+ lv[bi + 2] += dImpN * imB * nz
469
+ av[bi + 0] += dImpN * iiB * cBxN
470
+ av[bi + 1] += dImpN * iiB * cByN
471
+ av[bi + 2] += dImpN * iiB * cBzN
472
+ }
473
+ }
474
+ }
475
+
476
+ // Friction. Bound = ±μ · current normal impulse.
336
477
  const muNormal = c.friction * c.appliedNormalImpulse
478
+ if (muNormal <= 0) return
337
479
 
338
- applyFrictionRow(c, ai, bi, t1x, t1y, t1z,
339
- rAx, rAy, rAz, rBx, rBy, rBz, dvX, dvY, dvZ,
340
- imA, imB, iiA, iiB, lv, av, muNormal, 1)
341
- applyFrictionRow(c, ai, bi, t2x, t2y, t2z,
342
- rAx, rAy, rAz, rBx, rBy, rBz, dvX, dvY, dvZ,
343
- imA, imB, iiA, iiB, lv, av, muNormal, 2)
480
+ // Re-read dv after the normal impulse possibly changed lv/av.
481
+ const vAx2 = lv[ai + 0] + av[ai + 1] * rAz - av[ai + 2] * rAy
482
+ const vAy2 = lv[ai + 1] + av[ai + 2] * rAx - av[ai + 0] * rAz
483
+ const vAz2 = lv[ai + 2] + av[ai + 0] * rAy - av[ai + 1] * rAx
484
+ const vBx2 = lv[bi + 0] + av[bi + 1] * rBz - av[bi + 2] * rBy
485
+ const vBy2 = lv[bi + 1] + av[bi + 2] * rBx - av[bi + 0] * rBz
486
+ const vBz2 = lv[bi + 2] + av[bi + 0] * rBy - av[bi + 1] * rBx
487
+ const dvx2 = vBx2 - vAx2
488
+ const dvy2 = vBy2 - vAy2
489
+ const dvz2 = vBz2 - vAz2
490
+
491
+ applyFrictionTangent(
492
+ c, ai, bi, dvx2, dvy2, dvz2,
493
+ c.t1x, c.t1y, c.t1z,
494
+ c.cAxT1, c.cAyT1, c.cAzT1, c.cBxT1, c.cByT1, c.cBzT1,
495
+ c.jacInvT1, muNormal, imA, imB, iiA, iiB, lv, av, 1,
496
+ )
497
+ applyFrictionTangent(
498
+ c, ai, bi, dvx2, dvy2, dvz2,
499
+ c.t2x, c.t2y, c.t2z,
500
+ c.cAxT2, c.cAyT2, c.cAzT2, c.cBxT2, c.cByT2, c.cBzT2,
501
+ c.jacInvT2, muNormal, imA, imB, iiA, iiB, lv, av, 2,
502
+ )
344
503
  }
345
504
 
346
- function applyFrictionRow(
505
+ function applyFrictionTangent(
347
506
  c: Contact,
348
507
  ai: number, bi: number,
508
+ dvx: number, dvy: number, dvz: number,
349
509
  tx: number, ty: number, tz: number,
350
- rAx: number, rAy: number, rAz: number,
351
- rBx: number, rBy: number, rBz: number,
352
- dvX: number, dvY: number, dvZ: number,
510
+ cAx: number, cAy: number, cAz: number,
511
+ cBx: number, cBy: number, cBz: number,
512
+ jacInv: number, muNormal: number,
353
513
  imA: number, imB: number, iiA: number, iiB: number,
354
514
  lv: Float32Array, av: Float32Array,
355
- muNormal: number,
356
515
  slot: 1 | 2,
357
516
  ): void {
358
- if (muNormal <= 0) return
359
- const cAx = rAy * tz - rAz * ty
360
- const cAy = rAz * tx - rAx * tz
361
- const cAz = rAx * ty - rAy * tx
362
- const cBx = rBy * tz - rBz * ty
363
- const cBy = rBz * tx - rBx * tz
364
- const cBz = rBx * ty - rBy * tx
365
- const denom = imA + imB +
366
- (cAx * cAx + cAy * cAy + cAz * cAz) * iiA +
367
- (cBx * cBx + cBy * cBy + cBz * cBz) * iiB
368
- if (denom <= 0) return
369
- const jacInv = 1 / denom
370
-
371
- const relVel = dvX * tx + dvY * ty + dvZ * tz
517
+ if (jacInv <= 0) return
518
+ const relVel = dvx * tx + dvy * ty + dvz * tz
372
519
  let dImp = -relVel * jacInv
373
520
  const old = slot === 1 ? c.appliedFrictionImpulse1 : c.appliedFrictionImpulse2
374
521
  let next = old + dImp