@aura3d/animation 3.0.0 → 4.0.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 (188) hide show
  1. package/dist/AnimationController.d.ts +8 -0
  2. package/dist/AnimationController.d.ts.map +1 -1
  3. package/dist/AnimationController.js +93 -0
  4. package/dist/AnimationController.js.map +1 -1
  5. package/dist/AnimationMixer.d.ts +4 -1
  6. package/dist/AnimationMixer.d.ts.map +1 -1
  7. package/dist/AnimationMixer.js +47 -29
  8. package/dist/AnimationMixer.js.map +1 -1
  9. package/dist/FootIk.d.ts +50 -0
  10. package/dist/FootIk.d.ts.map +1 -1
  11. package/dist/FootIk.js +224 -8
  12. package/dist/FootIk.js.map +1 -1
  13. package/dist/HumanoidRetargeting.d.ts +6 -2
  14. package/dist/HumanoidRetargeting.d.ts.map +1 -1
  15. package/dist/HumanoidRetargeting.js +18 -8
  16. package/dist/HumanoidRetargeting.js.map +1 -1
  17. package/dist/IK.d.ts +26 -0
  18. package/dist/IK.d.ts.map +1 -1
  19. package/dist/IK.js +515 -0
  20. package/dist/IK.js.map +1 -1
  21. package/dist/Keyframe.d.ts.map +1 -1
  22. package/dist/Keyframe.js +13 -12
  23. package/dist/Keyframe.js.map +1 -1
  24. package/dist/LocomotionEvidence.d.ts +34 -0
  25. package/dist/LocomotionEvidence.d.ts.map +1 -0
  26. package/dist/LocomotionEvidence.js +48 -0
  27. package/dist/LocomotionEvidence.js.map +1 -0
  28. package/dist/RootMotion.d.ts +13 -4
  29. package/dist/RootMotion.d.ts.map +1 -1
  30. package/dist/RootMotion.js +21 -1
  31. package/dist/RootMotion.js.map +1 -1
  32. package/dist/SpringBones.d.ts +80 -0
  33. package/dist/SpringBones.d.ts.map +1 -1
  34. package/dist/SpringBones.js +593 -49
  35. package/dist/SpringBones.js.map +1 -1
  36. package/dist/browser-index.d.ts +1 -1
  37. package/dist/browser-index.d.ts.map +1 -1
  38. package/dist/browser-index.js +1 -1
  39. package/dist/browser-index.js.map +1 -1
  40. package/dist/contracts/index.d.ts +2 -0
  41. package/dist/contracts/index.d.ts.map +1 -0
  42. package/dist/contracts/index.js +7 -0
  43. package/dist/contracts/index.js.map +1 -0
  44. package/dist/contracts/pose.d.ts +25 -0
  45. package/dist/contracts/pose.d.ts.map +1 -0
  46. package/dist/contracts/pose.js +41 -0
  47. package/dist/contracts/pose.js.map +1 -0
  48. package/dist/experimental/CrowdAnimation.d.ts +13 -0
  49. package/dist/experimental/CrowdAnimation.d.ts.map +1 -0
  50. package/dist/experimental/CrowdAnimation.js +14 -0
  51. package/dist/experimental/CrowdAnimation.js.map +1 -0
  52. package/dist/index.d.ts +2 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +2 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/lanes/index.d.ts +17 -0
  57. package/dist/lanes/index.d.ts.map +1 -0
  58. package/dist/lanes/index.js +17 -0
  59. package/dist/lanes/index.js.map +1 -0
  60. package/dist/lanes/prd01.d.ts +6 -0
  61. package/dist/lanes/prd01.d.ts.map +1 -0
  62. package/dist/lanes/prd01.js +6 -0
  63. package/dist/lanes/prd01.js.map +1 -0
  64. package/dist/lanes/prd02.d.ts +6 -0
  65. package/dist/lanes/prd02.d.ts.map +1 -0
  66. package/dist/lanes/prd02.js +6 -0
  67. package/dist/lanes/prd02.js.map +1 -0
  68. package/dist/lanes/prd03.d.ts +6 -0
  69. package/dist/lanes/prd03.d.ts.map +1 -0
  70. package/dist/lanes/prd03.js +6 -0
  71. package/dist/lanes/prd03.js.map +1 -0
  72. package/dist/lanes/prd04.d.ts +6 -0
  73. package/dist/lanes/prd04.d.ts.map +1 -0
  74. package/dist/lanes/prd04.js +6 -0
  75. package/dist/lanes/prd04.js.map +1 -0
  76. package/dist/lanes/prd05.d.ts +6 -0
  77. package/dist/lanes/prd05.d.ts.map +1 -0
  78. package/dist/lanes/prd05.js +6 -0
  79. package/dist/lanes/prd05.js.map +1 -0
  80. package/dist/lanes/prd06.d.ts +37 -0
  81. package/dist/lanes/prd06.d.ts.map +1 -0
  82. package/dist/lanes/prd06.js +26 -0
  83. package/dist/lanes/prd06.js.map +1 -0
  84. package/dist/lanes/prd07.d.ts +6 -0
  85. package/dist/lanes/prd07.d.ts.map +1 -0
  86. package/dist/lanes/prd07.js +6 -0
  87. package/dist/lanes/prd07.js.map +1 -0
  88. package/dist/lanes/prd08.d.ts +6 -0
  89. package/dist/lanes/prd08.d.ts.map +1 -0
  90. package/dist/lanes/prd08.js +6 -0
  91. package/dist/lanes/prd08.js.map +1 -0
  92. package/dist/lanes/prd09.d.ts +6 -0
  93. package/dist/lanes/prd09.d.ts.map +1 -0
  94. package/dist/lanes/prd09.js +6 -0
  95. package/dist/lanes/prd09.js.map +1 -0
  96. package/dist/lanes/prd10.d.ts +6 -0
  97. package/dist/lanes/prd10.d.ts.map +1 -0
  98. package/dist/lanes/prd10.js +6 -0
  99. package/dist/lanes/prd10.js.map +1 -0
  100. package/dist/lanes/prd11.d.ts +6 -0
  101. package/dist/lanes/prd11.d.ts.map +1 -0
  102. package/dist/lanes/prd11.js +6 -0
  103. package/dist/lanes/prd11.js.map +1 -0
  104. package/dist/lanes/prd12.d.ts +6 -0
  105. package/dist/lanes/prd12.d.ts.map +1 -0
  106. package/dist/lanes/prd12.js +6 -0
  107. package/dist/lanes/prd12.js.map +1 -0
  108. package/dist/lanes/prd13.d.ts +6 -0
  109. package/dist/lanes/prd13.d.ts.map +1 -0
  110. package/dist/lanes/prd13.js +6 -0
  111. package/dist/lanes/prd13.js.map +1 -0
  112. package/dist/lanes/prd14.d.ts +6 -0
  113. package/dist/lanes/prd14.d.ts.map +1 -0
  114. package/dist/lanes/prd14.js +6 -0
  115. package/dist/lanes/prd14.js.map +1 -0
  116. package/dist/lanes/prd15.d.ts +6 -0
  117. package/dist/lanes/prd15.d.ts.map +1 -0
  118. package/dist/lanes/prd15.js +6 -0
  119. package/dist/lanes/prd15.js.map +1 -0
  120. package/dist/pose/BoneMask.d.ts +39 -0
  121. package/dist/pose/BoneMask.d.ts.map +1 -0
  122. package/dist/pose/BoneMask.js +160 -0
  123. package/dist/pose/BoneMask.js.map +1 -0
  124. package/dist/pose/CcdIkConstraint.d.ts +38 -0
  125. package/dist/pose/CcdIkConstraint.d.ts.map +1 -0
  126. package/dist/pose/CcdIkConstraint.js +198 -0
  127. package/dist/pose/CcdIkConstraint.js.map +1 -0
  128. package/dist/pose/CompiledClip.d.ts +48 -0
  129. package/dist/pose/CompiledClip.d.ts.map +1 -0
  130. package/dist/pose/CompiledClip.js +209 -0
  131. package/dist/pose/CompiledClip.js.map +1 -0
  132. package/dist/pose/LookAtConstraint.d.ts +48 -0
  133. package/dist/pose/LookAtConstraint.d.ts.map +1 -0
  134. package/dist/pose/LookAtConstraint.js +251 -0
  135. package/dist/pose/LookAtConstraint.js.map +1 -0
  136. package/dist/pose/MotionMetrics.d.ts +136 -0
  137. package/dist/pose/MotionMetrics.d.ts.map +1 -0
  138. package/dist/pose/MotionMetrics.js +253 -0
  139. package/dist/pose/MotionMetrics.js.map +1 -0
  140. package/dist/pose/PoseBuffer.d.ts +23 -0
  141. package/dist/pose/PoseBuffer.d.ts.map +1 -0
  142. package/dist/pose/PoseBuffer.js +48 -0
  143. package/dist/pose/PoseBuffer.js.map +1 -0
  144. package/dist/pose/PoseInertializer.d.ts +38 -0
  145. package/dist/pose/PoseInertializer.d.ts.map +1 -0
  146. package/dist/pose/PoseInertializer.js +208 -0
  147. package/dist/pose/PoseInertializer.js.map +1 -0
  148. package/dist/pose/PoseMixer.d.ts +256 -0
  149. package/dist/pose/PoseMixer.d.ts.map +1 -0
  150. package/dist/pose/PoseMixer.js +848 -0
  151. package/dist/pose/PoseMixer.js.map +1 -0
  152. package/dist/pose/Retarget.d.ts +77 -0
  153. package/dist/pose/Retarget.d.ts.map +1 -0
  154. package/dist/pose/Retarget.js +378 -0
  155. package/dist/pose/Retarget.js.map +1 -0
  156. package/dist/pose/RetargetCache.d.ts +21 -0
  157. package/dist/pose/RetargetCache.d.ts.map +1 -0
  158. package/dist/pose/RetargetCache.js +102 -0
  159. package/dist/pose/RetargetCache.js.map +1 -0
  160. package/dist/pose/RetargetWorker.d.ts +21 -0
  161. package/dist/pose/RetargetWorker.d.ts.map +1 -0
  162. package/dist/pose/RetargetWorker.js +56 -0
  163. package/dist/pose/RetargetWorker.js.map +1 -0
  164. package/dist/pose/SkeletonBinding.d.ts +55 -0
  165. package/dist/pose/SkeletonBinding.d.ts.map +1 -0
  166. package/dist/pose/SkeletonBinding.js +62 -0
  167. package/dist/pose/SkeletonBinding.js.map +1 -0
  168. package/dist/pose/blendKernels.d.ts +32 -0
  169. package/dist/pose/blendKernels.d.ts.map +1 -0
  170. package/dist/pose/blendKernels.js +101 -0
  171. package/dist/pose/blendKernels.js.map +1 -0
  172. package/dist/pose/makeClipAdditive.d.ts +22 -0
  173. package/dist/pose/makeClipAdditive.d.ts.map +1 -0
  174. package/dist/pose/makeClipAdditive.js +101 -0
  175. package/dist/pose/makeClipAdditive.js.map +1 -0
  176. package/dist/pose/poseMixerFlags.d.ts +18 -0
  177. package/dist/pose/poseMixerFlags.d.ts.map +1 -0
  178. package/dist/pose/poseMixerFlags.js +30 -0
  179. package/dist/pose/poseMixerFlags.js.map +1 -0
  180. package/dist/pose/quatFlat.d.ts +16 -0
  181. package/dist/pose/quatFlat.d.ts.map +1 -0
  182. package/dist/pose/quatFlat.js +88 -0
  183. package/dist/pose/quatFlat.js.map +1 -0
  184. package/dist/pose/retarget.worker.d.ts +25 -0
  185. package/dist/pose/retarget.worker.d.ts.map +1 -0
  186. package/dist/pose/retarget.worker.js +22 -0
  187. package/dist/pose/retarget.worker.js.map +1 -0
  188. package/package.json +14 -2
