@driftengine/physics 3.61.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 (136) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +503 -0
  4. package/dist/bodies.d.ts +121 -0
  5. package/dist/bodies.js +298 -0
  6. package/dist/buoyancy.d.ts +46 -0
  7. package/dist/buoyancy.js +45 -0
  8. package/dist/cloth.d.ts +226 -0
  9. package/dist/cloth.js +587 -0
  10. package/dist/collide/aabb.d.ts +10 -0
  11. package/dist/collide/aabb.js +11 -0
  12. package/dist/collide/body.d.ts +202 -0
  13. package/dist/collide/body.js +142 -0
  14. package/dist/collide/eject.d.ts +39 -0
  15. package/dist/collide/eject.js +142 -0
  16. package/dist/collide/index.d.ts +18 -0
  17. package/dist/collide/index.js +5 -0
  18. package/dist/collide/scratch.d.ts +8 -0
  19. package/dist/collide/scratch.js +9 -0
  20. package/dist/collide/segment.d.ts +12 -0
  21. package/dist/collide/segment.js +109 -0
  22. package/dist/collide/sweep.d.ts +3 -0
  23. package/dist/collide/sweep.js +1036 -0
  24. package/dist/colliderSet.d.ts +239 -0
  25. package/dist/colliderSet.js +546 -0
  26. package/dist/compoundContact.d.ts +70 -0
  27. package/dist/compoundContact.js +221 -0
  28. package/dist/controller.d.ts +351 -0
  29. package/dist/controller.js +641 -0
  30. package/dist/cylinder.d.ts +78 -0
  31. package/dist/cylinder.js +294 -0
  32. package/dist/cylinderContact.d.ts +7 -0
  33. package/dist/cylinderContact.js +704 -0
  34. package/dist/decompose.d.ts +105 -0
  35. package/dist/decompose.js +979 -0
  36. package/dist/events.d.ts +43 -0
  37. package/dist/events.js +95 -0
  38. package/dist/executor.d.ts +147 -0
  39. package/dist/executor.js +173 -0
  40. package/dist/faces.d.ts +26 -0
  41. package/dist/faces.js +34 -0
  42. package/dist/fingerprint.d.ts +61 -0
  43. package/dist/fingerprint.js +139 -0
  44. package/dist/groundMotor.d.ts +34 -0
  45. package/dist/groundMotor.js +41 -0
  46. package/dist/heightfieldShape.d.ts +128 -0
  47. package/dist/heightfieldShape.js +328 -0
  48. package/dist/index.d.ts +73 -0
  49. package/dist/index.js +47 -0
  50. package/dist/island.d.ts +55 -0
  51. package/dist/island.js +227 -0
  52. package/dist/islandSolve.d.ts +70 -0
  53. package/dist/islandSolve.js +171 -0
  54. package/dist/islandWorker.d.ts +18 -0
  55. package/dist/islandWorker.js +125 -0
  56. package/dist/joints.d.ts +180 -0
  57. package/dist/joints.js +1094 -0
  58. package/dist/manifold.d.ts +89 -0
  59. package/dist/manifold.js +608 -0
  60. package/dist/mass.d.ts +53 -0
  61. package/dist/mass.js +353 -0
  62. package/dist/meshContact.d.ts +57 -0
  63. package/dist/meshContact.js +393 -0
  64. package/dist/meshShape.d.ts +76 -0
  65. package/dist/meshShape.js +175 -0
  66. package/dist/pairs.d.ts +62 -0
  67. package/dist/pairs.js +154 -0
  68. package/dist/query.d.ts +71 -0
  69. package/dist/query.js +683 -0
  70. package/dist/ragdoll.d.ts +135 -0
  71. package/dist/ragdoll.js +489 -0
  72. package/dist/shape.d.ts +177 -0
  73. package/dist/shape.js +533 -0
  74. package/dist/solver.d.ts +136 -0
  75. package/dist/solver.js +601 -0
  76. package/dist/stage.d.ts +167 -0
  77. package/dist/stage.js +350 -0
  78. package/dist/stallEscape.d.ts +141 -0
  79. package/dist/stallEscape.js +254 -0
  80. package/dist/sweptStep.d.ts +173 -0
  81. package/dist/sweptStep.js +472 -0
  82. package/dist/tree.d.ts +61 -0
  83. package/dist/tree.js +348 -0
  84. package/dist/vehicle.d.ts +154 -0
  85. package/dist/vehicle.js +343 -0
  86. package/dist/workerPool.d.ts +107 -0
  87. package/dist/workerPool.js +239 -0
  88. package/dist/workers.d.ts +37 -0
  89. package/dist/workers.js +35 -0
  90. package/dist/world.d.ts +298 -0
  91. package/dist/world.js +712 -0
  92. package/package.json +55 -0
  93. package/src/bodies.ts +369 -0
  94. package/src/buoyancy.ts +78 -0
  95. package/src/cloth.ts +734 -0
  96. package/src/collide/aabb.ts +28 -0
  97. package/src/collide/body.ts +336 -0
  98. package/src/collide/eject.ts +156 -0
  99. package/src/collide/index.ts +26 -0
  100. package/src/collide/scratch.ts +10 -0
  101. package/src/collide/segment.ts +127 -0
  102. package/src/collide/sweep.ts +1132 -0
  103. package/src/colliderSet.ts +637 -0
  104. package/src/compoundContact.ts +284 -0
  105. package/src/controller.ts +817 -0
  106. package/src/cylinder.ts +340 -0
  107. package/src/cylinderContact.ts +825 -0
  108. package/src/decompose.ts +1198 -0
  109. package/src/events.ts +108 -0
  110. package/src/executor.ts +224 -0
  111. package/src/faces.ts +37 -0
  112. package/src/fingerprint.ts +146 -0
  113. package/src/groundMotor.ts +53 -0
  114. package/src/heightfieldShape.ts +395 -0
  115. package/src/index.ts +164 -0
  116. package/src/island.ts +219 -0
  117. package/src/islandSolve.ts +218 -0
  118. package/src/islandWorker.ts +141 -0
  119. package/src/joints.ts +1367 -0
  120. package/src/manifold.ts +798 -0
  121. package/src/mass.ts +424 -0
  122. package/src/meshContact.ts +443 -0
  123. package/src/meshShape.ts +261 -0
  124. package/src/pairs.ts +169 -0
  125. package/src/query.ts +844 -0
  126. package/src/ragdoll.ts +662 -0
  127. package/src/shape.ts +656 -0
  128. package/src/solver.ts +873 -0
  129. package/src/stage.ts +406 -0
  130. package/src/stallEscape.ts +362 -0
  131. package/src/sweptStep.ts +609 -0
  132. package/src/tree.ts +366 -0
  133. package/src/vehicle.ts +489 -0
  134. package/src/workerPool.ts +283 -0
  135. package/src/workers.ts +38 -0
  136. package/src/world.ts +961 -0