@@ -22,91 +22,268 @@ export function createSpringChain(options) {
22
22
  const gravity = options.gravity ?? [0, -9.81, 0];
23
23
  const colliders = options.colliders ?? [];
24
24
  const substeps = Math.max(1, Math.floor(options.substeps ?? 2));
25
+ const relativeDamping = Math.max(0, options.relativeDamping ?? 0);
25
26
  const name = options.name ?? "spring-chain";
26
27
  // Rest offset of each particle relative to its parent, in the root's initial frame.
27
- const restOffsets = options.bones.map((bone, i) => i === 0 ? [0, 0, 0] : sub(bone, options.bones[i - 1]));
28
- const restLengths = restOffsets.map((o) => len(o));
29
- const positions = options.bones.map((b) => [...b]);
30
- const velocities = options.bones.map(() => [0, 0, 0]);
28
+ const restOffsets = new Float32Array(options.bones.length * 3);
29
+ for (let i = 0; i < options.bones.length; i += 1) {
30
+ const bone = options.bones[i];
31
+ restOffsets[i * 3] = i === 0 ? 0 : bone[0] - options.bones[i - 1][0];
32
+ restOffsets[i * 3 + 1] = i === 0 ? 0 : bone[1] - options.bones[i - 1][1];
33
+ restOffsets[i * 3 + 2] = i === 0 ? 0 : bone[2] - options.bones[i - 1][2];
34
+ }
35
+ const restLengths = new Float32Array(options.bones.length);
36
+ for (let i = 0; i < options.bones.length; i += 1) {
37
+ restLengths[i] = Math.hypot(restOffsets[i * 3], restOffsets[i * 3 + 1], restOffsets[i * 3 + 2]);
38
+ }
39
+ // §13 — flat particle state so `step` performs scalar math with no per-call
40
+ // allocation (the Vec3-of-arrays version measured ~5× over the spring
41
+ // micro-budget).
42
+ const count = options.bones.length;
43
+ const positions = new Float32Array(count * 3);
44
+ const velocities = new Float32Array(count * 3);
45
+ for (let i = 0; i < count; i += 1) {
46
+ const bone = options.bones[i];
47
+ positions[i * 3] = bone[0];
48
+ positions[i * 3 + 1] = bone[1];
49
+ positions[i * 3 + 2] = bone[2];
50
+ }
31
51
  let contacts = 0;
32
- function rotatedRestOffset(i, rotation) {
33
- return rotation ? rotateVec3(restOffsets[i], rotation) : restOffsets[i];
52
+ const anchorSpace = options.space === "anchor";
53
+ // Anchor rotation at the previous step (anchor space only).
54
+ const prevAnchorRot = new Float64Array([0, 0, 0, 1]);
55
+ /**
56
+ * Anchor space: carry every simulated particle and its velocity rigidly
57
+ * with the root's motion since the last step (translation + delta
58
+ * rotation about the root), before the spring/gravity update.
59
+ */
60
+ function transportWithAnchor(root) {
61
+ const q = root.rotation;
62
+ let dx = 0, dy = 0, dz = 0, dw = 1;
63
+ {
64
+ const qx = q?.[0] ?? 0, qy = q?.[1] ?? 0, qz = q?.[2] ?? 0, qw = q?.[3] ?? 1;
65
+ // dq = q * conj(prev)
66
+ const bx = -prevAnchorRot[0], by = -prevAnchorRot[1], bz = -prevAnchorRot[2], bw = prevAnchorRot[3];
67
+ dx = qx * bw + qw * bx + qy * bz - qz * by;
68
+ dy = qy * bw + qw * by + qz * bx - qx * bz;
69
+ dz = qz * bw + qw * bz + qx * by - qy * bx;
70
+ dw = qw * bw - qx * bx - qy * by - qz * bz;
71
+ }
72
+ const ox = positions[0], oy = positions[1], oz = positions[2];
73
+ const rx = root.position[0], ry = root.position[1], rz = root.position[2];
74
+ for (let i = 1; i < count; i += 1) {
75
+ const ci = i * 3;
76
+ // Scalar quaternion rotation keeps the hot transport path allocation-free.
77
+ const px = positions[ci] - ox, py = positions[ci + 1] - oy, pz = positions[ci + 2] - oz;
78
+ const tx = 2 * (dy * pz - dz * py), ty = 2 * (dz * px - dx * pz), tz = 2 * (dx * py - dy * px);
79
+ positions[ci] = rx + px + dw * tx + dy * tz - dz * ty;
80
+ positions[ci + 1] = ry + py + dw * ty + dz * tx - dx * tz;
81
+ positions[ci + 2] = rz + pz + dw * tz + dx * ty - dy * tx;
82
+ const vx = velocities[ci], vy = velocities[ci + 1], vz = velocities[ci + 2];
83
+ const ux = 2 * (dy * vz - dz * vy), uy = 2 * (dz * vx - dx * vz), uz = 2 * (dx * vy - dy * vx);
84
+ velocities[ci] = vx + dw * ux + dy * uz - dz * uy;
85
+ velocities[ci + 1] = vy + dw * uy + dz * ux - dx * uz;
86
+ velocities[ci + 2] = vz + dw * uz + dx * uy - dy * ux;
87
+ }
88
+ prevAnchorRot[0] = q?.[0] ?? 0;
89
+ prevAnchorRot[1] = q?.[1] ?? 0;
90
+ prevAnchorRot[2] = q?.[2] ?? 0;
91
+ prevAnchorRot[3] = q?.[3] ?? 1;
34
92
  }
35
93
  function step(dt, root) {
36
94
  contacts = 0;
37
- // Root particle is kinematic.
38
- positions[0] = [...root.position];
39
- velocities[0] = [0, 0, 0];
40
- for (let i = 1; i < positions.length; i += 1) {
41
- const parent = positions[i - 1];
42
- const rest = add(parent, rotatedRestOffset(i, root.rotation));
43
- const p = positions[i];
44
- const v = velocities[i];
95
+ const q = root.rotation;
96
+ const offs = root.offsets;
97
+ if (anchorSpace)
98
+ transportWithAnchor(root);
99
+ // Root particle is kinematic (zero velocity: in anchor space it is the
100
+ // frame origin; in model space this is the legacy behaviour).
101
+ velocities[0] = 0;
102
+ velocities[1] = 0;
103
+ velocities[2] = 0;
104
+ positions[0] = root.position[0];
105
+ positions[1] = root.position[1];
106
+ positions[2] = root.position[2];
107
+ for (let i = 1; i < count; i += 1) {
108
+ const pi = (i - 1) * 3;
109
+ const ci = i * 3;
110
+ // Rest target = parent position + rest offset: the animated offset
111
+ // when supplied, else the bind offset rotated into the root frame.
112
+ let ox, oy, oz;
113
+ if (offs !== undefined) {
114
+ ox = offs[ci];
115
+ oy = offs[ci + 1];
116
+ oz = offs[ci + 2];
117
+ }
118
+ else {
119
+ ox = restOffsets[ci];
120
+ oy = restOffsets[ci + 1];
121
+ oz = restOffsets[ci + 2];
122
+ if (q !== undefined) {
123
+ const ix = q[3] * ox + q[1] * oz - q[2] * oy;
124
+ const iy = q[3] * oy + q[2] * ox - q[0] * oz;
125
+ const iz = q[3] * oz + q[0] * oy - q[1] * ox;
126
+ const iw = -q[0] * ox - q[1] * oy - q[2] * oz;
127
+ ox = ix * q[3] + iw * -q[0] + iy * -q[2] - iz * -q[1];
128
+ oy = iy * q[3] + iw * -q[1] + iz * -q[0] - ix * -q[2];
129
+ oz = iz * q[3] + iw * -q[2] + ix * -q[1] - iy * -q[0];
130
+ }
131
+ }
132
+ const restLength = offs !== undefined ? Math.hypot(ox, oy, oz) : restLengths[i];
133
+ const rx = positions[pi] + ox;
134
+ const ry = positions[pi + 1] + oy;
135
+ const rz = positions[pi + 2] + oz;
136
+ const px = positions[ci], py = positions[ci + 1], pz = positions[ci + 2];
137
+ const vx = velocities[ci], vy = velocities[ci + 1], vz = velocities[ci + 2];
45
138
  // a = stiffness*(rest - p) + gravity - damping*v (semi-implicit: update v, then p)
46
- const ax = stiffness * (rest[0] - p[0]) + gravity[0] - damping * v[0];
47
- const ay = stiffness * (rest[1] - p[1]) + gravity[1] - damping * v[1];
48
- const az = stiffness * (rest[2] - p[2]) + gravity[2] - damping * v[2];
49
- const nv = [v[0] + ax * dt, v[1] + ay * dt, v[2] + az * dt];
50
- let np = [p[0] + nv[0] * dt, p[1] + nv[1] * dt, p[2] + nv[2] * dt];
139
+ const ax = stiffness * (rx - px) + gravity[0] - damping * vx;
140
+ const ay = stiffness * (ry - py) + gravity[1] - damping * vy;
141
+ const az = stiffness * (rz - pz) + gravity[2] - damping * vz;
142
+ let nvx = vx + ax * dt;
143
+ let nvy = vy + ay * dt;
144
+ let nvz = vz + az * dt;
145
+ let npx = px + nvx * dt;
146
+ let npy = py + nvy * dt;
147
+ let npz = pz + nvz * dt;
51
148
  // Distance constraint: keep the bone length to the parent.
52
- np = enforceLength(parent, np, restLengths[i]);
53
- // Collider push-out.
149
+ {
150
+ const dx = npx - positions[pi], dy = npy - positions[pi + 1], dz = npz - positions[pi + 2];
151
+ const l = Math.hypot(dx, dy, dz);
152
+ if (l <= 1e-8) {
153
+ npx = positions[pi];
154
+ npy = positions[pi + 1] + restLength;
155
+ npz = positions[pi + 2];
156
+ }
157
+ else {
158
+ const s = restLength / l;
159
+ npx = positions[pi] + dx * s;
160
+ npy = positions[pi + 1] + dy * s;
161
+ npz = positions[pi + 2] + dz * s;
162
+ }
163
+ }
164
+ // Collider push-out (scalar form of pushOut/closestPointOnSegment).
54
165
  for (const collider of colliders) {
55
- const resolved = pushOut(np, collider);
56
- if (resolved) {
57
- np = resolved.position;
58
- // Remove the inward velocity component so it doesn't tunnel back in.
59
- const dot = nv[0] * resolved.normal[0] + nv[1] * resolved.normal[1] + nv[2] * resolved.normal[2];
60
- if (dot < 0) {
61
- nv[0] -= dot * resolved.normal[0];
62
- nv[1] -= dot * resolved.normal[1];
63
- nv[2] -= dot * resolved.normal[2];
64
- }
65
- contacts += 1;
166
+ let cx = collider.center[0], cy = collider.center[1], cz = collider.center[2];
167
+ if (collider.kind === "capsule" && collider.tail) {
168
+ const abx = collider.tail[0] - cx, aby = collider.tail[1] - cy, abz = collider.tail[2] - cz;
169
+ const t = clamp(((npx - cx) * abx + (npy - cy) * aby + (npz - cz) * abz) / Math.max(1e-8, abx * abx + aby * aby + abz * abz), 0, 1);
170
+ cx += abx * t;
171
+ cy += aby * t;
172
+ cz += abz * t;
66
173
  }
174
+ const dx = npx - cx, dy = npy - cy, dz = npz - cz;
175
+ const l = Math.hypot(dx, dy, dz);
176
+ if (l >= collider.radius)
177
+ continue;
178
+ const inx = l <= 1e-8 ? 0 : dx / l;
179
+ const iny = l <= 1e-8 ? 1 : dy / l;
180
+ const inz = l <= 1e-8 ? 0 : dz / l;
181
+ npx = cx + inx * collider.radius;
182
+ npy = cy + iny * collider.radius;
183
+ npz = cz + inz * collider.radius;
184
+ // Remove the inward velocity component so it doesn't tunnel back in.
185
+ const d = nvx * inx + nvy * iny + nvz * inz;
186
+ if (d < 0) {
187
+ nvx -= d * inx;
188
+ nvy -= d * iny;
189
+ nvz -= d * inz;
190
+ }
191
+ contacts += 1;
67
192
  }
68
193
  // Recompute velocity from the constrained position so energy bookkeeping stays consistent.
69
- velocities[i] = [(np[0] - p[0]) / dt, (np[1] - p[1]) / dt, (np[2] - p[2]) / dt];
70
- positions[i] = np;
194
+ let evx = (npx - px) / dt;
195
+ let evy = (npy - py) / dt;
196
+ let evz = (npz - pz) / dt;
197
+ if (relativeDamping > 0) {
198
+ const keep = Math.exp(-relativeDamping * dt);
199
+ const pvx = velocities[pi], pvy = velocities[pi + 1], pvz = velocities[pi + 2];
200
+ evx = pvx + (evx - pvx) * keep;
201
+ evy = pvy + (evy - pvy) * keep;
202
+ evz = pvz + (evz - pvz) * keep;
203
+ }
204
+ velocities[ci] = evx;
205
+ velocities[ci + 1] = evy;
206
+ velocities[ci + 2] = evz;
207
+ positions[ci] = npx;
208
+ positions[ci + 1] = npy;
209
+ positions[ci + 2] = npz;
71
210
  }
72
211
  }
73
212
  return {
74
213
  integrate(dt, rootTransform) {
75
214
  if (!Number.isFinite(dt) || dt <= 0)
76
215
  throw new Error("Spring chain dt must be finite and positive.");
216
+ if (rootTransform.offsets !== undefined && rootTransform.offsets.length !== count * 3)
217
+ throw new Error("Spring chain offsets must contain 3 floats per particle.");
77
218
  const sub = dt / substeps;
78
219
  for (let s = 0; s < substeps; s += 1)
79
220
  step(sub, rootTransform);
80
221
  },
81
222
  positions() {
82
- return positions.map((p) => [...p]);
223
+ const out = new Array(count);
224
+ for (let i = 0; i < count; i += 1) {
225
+ out[i] = [positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]];
226
+ }
227
+ return out;
228
+ },
229
+ positionsFlat(out) {
230
+ out.set(positions.subarray(0, count * 3));
231
+ return out;
83
232
  },
84
233
  telemetry() {
85
- const tip = positions[positions.length - 1];
86
- const root = positions[0];
234
+ const ti = (count - 1) * 3;
87
235
  let ke = 0;
88
- for (const v of velocities)
89
- ke += 0.5 * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
236
+ for (let i = 0; i < count * 3; i += 1)
237
+ ke += 0.5 * velocities[i] * velocities[i];
90
238
  return {
91
239
  name,
92
- boneCount: positions.length,
240
+ boneCount: count,
93
241
  stiffness,
94
242
  damping,
95
- rootPosition: [...root],
96
- tipPosition: [...tip],
97
- maxDisplacement: dist(root, tip),
243
+ rootPosition: [positions[0], positions[1], positions[2]],
244
+ tipPosition: [positions[ti], positions[ti + 1], positions[ti + 2]],
245
+ maxDisplacement: Math.hypot(positions[ti] - positions[0], positions[ti + 1] - positions[1], positions[ti + 2] - positions[2]),
98
246
  collisionContacts: contacts,
99
247
  kineticEnergy: ke
100
248
  };
101
249
  },
102
250
  reset(rootTransform) {
103
251
  const rootPos = rootTransform?.position ?? options.bones[0];
104
- const rotation = rootTransform?.rotation;
105
- positions[0] = [...rootPos];
106
- velocities[0] = [0, 0, 0];
107
- for (let i = 1; i < positions.length; i += 1) {
108
- positions[i] = add(positions[i - 1], rotatedRestOffset(i, rotation));
109
- velocities[i] = [0, 0, 0];
252
+ const q = rootTransform?.rotation;
253
+ const offs = rootTransform?.offsets;
254
+ if (offs !== undefined && offs.length !== count * 3)
255
+ throw new Error("Spring chain offsets must contain 3 floats per particle.");
256
+ prevAnchorRot[0] = q?.[0] ?? 0;
257
+ prevAnchorRot[1] = q?.[1] ?? 0;
258
+ prevAnchorRot[2] = q?.[2] ?? 0;
259
+ prevAnchorRot[3] = q?.[3] ?? 1;
260
+ contacts = 0;
261
+ positions[0] = rootPos[0];
262
+ positions[1] = rootPos[1];
263
+ positions[2] = rootPos[2];
264
+ velocities.fill(0);
265
+ for (let i = 1; i < count; i += 1) {
266
+ const pi = (i - 1) * 3;
267
+ const ci = i * 3;
268
+ if (offs !== undefined) {
269
+ positions[ci] = positions[pi] + offs[ci];
270
+ positions[ci + 1] = positions[pi + 1] + offs[ci + 1];
271
+ positions[ci + 2] = positions[pi + 2] + offs[ci + 2];
272
+ continue;
273
+ }
274
+ let ox = restOffsets[ci], oy = restOffsets[ci + 1], oz = restOffsets[ci + 2];
275
+ if (q !== undefined) {
276
+ const ix = q[3] * ox + q[1] * oz - q[2] * oy;
277
+ const iy = q[3] * oy + q[2] * ox - q[0] * oz;
278
+ const iz = q[3] * oz + q[0] * oy - q[1] * ox;
279
+ const iw = -q[0] * ox - q[1] * oy - q[2] * oz;
280
+ ox = ix * q[3] + iw * -q[0] + iy * -q[2] - iz * -q[1];
281
+ oy = iy * q[3] + iw * -q[1] + iz * -q[0] - ix * -q[2];
282
+ oz = iz * q[3] + iw * -q[2] + ix * -q[1] - iy * -q[0];
283
+ }
284
+ positions[ci] = positions[pi] + ox;
285
+ positions[ci + 1] = positions[pi + 1] + oy;
286
+ positions[ci + 2] = positions[pi + 2] + oz;
110
287
  }
111
288
  }
112
289
  };
@@ -193,4 +370,371 @@ function rotateVec3(v, q) {
193
370
  iz * w + iw * -z + ix * -y - iy * -x
194
371
  ];
195
372
  }
373
+ function springFkFrame(pose, skeleton, joint) {
374
+ const rot = [pose.rotations[joint * 4], pose.rotations[joint * 4 + 1], pose.rotations[joint * 4 + 2], pose.rotations[joint * 4 + 3]];
375
+ const scl = [pose.scales[joint * 3], pose.scales[joint * 3 + 1], pose.scales[joint * 3 + 2]];
376
+ const pos = [pose.positions[joint * 3], pose.positions[joint * 3 + 1], pose.positions[joint * 3 + 2]];
377
+ const parent = skeleton.parentIndices[joint] ?? -1;
378
+ if (parent < 0 || parent >= skeleton.boneCount || parent === joint) {
379
+ return { position: pos, rotation: rot, scale: scl };
380
+ }
381
+ const pf = springFkFrame(pose, skeleton, parent);
382
+ const scaled = [pos[0] * pf.scale[0], pos[1] * pf.scale[1], pos[2] * pf.scale[2]];
383
+ const rotated = rotateVec3(scaled, pf.rotation);
384
+ return {
385
+ position: [pf.position[0] + rotated[0], pf.position[1] + rotated[1], pf.position[2] + rotated[2]],
386
+ rotation: multiplyQuat(pf.rotation, rot),
387
+ scale: [pf.scale[0] * scl[0], pf.scale[1] * scl[1], pf.scale[2] * scl[2]]
388
+ };
389
+ }
390
+ function multiplyQuat(a, b) {
391
+ const [ax, ay, az, aw] = a;
392
+ const [bx, by, bz, bw] = b;
393
+ return [
394
+ ax * bw + aw * bx + ay * bz - az * by,
395
+ ay * bw + aw * by + az * bx - ax * bz,
396
+ az * bw + aw * bz + ax * by - ay * bx,
397
+ aw * bw - ax * bx - ay * by - az * bz
398
+ ];
399
+ }
400
+ function conjugateQuat(q) {
401
+ return [-q[0], -q[1], -q[2], q[3]];
402
+ }
403
+ function quatFromUnitVectors(from, to) {
404
+ const d = Math.max(-1, Math.min(1, from[0] * to[0] + from[1] * to[1] + from[2] * to[2]));
405
+ if (d > 1 - 1e-9)
406
+ return [0, 0, 0, 1];
407
+ if (d < -1 + 1e-9) {
408
+ const axis = Math.abs(from[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
409
+ const c = [
410
+ from[1] * axis[2] - from[2] * axis[1],
411
+ from[2] * axis[0] - from[0] * axis[2],
412
+ from[0] * axis[1] - from[1] * axis[0]
413
+ ];
414
+ const l = Math.hypot(c[0], c[1], c[2]) || 1;
415
+ return [c[0] / l, c[1] / l, c[2] / l, 0];
416
+ }
417
+ const c = [
418
+ from[1] * to[2] - from[2] * to[1],
419
+ from[2] * to[0] - from[0] * to[2],
420
+ from[0] * to[1] - from[1] * to[0]
421
+ ];
422
+ const w = Math.sqrt((1 + d) / 2);
423
+ const s = 1 / (2 * w);
424
+ return [c[0] * s, c[1] * s, c[2] * s, w];
425
+ }
426
+ function normalizeVec3(v) {
427
+ const l = len(v);
428
+ return l <= 1e-9 ? [0, 1, 0] : [v[0] / l, v[1] / l, v[2] / l];
429
+ }
430
+ /**
431
+ * T4.1 (PRD-06 §7.1) — bind a {@link SpringChain} to a skeleton bone chain.
432
+ * `boneNames` lists the chain root first (the kinematic anchor, driven by
433
+ * animation) followed by the simulated bones. Unknown names throw — a
434
+ * misspelled chain is louder than a silently dead spring.
435
+ *
436
+ * Per `step`, the kinematic root frame is read from the pose, the fixed-step
437
+ * accumulator integrates at `substepHz`, and each bone with a simulated child
438
+ * gets a local rotation aiming its rest child-direction at the simulated
439
+ * segment direction. Positions stay FK-driven: the sim only bends the chain.
440
+ */
441
+ export function bindSpringChainToSkeleton(chain, skeleton, boneNames, options) {
442
+ if (boneNames.length < 2) {
443
+ throw new Error("SpringBones: a bound chain needs a root plus at least one simulated bone.");
444
+ }
445
+ const indices = boneNames.map((name) => {
446
+ const slots = skeleton.jointIndicesByName.get(name);
447
+ if (slots === undefined || slots.length === 0) {
448
+ throw new Error(`SpringBones: unknown bone "${name}".`);
449
+ }
450
+ return slots[0];
451
+ });
452
+ const substepHz = Math.max(1, options?.substepHz ?? 60);
453
+ const maxSubsteps = Math.max(1, Math.floor(options?.maxSubsteps ?? 8));
454
+ const h = 1 / substepHz;
455
+ let accumulator = 0;
456
+ let initialized = false;
457
+ // §13 — flat FK-frame scratch: `springFkFrame` re-walked the ancestor chain
458
+ // and allocated per call (~allocs × depth × bones per step, ~5× over the
459
+ // spring micro-budget). Now frames resolve once per step into flat arrays,
460
+ // memoized by generation mark, and the aim write is pure scalar math.
461
+ const fPos = new Float32Array(skeleton.boneCount * 3);
462
+ const fRot = new Float32Array(skeleton.boneCount * 4);
463
+ const fScl = new Float32Array(skeleton.boneCount * 3);
464
+ const fMark = new Uint32Array(skeleton.boneCount);
465
+ const simPos = new Float32Array(indices.length * 3);
466
+ const rootPosArr = [0, 0, 0];
467
+ const rootRotArr = [0, 0, 0, 1];
468
+ // Animated rest targets (this frame and the last integrated frame) and the
469
+ // per-substep interpolated copies handed to the chain.
470
+ const curOffsets = new Float32Array(indices.length * 3);
471
+ const prevOffsets = new Float32Array(indices.length * 3);
472
+ const subOffsets = new Float32Array(indices.length * 3);
473
+ const prevRoot = new Float32Array(3);
474
+ const subRoot = [0, 0, 0];
475
+ // Aim write bookkeeping (see `restoreUnauthoredAims`).
476
+ const aimRot = new Float32Array(indices.length * 4);
477
+ const authoredRot = new Float32Array(indices.length * 4);
478
+ const aimWritten = new Uint8Array(indices.length);
479
+ let fGen = 0;
480
+ /** World-frame of `joint` in `pose`, computed once per generation into the flat scratch. */
481
+ function frameOf(pose, joint) {
482
+ if (fMark[joint] === fGen)
483
+ return;
484
+ const lp = joint * 3;
485
+ const lr = joint * 4;
486
+ const lpx = pose.positions[lp], lpy = pose.positions[lp + 1], lpz = pose.positions[lp + 2];
487
+ const lrx = pose.rotations[lr], lry = pose.rotations[lr + 1], lrz = pose.rotations[lr + 2], lrw = pose.rotations[lr + 3];
488
+ const lsx = pose.scales[lp], lsy = pose.scales[lp + 1], lsz = pose.scales[lp + 2];
489
+ const parent = skeleton.parentIndices[joint] ?? -1;
490
+ if (parent < 0 || parent >= skeleton.boneCount || parent === joint) {
491
+ fPos[lp] = lpx;
492
+ fPos[lp + 1] = lpy;
493
+ fPos[lp + 2] = lpz;
494
+ fRot[lr] = lrx;
495
+ fRot[lr + 1] = lry;
496
+ fRot[lr + 2] = lrz;
497
+ fRot[lr + 3] = lrw;
498
+ fScl[lp] = lsx;
499
+ fScl[lp + 1] = lsy;
500
+ fScl[lp + 2] = lsz;
501
+ fMark[joint] = fGen;
502
+ return;
503
+ }
504
+ frameOf(pose, parent);
505
+ const pp = parent * 3;
506
+ const pr = parent * 4;
507
+ const qx = fRot[pr], qy = fRot[pr + 1], qz = fRot[pr + 2], qw = fRot[pr + 3];
508
+ // pos = parentPos + rotate(parentRot, localPos * parentScale)
509
+ const sx = lpx * fScl[pp], sy = lpy * fScl[pp + 1], sz = lpz * fScl[pp + 2];
510
+ const ix = qw * sx + qy * sz - qz * sy;
511
+ const iy = qw * sy + qz * sx - qx * sz;
512
+ const iz = qw * sz + qx * sy - qy * sx;
513
+ const iw = -qx * sx - qy * sy - qz * sz;
514
+ fPos[lp] = fPos[pp] + (ix * qw + iw * -qx + iy * -qz - iz * -qy);
515
+ fPos[lp + 1] = fPos[pp + 1] + (iy * qw + iw * -qy + iz * -qx - ix * -qz);
516
+ fPos[lp + 2] = fPos[pp + 2] + (iz * qw + iw * -qz + ix * -qy - iy * -qx);
517
+ // rot = parentRot * localRot
518
+ fRot[lr] = qx * lrw + qw * lrx + qy * lrz - qz * lry;
519
+ fRot[lr + 1] = qy * lrw + qw * lry + qz * lrx - qx * lrz;
520
+ fRot[lr + 2] = qz * lrw + qw * lrz + qx * lry - qy * lrx;
521
+ fRot[lr + 3] = qw * lrw - qx * lrx - qy * lry - qz * lrz;
522
+ fScl[lp] = fScl[pp] * lsx;
523
+ fScl[lp + 1] = fScl[pp + 1] * lsy;
524
+ fScl[lp + 2] = fScl[pp + 2] * lsz;
525
+ fMark[joint] = fGen;
526
+ }
527
+ // Rest child-direction per bone expressed in its own frame. Derived from the
528
+ // bind-time (rest) pose so the aim write preserves the authored chain shape
529
+ // and only bends it toward the simulated segment.
530
+ const restFrames = indices.map((joint) => springFkFrame(skeleton.restPose, skeleton, joint));
531
+ const childDirLocal = indices.map((joint, i) => {
532
+ if (i + 1 >= indices.length)
533
+ return [0, 0, 0];
534
+ const restDir = normalizeVec3([
535
+ restFrames[i + 1].position[0] - restFrames[i].position[0],
536
+ restFrames[i + 1].position[1] - restFrames[i].position[1],
537
+ restFrames[i + 1].position[2] - restFrames[i].position[2]
538
+ ]);
539
+ return normalizeVec3(rotateVec3(restDir, conjugateQuat(restFrames[i].rotation)));
540
+ });
541
+ function initialize(pose) {
542
+ fGen += 1;
543
+ restoreUnauthoredAims(pose);
544
+ readAnimatedTargets(pose);
545
+ chain.reset({ position: rootPosArr, rotation: rootRotArr, offsets: curOffsets });
546
+ prevRoot.set(rootPosArr);
547
+ prevOffsets.set(curOffsets);
548
+ aimWritten.fill(0);
549
+ accumulator = 0;
550
+ initialized = true;
551
+ }
552
+ /**
553
+ * The aim write below edits `pose.rotations` in place. A host that mixes a
554
+ * fresh pose every frame overwrites it; one that re-steps the same buffer
555
+ * would otherwise feed the aimed rotation back in as the "animated" target
556
+ * (the spring would chase itself). An aimed joint whose rotation is still
557
+ * bit-identical to the value written is restored to the authored rotation.
558
+ */
559
+ function restoreUnauthoredAims(pose) {
560
+ for (let i = 1; i + 1 < indices.length; i += 1) {
561
+ if (aimWritten[i] === 0)
562
+ continue;
563
+ const jr = indices[i] * 4;
564
+ const ar = i * 4;
565
+ if (pose.rotations[jr] === aimRot[ar] && pose.rotations[jr + 1] === aimRot[ar + 1] &&
566
+ pose.rotations[jr + 2] === aimRot[ar + 2] && pose.rotations[jr + 3] === aimRot[ar + 3]) {
567
+ pose.rotations[jr] = authoredRot[ar];
568
+ pose.rotations[jr + 1] = authoredRot[ar + 1];
569
+ pose.rotations[jr + 2] = authoredRot[ar + 2];
570
+ pose.rotations[jr + 3] = authoredRot[ar + 3];
571
+ }
572
+ }
573
+ }
574
+ /**
575
+ * Animated (pre-aim) chain frame: root world position/rotation and each
576
+ * particle's FK offset from its parent. These are the spring's rest
577
+ * targets, so the chain settles onto the *animated* pose — a clip that
578
+ * re-poses the chain (a crouch, a landing) moves the target with it instead
579
+ * of leaving the particles springing toward bind-pose offsets.
580
+ */
581
+ function readAnimatedTargets(pose) {
582
+ for (let i = 0; i < indices.length; i += 1)
583
+ frameOf(pose, indices[i]);
584
+ const ri = indices[0];
585
+ rootPosArr[0] = fPos[ri * 3];
586
+ rootPosArr[1] = fPos[ri * 3 + 1];
587
+ rootPosArr[2] = fPos[ri * 3 + 2];
588
+ rootRotArr[0] = fRot[ri * 4];
589
+ rootRotArr[1] = fRot[ri * 4 + 1];
590
+ rootRotArr[2] = fRot[ri * 4 + 2];
591
+ rootRotArr[3] = fRot[ri * 4 + 3];
592
+ for (let i = 1; i < indices.length; i += 1) {
593
+ const a = indices[i - 1] * 3;
594
+ const b = indices[i] * 3;
595
+ curOffsets[i * 3] = fPos[b] - fPos[a];
596
+ curOffsets[i * 3 + 1] = fPos[b + 1] - fPos[a + 1];
597
+ curOffsets[i * 3 + 2] = fPos[b + 2] - fPos[a + 2];
598
+ }
599
+ }
600
+ function step(pose, dt) {
601
+ if (!Number.isFinite(dt) || dt < 0) {
602
+ throw new Error("SpringBones: step dt must be finite and non-negative.");
603
+ }
604
+ if (!initialized)
605
+ initialize(pose);
606
+ fGen += 1;
607
+ restoreUnauthoredAims(pose);
608
+ accumulator = Math.min(accumulator + dt, 0.25);
609
+ const substeps = Math.min(Math.floor(accumulator / h), maxSubsteps);
610
+ if (substeps > 0) {
611
+ readAnimatedTargets(pose);
612
+ // The animated pose only advances once per host frame; spread that
613
+ // motion across this call's substeps (linear in position/offset)
614
+ // instead of teleporting the anchor on the first substep. A teleport
615
+ // makes the length constraint convert a whole frame's anchor travel
616
+ // into one substep's velocity — ~substeps× the real anchor speed —
617
+ // which flings the chain on every fast anchor move (the landing).
618
+ for (let k = 0; k < substeps; k += 1) {
619
+ const u = (k + 1) / substeps;
620
+ subRoot[0] = prevRoot[0] + (rootPosArr[0] - prevRoot[0]) * u;
621
+ subRoot[1] = prevRoot[1] + (rootPosArr[1] - prevRoot[1]) * u;
622
+ subRoot[2] = prevRoot[2] + (rootPosArr[2] - prevRoot[2]) * u;
623
+ for (let c = 3; c < subOffsets.length; c += 1) {
624
+ subOffsets[c] = prevOffsets[c] + (curOffsets[c] - prevOffsets[c]) * u;
625
+ }
626
+ chain.integrate(h, { position: subRoot, rotation: rootRotArr, offsets: subOffsets });
627
+ }
628
+ prevRoot.set(rootPosArr);
629
+ prevOffsets.set(curOffsets);
630
+ accumulator -= substeps * h;
631
+ }
632
+ chain.positionsFlat(simPos);
633
+ // Animated-target FK frames predate the aim writes. Recompute the parent
634
+ // chain lazily as each newly aimed rotation becomes a descendant's parent.
635
+ fGen += 1;
636
+ // Aim bones[1..n-2]: each simulated bone that has a simulated child. The
637
+ // chain root stays purely kinematic — writing its rotation would feed the
638
+ // aim back into the next frame's root frame and the rest target would
639
+ // chase itself; the tip has no child to aim at.
640
+ for (let i = 1; i + 1 < indices.length; i += 1) {
641
+ const joint = indices[i];
642
+ const si = i * 3;
643
+ const sdx = simPos[si + 3] - simPos[si];
644
+ const sdy = simPos[si + 4] - simPos[si + 1];
645
+ const sdz = simPos[si + 5] - simPos[si + 2];
646
+ const sl = Math.hypot(sdx, sdy, sdz);
647
+ if (sl <= 1e-9)
648
+ continue;
649
+ const ndx = sdx / sl, ndy = sdy / sl, ndz = sdz / sl;
650
+ const parent = skeleton.parentIndices[joint] ?? -1;
651
+ let prx = 0, pry = 0, prz = 0, prw = 1;
652
+ if (parent >= 0 && parent < skeleton.boneCount) {
653
+ frameOf(pose, parent);
654
+ const pr = parent * 4;
655
+ prx = fRot[pr];
656
+ pry = fRot[pr + 1];
657
+ prz = fRot[pr + 2];
658
+ prw = fRot[pr + 3];
659
+ }
660
+ const jr = joint * 4;
661
+ const lrx = pose.rotations[jr], lry = pose.rotations[jr + 1], lrz = pose.rotations[jr + 2], lrw = pose.rotations[jr + 3];
662
+ // worldRot = parentRot * localRot
663
+ const wx = prx * lrw + prw * lrx + pry * lrz - prz * lry;
664
+ const wy = pry * lrw + prw * lry + prz * lrx - prx * lrz;
665
+ const wz = prz * lrw + prw * lrz + prx * lry - pry * lrx;
666
+ const ww = prw * lrw - prx * lrx - pry * lry - prz * lrz;
667
+ // currentDir = normalize(rotate(childDirLocal[i], worldRot))
668
+ const cv = childDirLocal[i];
669
+ const cix = ww * cv[0] + wy * cv[2] - wz * cv[1];
670
+ const ciy = ww * cv[1] + wz * cv[0] - wx * cv[2];
671
+ const ciz = ww * cv[2] + wx * cv[1] - wy * cv[0];
672
+ const ciw = -wx * cv[0] - wy * cv[1] - wz * cv[2];
673
+ let cdx = cix * ww + ciw * -wx + ciy * -wz - ciz * -wy;
674
+ let cdy = ciy * ww + ciw * -wy + ciz * -wx - cix * -wz;
675
+ let cdz = ciz * ww + ciw * -wz + cix * -wy - ciy * -wx;
676
+ const cl = Math.hypot(cdx, cdy, cdz);
677
+ if (cl <= 1e-9) {
678
+ cdx = 0;
679
+ cdy = 1;
680
+ cdz = 0;
681
+ }
682
+ else {
683
+ cdx /= cl;
684
+ cdy /= cl;
685
+ cdz /= cl;
686
+ }
687
+ // aim = quatFromUnitVectors(currentDir, simDir)
688
+ let ax = 0, ay = 0, az = 0, aw = 1;
689
+ const d = Math.max(-1, Math.min(1, cdx * ndx + cdy * ndy + cdz * ndz));
690
+ if (d > 1 - 1e-9) {
691
+ // aim stays identity
692
+ }
693
+ else if (d < -1 + 1e-9) {
694
+ const ux = Math.abs(cdx) < 0.9 ? 1 : 0;
695
+ const uy = Math.abs(cdx) < 0.9 ? 0 : 1;
696
+ let cxx = cdy * 0 - cdz * uy;
697
+ let cyy = cdz * ux - cdx * 0;
698
+ let czz = cdx * uy - cdy * ux;
699
+ const ul = Math.hypot(cxx, cyy, czz) || 1;
700
+ ax = cxx / ul;
701
+ ay = cyy / ul;
702
+ az = czz / ul;
703
+ aw = 0;
704
+ }
705
+ else {
706
+ const cx = cdy * ndz - cdz * ndy;
707
+ const cy = cdz * ndx - cdx * ndz;
708
+ const cz = cdx * ndy - cdy * ndx;
709
+ const hw = Math.sqrt((1 + d) / 2);
710
+ const s = 1 / (2 * hw);
711
+ ax = cx * s;
712
+ ay = cy * s;
713
+ az = cz * s;
714
+ aw = hw;
715
+ }
716
+ // newLocal = conjugate(parentRot) * (aim * worldRot)
717
+ const m1x = ax * ww + aw * wx + ay * wz - az * wy;
718
+ const m1y = ay * ww + aw * wy + az * wx - ax * wz;
719
+ const m1z = az * ww + aw * wz + ax * wy - ay * wx;
720
+ const m1w = aw * ww - ax * wx - ay * wy - az * wz;
721
+ pose.rotations[jr] = -prx * m1w + prw * m1x - pry * m1z + prz * m1y;
722
+ pose.rotations[jr + 1] = -pry * m1w + prw * m1y - prz * m1x + prx * m1z;
723
+ pose.rotations[jr + 2] = -prz * m1w + prw * m1z - prx * m1y + pry * m1x;
724
+ pose.rotations[jr + 3] = prw * m1w + prx * m1x + pry * m1y + prz * m1z;
725
+ const ar = i * 4;
726
+ authoredRot[ar] = lrx;
727
+ authoredRot[ar + 1] = lry;
728
+ authoredRot[ar + 2] = lrz;
729
+ authoredRot[ar + 3] = lrw;
730
+ aimRot[ar] = pose.rotations[jr];
731
+ aimRot[ar + 1] = pose.rotations[jr + 1];
732
+ aimRot[ar + 2] = pose.rotations[jr + 2];
733
+ aimRot[ar + 3] = pose.rotations[jr + 3];
734
+ aimWritten[i] = 1;
735
+ fMark[joint] = 0;
736
+ }
737
+ }
738
+ return { initialize, step };
739
+ }
196
740
  //# sourceMappingURL=SpringBones.js.map