@@ -0,0 +1,135 @@
1
+ import type { PhysicsWorld } from './world.ts';
2
+ /**
3
+ * A ragdoll built from bones, where a bone is a joint and its parent.
4
+ *
5
+ * **It takes plain data and never learns what a skeleton is.** `Skeleton` lives in
6
+ * `@driftengine/animation`, which imports core, the container package and `gl-matrix`; this package
7
+ * imports no other engine package, and that is not a rule to bend for one function. So the builder
8
+ * takes a parent array and a flat array of joint world matrices — both of which a skeleton already
9
+ * exposes — and writes back through a *structural* target with `translation`, `rotation` and
10
+ * `scale`, which `ShapePose` satisfies exactly. Nothing is imported in either direction, and the two
11
+ * shapes cannot drift apart because neither names the other.
12
+ *
13
+ * **One capsule per bone, cone-twist jointed to its parent's.** A joint with no parent has no bone;
14
+ * a humanoid's hips are usually that joint, and the spine and thigh bones hang off it.
15
+ *
16
+ * `drive` steers the bodies toward an animated pose rather than replacing them, which is what makes
17
+ * a hit reaction. A fully limp ragdoll is the degenerate case of that, at weight zero.
18
+ */
19
+ /** Anything shaped like an animation pose. Declared structurally so nothing is imported. */
20
+ export interface PoseTarget {
21
+ readonly translation: Float32Array;
22
+ readonly rotation: Float32Array;
23
+ readonly scale: Float32Array;
24
+ }
25
+ export interface RagdollOptions {
26
+ /** Capsule radius, as a fraction of the bone's length. */
27
+ radiusRatio?: number;
28
+ /** A floor under the radius, so a short bone is not a needle. */
29
+ minRadius?: number;
30
+ density?: number;
31
+ /**
32
+ * Cosine of the swing cone allowed: one number for every joint, or one per joint.
33
+ *
34
+ * **It was one number for a whole body until 2026-08-28, and a body is not one joint.** A knee is
35
+ * a hinge with no twist; a shoulder is nearly a ball. Given one pair, a consumer chooses which of
36
+ * the two is wrong — and the one who reported this chose loose, a 78° cone and 74° of twist at
37
+ * every joint, with a comment saying tight limits look like a wooden puppet. What it produced was
38
+ * reported from play as *a scrambled ball of things, limbs torso head mixed together*: at 78°
39
+ * nothing stops a knee or an elbow folding a limb through the torso, so a body that lands rolls
40
+ * itself into a knot.
41
+ *
42
+ * An array is **indexed by the joint, exactly as `parents` is**, so the entry at the knee limits
43
+ * the knee: the joint a bone gets is the one at its parent end, and the bone at index `j` is the
44
+ * one below joint `j`. Index 0 is a rig's root, which has no bone and therefore no joint, and its
45
+ * entry is ignored. An index the array does not reach, or one holding anything that is not a
46
+ * finite number, takes the default — which matters more than it sounds, because the alternative
47
+ * reading of a missing entry is zero, and zero is a 90° cone at exactly the joint nobody thought
48
+ * about.
49
+ *
50
+ * **What this does not buy is a real hinge.** A narrow cone with no twist is a *near* hinge: it
51
+ * still bends a little in every direction, because a cone is a cone. A knee that cannot bend
52
+ * sideways at all needs a revolute joint and an axis, and the axis is rig data this builder is
53
+ * never given — a parent array and a set of world matrices do not say which way a knee folds.
54
+ * **What would change it** is a consumer measuring that sideways bend as the problem, and the
55
+ * answer then is a joint type per bone with the axis supplied, not a wider cone.
56
+ *
57
+ * **A value here is compared every tick, so compute it the way the block below computes its
58
+ * defaults.** `Math.cos` is not specified precisely by ECMAScript, so a limit built with one is a
59
+ * limit that can differ by a ulp between two engines, and a ragdoll that diverges. Write the
60
+ * numbers down.
61
+ */
62
+ swingCos?: number | ArrayLike<number>;
63
+ /** Sine of the half-angle of twist allowed: one number for every joint, or one per joint. */
64
+ twistSin?: number | ArrayLike<number>;
65
+ /** Bones shorter than this get no body, which is how a rig's leaf tips are skipped. */
66
+ minLength?: number;
67
+ layer?: number;
68
+ mask?: number;
69
+ /**
70
+ * Whether the doll's own bones collide with each other. Default `true`.
71
+ *
72
+ * **Bones that share an end are excluded either way, and that is not a setting.** Two capsules
73
+ * jointed end to end overlap near the joint by construction, because that is what having a
74
+ * radius means, so every parent and child in a doll and every pair of siblings starts
75
+ * interpenetrating. Left to resolve, that contact fights the joint holding them together: a
76
+ * consumer measured it holding settled joints 14.2 cm open, turning a body at 3.0 rad/s while it
77
+ * lay still, and propping it 28 cm off the road on its own thighs.
78
+ *
79
+ * **`false` is the cheap answer and it is measurably worse.** With nothing inside the doll
80
+ * colliding, the cones alone do not stop a limb folding through the torso — the same consumer
81
+ * measured a foot reaching 15% of its resting distance from the chest, which is a foot inside
82
+ * the ribcage. Pass it for a crowd far enough away that nobody can see, and not otherwise.
83
+ */
84
+ selfCollision?: boolean;
85
+ }
86
+ export interface Ragdoll {
87
+ /** Body index per joint, or −1 where the joint has no bone. */
88
+ readonly bodyOf: Int32Array;
89
+ /** How many bones were built. */
90
+ readonly boneCount: number;
91
+ /** Write the ragdoll's current shape into a pose. */
92
+ writePose(out: PoseTarget): void;
93
+ /** Steer the bodies toward a pose. Weight 1 tracks it, weight 0 goes limp. */
94
+ drive(world: PhysicsWorld, pose: PoseTarget, weight: number): void;
95
+ /**
96
+ * Put every body back on its bone and clear its velocity.
97
+ *
98
+ * **A hit reaction starts from the pose the character is in now, and this is how it says so.**
99
+ * A ragdoll is built once, because building one allocates bodies and joints and a hit is not
100
+ * the moment for that — but between reactions the character keeps moving and the bodies do not.
101
+ * They sit where the last reaction left them, or where they were on the frame the ragdoll was
102
+ * built, and a reaction that begins by blending toward *that* snaps the character to a stale
103
+ * shape somewhere else before it starts.
104
+ *
105
+ * `worldMatrices` is the same sixteen-floats-a-joint array `ragdollFromBones` took. The joints
106
+ * are left alone: they connect bones and the bones have not changed length.
107
+ */
108
+ sync(world: PhysicsWorld, worldMatrices: Float32Array): void;
109
+ /**
110
+ * Where the first bone's parent end is now, so a caller can move the character's node.
111
+ *
112
+ * The **head** of that bone, which is a different point from the body holding it: a capsule is
113
+ * built about its bone's middle, so the two are half a bone apart — 0.46 m for a root bone of
114
+ * 0.92 m, which is a character standing in the air. Read all three or none; there is nothing
115
+ * else here that answers a centre, and a caller wanting one reads `world.bodies` directly.
116
+ *
117
+ * **Two of these answered the centre until 2026-08-28 and the third only looked as if it did
118
+ * not.** Reported from outside, where the symptom was a body drawn half a metre above the
119
+ * physics holding it — which reads exactly like a pose that never arrived, and cost the reporter
120
+ * two weeks of looking for one. `rootX` reached the centre through a helper named `boneHeadX`,
121
+ * so the name answered the question and the arithmetic did not. **What would make this wrong**
122
+ * is a rig whose first bone is not the one a caller wants to place a node by; `bodyOf` is the
123
+ * door to any other bone, and this is the convenience for the common case.
124
+ */
125
+ rootX(): number;
126
+ rootY(): number;
127
+ rootZ(): number;
128
+ }
129
+ /**
130
+ * Build one.
131
+ *
132
+ * `worldMatrices` is sixteen floats a joint, column-major, exactly as a skinning pipeline's world
133
+ * array is. `parents` is one index a joint, negative for a root.
134
+ */
135
+ export declare function ragdollFromBones(world: PhysicsWorld, parents: ArrayLike<number>, worldMatrices: Float32Array, options?: RagdollOptions): Ragdoll;
@@ -0,0 +1,489 @@
1
+ import { BODY_DYNAMIC } from './bodies.js';
2
+ import { JOINT_CONE_TWIST } from './joints.js';
3
+ import { capsuleShape } from './shape.js';
4
+ const TMP = new Float64Array(4);
5
+ /**
6
+ * Build one.
7
+ *
8
+ * `worldMatrices` is sixteen floats a joint, column-major, exactly as a skinning pipeline's world
9
+ * array is. `parents` is one index a joint, negative for a root.
10
+ */
11
+ export function ragdollFromBones(world, parents, worldMatrices, options = {}) {
12
+ const jointCount = parents.length;
13
+ const radiusRatio = options.radiusRatio ?? 0.22;
14
+ const minRadius = options.minRadius ?? 0.03;
15
+ const density = options.density ?? 1000;
16
+ /*
17
+ * **Literals, not `Math.cos(Math.PI / 4)`.**
18
+ *
19
+ * These run once, when a ragdoll is built, so they looked like a legitimate build-time use — and
20
+ * they are not, because what they produce is a *joint limit compared every tick*. ECMAScript does
21
+ * not specify `cos` or `sin` precisely, so two engines would get swing cones a ulp apart and the
22
+ * ragdoll would diverge. The determinism gate caught it, which is the whole reason that gate
23
+ * scans the package rather than the tick: a value's *use* decides whether it may be computed,
24
+ * not the moment it is computed at.
25
+ *
26
+ * `Math.SQRT1_2` is a constant rather than a call, and `cos(45°)` is exactly that. `sin(22.5°)`
27
+ * has no named constant, so it is written out.
28
+ */
29
+ const swingCos = options.swingCos ?? Math.SQRT1_2;
30
+ const twistSin = options.twistSin ?? 0.3826834323650898;
31
+ const minLength = options.minLength ?? 0.02;
32
+ /**
33
+ * One limit, for one joint.
34
+ *
35
+ * Resolved per joint at build time rather than once for the rig, which is the whole of the
36
+ * per-joint change: a scalar answers the same number everywhere and an array answers its entry,
37
+ * with the default standing in wherever the array does not reach or holds something that is not a
38
+ * number. `Number.isFinite` and not a truthiness test, because zero is a legitimate limit — a
39
+ * 90° cone — and `NaN` is the one value that would reach the solver and stay there.
40
+ */
41
+ const limitAt = (given, fallback, joint) => {
42
+ if (typeof given === 'number')
43
+ return given;
44
+ const value = given[joint];
45
+ return value === undefined || !Number.isFinite(value) ? fallback : value;
46
+ };
47
+ const bodyOf = new Int32Array(jointCount).fill(-1);
48
+ /** `conj(bodyRotation) · jointWorldRotation`, so a pose can be recovered from a body. */
49
+ const bodyToJoint = new Float32Array(jointCount * 4);
50
+ const localRest = new Float32Array(jointCount * 3);
51
+ const restRotation = new Float32Array(jointCount * 4);
52
+ /**
53
+ * Half of each bone's length, which is where its two ends are in its own body frame.
54
+ *
55
+ * A capsule stands along its own +y, so a bone's head is at `-halfOf[j]` and its tail at
56
+ * `+halfOf[j]`. The bone's length and not the capsule's half height: `minLength` can float a
57
+ * short bone's capsule out past the end of the bone it stands for, and the joint belongs where
58
+ * the bones meet.
59
+ */
60
+ const halfOf = new Float32Array(jointCount);
61
+ let boneCount = 0;
62
+ let firstBone = -1;
63
+ /** Half the first bone's length, which is how far its head sits from the body's centre. */
64
+ let firstBoneHalf = 0;
65
+ for (let j = 0; j < jointCount; j++) {
66
+ const p = parents[j] ?? -1;
67
+ matrixRotation(worldMatrices, j, restRotation, j * 4);
68
+ if (p < 0)
69
+ continue;
70
+ const hx = worldMatrices[p * 16 + 12] ?? 0;
71
+ const hy = worldMatrices[p * 16 + 13] ?? 0;
72
+ const hz = worldMatrices[p * 16 + 14] ?? 0;
73
+ const tx = worldMatrices[j * 16 + 12] ?? 0;
74
+ const ty = worldMatrices[j * 16 + 13] ?? 0;
75
+ const tz = worldMatrices[j * 16 + 14] ?? 0;
76
+ localRest[j * 3] = tx - hx;
77
+ localRest[j * 3 + 1] = ty - hy;
78
+ localRest[j * 3 + 2] = tz - hz;
79
+ const dx = tx - hx;
80
+ const dy = ty - hy;
81
+ const dz = tz - hz;
82
+ const length = Math.sqrt(dx * dx + dy * dy + dz * dz);
83
+ if (length < minLength)
84
+ continue;
85
+ const radius = Math.max(minRadius, length * radiusRatio);
86
+ // A capsule stands along y, so the body is turned to put its y along the bone.
87
+ alignYTo(dx / length, dy / length, dz / length, TMP);
88
+ const body = world.addBody({
89
+ type: BODY_DYNAMIC,
90
+ shape: capsuleShape(radius, Math.max(minLength, length / 2)),
91
+ x: (hx + tx) / 2,
92
+ y: (hy + ty) / 2,
93
+ z: (hz + tz) / 2,
94
+ qx: TMP[0],
95
+ qy: TMP[1],
96
+ qz: TMP[2],
97
+ qw: TMP[3],
98
+ density,
99
+ layer: options.layer,
100
+ mask: options.mask,
101
+ });
102
+ bodyOf[j] = body;
103
+ halfOf[j] = length / 2;
104
+ // What takes the body's rotation back to the joint's, captured while both are at rest.
105
+ multiplyConjugate(TMP, restRotation, j * 4, bodyToJoint, j * 4);
106
+ boneCount++;
107
+ if (firstBone < 0) {
108
+ firstBone = j;
109
+ /* Taken here rather than derived later: this is the length the body was placed from, so
110
+ the two cannot disagree, and a bone is rigid so it stays true for as long as the doll. */
111
+ firstBoneHalf = length / 2;
112
+ }
113
+ }
114
+ // Joint each bone to its parent's bone, at the shared end.
115
+ for (let j = 0; j < jointCount; j++) {
116
+ const body = bodyOf[j] ?? -1;
117
+ if (body < 0)
118
+ continue;
119
+ const p = parents[j] ?? -1;
120
+ const parentBody = p >= 0 ? (bodyOf[p] ?? -1) : -1;
121
+ if (parentBody < 0)
122
+ continue;
123
+ world.addJoint({
124
+ type: JOINT_CONE_TWIST,
125
+ bodyA: parentBody,
126
+ bodyB: body,
127
+ /* `j` and not `p`: the joint a bone hangs from is the one at its own parent end, so the
128
+ entry at the knee is the knee's. */
129
+ swingCos: limitAt(swingCos, Math.SQRT1_2, j),
130
+ twistSin: limitAt(twistSin, 0.3826834323650898, j),
131
+ axisY: 1,
132
+ /*
133
+ * **The shared end, which is what makes the line above this loop true.**
134
+ *
135
+ * Given no anchors a joint takes the default `joints.ts` documents: B's centre, expressed in
136
+ * A's frame. That default is right for what it was written for — it stops a fixed joint
137
+ * yanking its body onto the origin on the first tick — and wrong for a limb. A knee is not
138
+ * the middle of a shin. Anchored at the child's centre, the shin is pinned by its middle and
139
+ * free to turn about it inside its cone, so the knee end of the shin swings up to half a
140
+ * shin away from the knee end of the thigh, and the cone meant to limit the knee is measured
141
+ * about the wrong pivot. Reported from outside as joints a body wide and everything
142
+ * vibrating, and measured there: a knee opening to 100 cm in flight and still 72 cm open
143
+ * after the body had landed, against 8.2 and 4.1 with the anchors here.
144
+ *
145
+ * Both resolve to the same world point at rest, because a bone's head is its parent's tail,
146
+ * so the first tick still moves nothing.
147
+ */
148
+ anchorAY: halfOf[p] ?? 0,
149
+ anchorBY: -(halfOf[j] ?? 0),
150
+ });
151
+ }
152
+ /*
153
+ * **Bones that share an end must not collide, and it cannot be said with layers.**
154
+ *
155
+ * A mask is a property of one body, so it can say what a bone is and not who two bones are to
156
+ * each other, and a doll needs the second: a thigh must not collide with the shin it is jointed
157
+ * to while still colliding with the *other* shin. `pairKey` carries why, and a consumer that
158
+ * tried it with layers spent a bit per bone and most of the 32 on one doll.
159
+ */
160
+ for (let j = 0; j < jointCount; j++) {
161
+ const body = bodyOf[j] ?? -1;
162
+ if (body < 0)
163
+ continue;
164
+ for (let k = j + 1; k < jointCount; k++) {
165
+ const other = bodyOf[k] ?? -1;
166
+ if (other < 0)
167
+ continue;
168
+ if (options.selfCollision === false) {
169
+ world.ignorePair(body, other);
170
+ continue;
171
+ }
172
+ const pj = parents[j] ?? -1;
173
+ const pk = parents[k] ?? -1;
174
+ /* Jointed, either way round, or two bones hanging off the same parent joint: siblings share
175
+ a head the way a parent and child share a point, so they overlap there too. */
176
+ if (pk === j || pj === k || (pj === pk && pj >= 0))
177
+ world.ignorePair(body, other);
178
+ }
179
+ }
180
+ const root = firstBone;
181
+ return {
182
+ bodyOf,
183
+ boneCount,
184
+ writePose(out) {
185
+ for (let j = 0; j < jointCount; j++) {
186
+ const body = bodyOf[j] ?? -1;
187
+ const p = parents[j] ?? -1;
188
+ if (body < 0) {
189
+ // No bone: leave the joint as the pose already had it.
190
+ continue;
191
+ }
192
+ // The joint's world rotation, recovered from the body's.
193
+ readBodyRotation(world, body, TMP);
194
+ multiplyInto(TMP, bodyToJoint, j * 4, WORLD_ROT, j * 4);
195
+ const parentBody = p >= 0 ? (bodyOf[p] ?? -1) : -1;
196
+ if (parentBody >= 0) {
197
+ conjugateMultiply(WORLD_ROT, p * 4, WORLD_ROT, j * 4, out.rotation, j * 4);
198
+ }
199
+ else {
200
+ copy4(WORLD_ROT, j * 4, out.rotation, j * 4);
201
+ }
202
+ // Bones are rigid, so translation stays what the rig was built with.
203
+ out.translation[j * 3] = localRest[j * 3] ?? 0;
204
+ out.translation[j * 3 + 1] = localRest[j * 3 + 1] ?? 0;
205
+ out.translation[j * 3 + 2] = localRest[j * 3 + 2] ?? 0;
206
+ out.scale[j * 3] = 1;
207
+ out.scale[j * 3 + 1] = 1;
208
+ out.scale[j * 3 + 2] = 1;
209
+ }
210
+ },
211
+ drive(w, pose, weight) {
212
+ if (weight <= 0)
213
+ return;
214
+ const k = weight > 1 ? 1 : weight;
215
+ for (let j = 0; j < jointCount; j++) {
216
+ const body = bodyOf[j] ?? -1;
217
+ if (body < 0)
218
+ continue;
219
+ const p = parents[j] ?? -1;
220
+ const parentBody = p >= 0 ? (bodyOf[p] ?? -1) : -1;
221
+ /*
222
+ * The target is the pose's local rotation composed onto the parent's *current* world
223
+ * rotation, so a driven limb follows the animation relative to a body that may itself have
224
+ * been knocked aside — which is what a partial ragdoll is.
225
+ */
226
+ if (parentBody >= 0) {
227
+ readBodyRotation(w, parentBody, TMP);
228
+ multiplyInto(TMP, bodyToJoint, p * 4, PARENT_ROT, 0);
229
+ multiplyRaw(PARENT_ROT, 0, pose.rotation, j * 4, TARGET_ROT, 0);
230
+ }
231
+ else {
232
+ copy4(pose.rotation, j * 4, TARGET_ROT, 0);
233
+ }
234
+ readBodyRotation(w, body, TMP);
235
+ multiplyInto(TMP, bodyToJoint, j * 4, CURRENT_ROT, 0);
236
+ // Twice the vector part of target · conj(current) is the rotation that closes the gap.
237
+ conjugateMultiply(CURRENT_ROT, 0, TARGET_ROT, 0, ERROR_ROT, 0);
238
+ let ex = ERROR_ROT[0] ?? 0;
239
+ let ey = ERROR_ROT[1] ?? 0;
240
+ let ez = ERROR_ROT[2] ?? 0;
241
+ if ((ERROR_ROT[3] ?? 1) < 0) {
242
+ ex = -ex;
243
+ ey = -ey;
244
+ ez = -ez;
245
+ }
246
+ // A proportional pull on angular velocity, which the solver then reconciles with the joints.
247
+ const gain = 20 * k;
248
+ w.bodies.angX[body] = (w.bodies.angX[body] ?? 0) + 2 * ex * gain;
249
+ w.bodies.angY[body] = (w.bodies.angY[body] ?? 0) + 2 * ey * gain;
250
+ w.bodies.angZ[body] = (w.bodies.angZ[body] ?? 0) + 2 * ez * gain;
251
+ }
252
+ },
253
+ sync(w, worldMatrices) {
254
+ for (let j = 0; j < jointCount; j++) {
255
+ const body = bodyOf[j] ?? -1;
256
+ if (body < 0)
257
+ continue;
258
+ const p = parents[j] ?? -1;
259
+ if (p < 0)
260
+ continue;
261
+ const hx = worldMatrices[p * 16 + 12] ?? 0;
262
+ const hy = worldMatrices[p * 16 + 13] ?? 0;
263
+ const hz = worldMatrices[p * 16 + 14] ?? 0;
264
+ const tx = worldMatrices[j * 16 + 12] ?? 0;
265
+ const ty = worldMatrices[j * 16 + 13] ?? 0;
266
+ const tz = worldMatrices[j * 16 + 14] ?? 0;
267
+ w.bodies.posX[body] = (hx + tx) / 2;
268
+ w.bodies.posY[body] = (hy + ty) / 2;
269
+ w.bodies.posZ[body] = (hz + tz) / 2;
270
+ /*
271
+ * The rotation is taken from the *bone*, the same way the build took it: the capsule
272
+ * stands along y, so the body is turned to put its y along the bone. Taking it from the
273
+ * joint's world matrix instead would be wrong wherever a rig's bind orientation is not
274
+ * the bone direction, which is most rigs.
275
+ */
276
+ const dx = tx - hx;
277
+ const dy = ty - hy;
278
+ const dz = tz - hz;
279
+ const length = Math.sqrt(dx * dx + dy * dy + dz * dz);
280
+ if (length > 0) {
281
+ alignYTo(dx / length, dy / length, dz / length, TMP);
282
+ w.bodies.rotX[body] = TMP[0] ?? 0;
283
+ w.bodies.rotY[body] = TMP[1] ?? 0;
284
+ w.bodies.rotZ[body] = TMP[2] ?? 0;
285
+ w.bodies.rotW[body] = TMP[3] ?? 1;
286
+ }
287
+ /* At rest, which is the half that makes it a reaction rather than a continuation: a body
288
+ carrying the speed of a two-second fall is thrown across the character the instant it
289
+ is written into the pose. */
290
+ w.bodies.velX[body] = 0;
291
+ w.bodies.velY[body] = 0;
292
+ w.bodies.velZ[body] = 0;
293
+ w.bodies.angX[body] = 0;
294
+ w.bodies.angY[body] = 0;
295
+ w.bodies.angZ[body] = 0;
296
+ w.wakeIsland(body);
297
+ }
298
+ },
299
+ rootX() {
300
+ return root < 0 ? 0 : boneHeadX(world, bodyOf[root] ?? 0, firstBoneHalf);
301
+ },
302
+ rootY() {
303
+ return root < 0 ? 0 : boneHeadY(world, bodyOf[root] ?? 0, firstBoneHalf);
304
+ },
305
+ rootZ() {
306
+ return root < 0 ? 0 : boneHeadZ(world, bodyOf[root] ?? 0, firstBoneHalf);
307
+ },
308
+ };
309
+ }
310
+ const WORLD_ROT = new Float32Array(256 * 4);
311
+ const PARENT_ROT = new Float32Array(4);
312
+ const TARGET_ROT = new Float32Array(4);
313
+ const CURRENT_ROT = new Float32Array(4);
314
+ const ERROR_ROT = new Float32Array(4);
315
+ /*
316
+ * A bone's head, from the body standing along it: the centre, half a bone back along the body's
317
+ * own +y.
318
+ *
319
+ * **Written out per component rather than rotating a vector**, because a caller asks for one
320
+ * number at a time and the three columns of a quaternion's rotation matrix are three short
321
+ * expressions. This is +y turned by the body's rotation — the second column — and nothing else,
322
+ * so an axis it does not need costs nothing. `alignYTo` at build time is the other half of the
323
+ * same fact: a capsule stands along +y, so +y is where the bone points.
324
+ *
325
+ * The cost is that the three do not share the multiplies they have in common. That is the trade a
326
+ * per-axis accessor makes, and it is a handful of arithmetic against a call a consumer makes once
327
+ * or three times a frame.
328
+ */
329
+ function boneHeadX(world, body, half) {
330
+ const x = world.bodies.rotX[body] ?? 0;
331
+ const y = world.bodies.rotY[body] ?? 0;
332
+ const z = world.bodies.rotZ[body] ?? 0;
333
+ const w = world.bodies.rotW[body] ?? 1;
334
+ return (world.bodies.posX[body] ?? 0) - half * 2 * (x * y - z * w);
335
+ }
336
+ function boneHeadY(world, body, half) {
337
+ const x = world.bodies.rotX[body] ?? 0;
338
+ const z = world.bodies.rotZ[body] ?? 0;
339
+ return (world.bodies.posY[body] ?? 0) - half * (1 - 2 * (x * x + z * z));
340
+ }
341
+ function boneHeadZ(world, body, half) {
342
+ const x = world.bodies.rotX[body] ?? 0;
343
+ const y = world.bodies.rotY[body] ?? 0;
344
+ const z = world.bodies.rotZ[body] ?? 0;
345
+ const w = world.bodies.rotW[body] ?? 1;
346
+ return (world.bodies.posZ[body] ?? 0) - half * 2 * (y * z + x * w);
347
+ }
348
+ function readBodyRotation(world, body, out) {
349
+ out[0] = world.bodies.rotX[body] ?? 0;
350
+ out[1] = world.bodies.rotY[body] ?? 0;
351
+ out[2] = world.bodies.rotZ[body] ?? 0;
352
+ out[3] = world.bodies.rotW[body] ?? 1;
353
+ }
354
+ /** The rotation part of a column-major 4x4, as a quaternion. */
355
+ function matrixRotation(m, index, out, at) {
356
+ const o = index * 16;
357
+ const m00 = m[o] ?? 1;
358
+ const m01 = m[o + 4] ?? 0;
359
+ const m02 = m[o + 8] ?? 0;
360
+ const m10 = m[o + 1] ?? 0;
361
+ const m11 = m[o + 5] ?? 1;
362
+ const m12 = m[o + 9] ?? 0;
363
+ const m20 = m[o + 2] ?? 0;
364
+ const m21 = m[o + 6] ?? 0;
365
+ const m22 = m[o + 10] ?? 1;
366
+ const trace = m00 + m11 + m22;
367
+ if (trace > 0) {
368
+ const s = Math.sqrt(trace + 1) * 2;
369
+ out[at + 3] = s / 4;
370
+ out[at] = (m21 - m12) / s;
371
+ out[at + 1] = (m02 - m20) / s;
372
+ out[at + 2] = (m10 - m01) / s;
373
+ }
374
+ else if (m00 > m11 && m00 > m22) {
375
+ const s = Math.sqrt(1 + m00 - m11 - m22) * 2;
376
+ out[at + 3] = (m21 - m12) / s;
377
+ out[at] = s / 4;
378
+ out[at + 1] = (m01 + m10) / s;
379
+ out[at + 2] = (m02 + m20) / s;
380
+ }
381
+ else if (m11 > m22) {
382
+ const s = Math.sqrt(1 + m11 - m00 - m22) * 2;
383
+ out[at + 3] = (m02 - m20) / s;
384
+ out[at] = (m01 + m10) / s;
385
+ out[at + 1] = s / 4;
386
+ out[at + 2] = (m12 + m21) / s;
387
+ }
388
+ else {
389
+ const s = Math.sqrt(1 + m22 - m00 - m11) * 2;
390
+ out[at + 3] = (m10 - m01) / s;
391
+ out[at] = (m02 + m20) / s;
392
+ out[at + 1] = (m12 + m21) / s;
393
+ out[at + 2] = s / 4;
394
+ }
395
+ }
396
+ /** A rotation taking +y onto the given unit direction, by the shortest arc. */
397
+ function alignYTo(dx, dy, dz, out) {
398
+ const dot = dy;
399
+ if (dot > 0.999999) {
400
+ out[0] = 0;
401
+ out[1] = 0;
402
+ out[2] = 0;
403
+ out[3] = 1;
404
+ return;
405
+ }
406
+ if (dot < -0.999999) {
407
+ // Opposite: a half turn about any perpendicular axis.
408
+ out[0] = 0;
409
+ out[1] = 0;
410
+ out[2] = 1;
411
+ out[3] = 0;
412
+ return;
413
+ }
414
+ // Axis is +y crossed with the direction; the half-angle form avoids a trig call.
415
+ const ax = dz;
416
+ const az = -dx;
417
+ const w = 1 + dot;
418
+ const len = Math.sqrt(ax * ax + az * az + w * w) || 1;
419
+ out[0] = ax / len;
420
+ out[1] = 0;
421
+ out[2] = az / len;
422
+ out[3] = w / len;
423
+ }
424
+ /** `conj(a) · b` into `out`. */
425
+ function multiplyConjugate(a, b, bAt, out, at) {
426
+ const ax = -a[0];
427
+ const ay = -a[1];
428
+ const az = -a[2];
429
+ const aw = a[3];
430
+ const bx = b[bAt] ?? 0;
431
+ const by = b[bAt + 1] ?? 0;
432
+ const bz = b[bAt + 2] ?? 0;
433
+ const bw = b[bAt + 3] ?? 1;
434
+ out[at] = aw * bx + ax * bw + ay * bz - az * by;
435
+ out[at + 1] = aw * by - ax * bz + ay * bw + az * bx;
436
+ out[at + 2] = aw * bz + ax * by - ay * bx + az * bw;
437
+ out[at + 3] = aw * bw - ax * bx - ay * by - az * bz;
438
+ }
439
+ /** `a · b` where `a` is a Float64 quaternion, into `out`. */
440
+ function multiplyInto(a, b, bAt, out, at) {
441
+ const ax = a[0];
442
+ const ay = a[1];
443
+ const az = a[2];
444
+ const aw = a[3];
445
+ const bx = b[bAt] ?? 0;
446
+ const by = b[bAt + 1] ?? 0;
447
+ const bz = b[bAt + 2] ?? 0;
448
+ const bw = b[bAt + 3] ?? 1;
449
+ out[at] = aw * bx + ax * bw + ay * bz - az * by;
450
+ out[at + 1] = aw * by - ax * bz + ay * bw + az * bx;
451
+ out[at + 2] = aw * bz + ax * by - ay * bx + az * bw;
452
+ out[at + 3] = aw * bw - ax * bx - ay * by - az * bz;
453
+ }
454
+ /** `a · b` for two Float32 quaternions. */
455
+ function multiplyRaw(a, aAt, b, bAt, out, at) {
456
+ const ax = a[aAt] ?? 0;
457
+ const ay = a[aAt + 1] ?? 0;
458
+ const az = a[aAt + 2] ?? 0;
459
+ const aw = a[aAt + 3] ?? 1;
460
+ const bx = b[bAt] ?? 0;
461
+ const by = b[bAt + 1] ?? 0;
462
+ const bz = b[bAt + 2] ?? 0;
463
+ const bw = b[bAt + 3] ?? 1;
464
+ out[at] = aw * bx + ax * bw + ay * bz - az * by;
465
+ out[at + 1] = aw * by - ax * bz + ay * bw + az * bx;
466
+ out[at + 2] = aw * bz + ax * by - ay * bx + az * bw;
467
+ out[at + 3] = aw * bw - ax * bx - ay * by - az * bz;
468
+ }
469
+ /** `conj(a) · b` for two Float32 quaternions. */
470
+ function conjugateMultiply(a, aAt, b, bAt, out, at) {
471
+ const ax = -(a[aAt] ?? 0);
472
+ const ay = -(a[aAt + 1] ?? 0);
473
+ const az = -(a[aAt + 2] ?? 0);
474
+ const aw = a[aAt + 3] ?? 1;
475
+ const bx = b[bAt] ?? 0;
476
+ const by = b[bAt + 1] ?? 0;
477
+ const bz = b[bAt + 2] ?? 0;
478
+ const bw = b[bAt + 3] ?? 1;
479
+ out[at] = aw * bx + ax * bw + ay * bz - az * by;
480
+ out[at + 1] = aw * by - ax * bz + ay * bw + az * bx;
481
+ out[at + 2] = aw * bz + ax * by - ay * bx + az * bw;
482
+ out[at + 3] = aw * bw - ax * bx - ay * by - az * bz;
483
+ }
484
+ function copy4(from, fromAt, to, toAt) {
485
+ to[toAt] = from[fromAt] ?? 0;
486
+ to[toAt + 1] = from[fromAt + 1] ?? 0;
487
+ to[toAt + 2] = from[fromAt + 2] ?? 0;
488
+ to[toAt + 3] = from[fromAt + 3] ?? 1;
489
+ }