@driftengine/physics 4.8.2 → 4.8.3
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.
- package/README.md +1 -1
- package/dist/ragdoll.js +126 -11
- package/package.json +1 -1
- package/src/ragdoll.ts +142 -11
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ kinematic sweep that resolves a moving body against a static world one axis at a
|
|
|
5
5
|
|
|
6
6
|
**It imports no other engine package.** Together with `@driftengine/entities` that makes a
|
|
7
7
|
deterministic simulation you can run with no renderer in its module graph — in Node, in a worker,
|
|
8
|
-
or on a server, which is what an authoritative host needs. **
|
|
8
|
+
or on a server, which is what an authoritative host needs. **47.0 KB gzipped** standalone, measured
|
|
9
9
|
by `scripts/size-gate.test.mjs` against `scripts/fixtures/size/physics-only.ts`.
|
|
10
10
|
|
|
11
11
|
**`@driftengine/core` re-exports every name here**, so reaching for collision through the core
|
package/dist/ragdoll.js
CHANGED
|
@@ -82,6 +82,11 @@ export function ragdollFromBones(world, parents, worldMatrices, options = {}) {
|
|
|
82
82
|
* the bones meet.
|
|
83
83
|
*/
|
|
84
84
|
const halfOf = new Float32Array(jointCount);
|
|
85
|
+
/**
|
|
86
|
+
* Each bone's rig offset over its length: 1 for a rig at unit scale, and what turns a length in
|
|
87
|
+
* metres back into the parent's own units where the rig is scaled.
|
|
88
|
+
*/
|
|
89
|
+
const restScale = new Float32Array(jointCount).fill(1);
|
|
85
90
|
let boneCount = 0;
|
|
86
91
|
let firstBone = -1;
|
|
87
92
|
/** Half the first bone's length, which is how far its head sits from the body's centre. */
|
|
@@ -123,6 +128,10 @@ export function ragdollFromBones(world, parents, worldMatrices, options = {}) {
|
|
|
123
128
|
});
|
|
124
129
|
bodyOf[j] = body;
|
|
125
130
|
halfOf[j] = length / 2;
|
|
131
|
+
const rx = localRest[j * 3] ?? 0;
|
|
132
|
+
const ry = localRest[j * 3 + 1] ?? 0;
|
|
133
|
+
const rz = localRest[j * 3 + 2] ?? 0;
|
|
134
|
+
restScale[j] = Math.sqrt(rx * rx + ry * ry + rz * rz) / length;
|
|
126
135
|
// What takes the body's rotation back to each of its two joints', captured at rest. Parents
|
|
127
136
|
// come before children in a rig, so the start joint's rotation is already read.
|
|
128
137
|
multiplyConjugate(TMP, restRotation, j * 4, bodyToJoint, j * 4);
|
|
@@ -139,15 +148,67 @@ export function ragdollFromBones(world, parents, worldMatrices, options = {}) {
|
|
|
139
148
|
firstBoneHalf = length / 2;
|
|
140
149
|
}
|
|
141
150
|
}
|
|
151
|
+
/*
|
|
152
|
+
* **A bone too short for a body is transparent.** Each joint resolves to the nearest one at or
|
|
153
|
+
* above it that a body ends at, or to its root: what hangs from a collar sitting on the chest
|
|
154
|
+
* joint hangs from the bone that ends at the chest. A rig puts one joint exactly on another as
|
|
155
|
+
* often as not, and a bone under `minLength` gets no body, so until 4.8.3 the spine above such a
|
|
156
|
+
* collar was jointed to nothing and collided with every bone it met there — compared by index,
|
|
157
|
+
* they shared no end. **What it gives up** is the skipped bone's own length, under `minLength`,
|
|
158
|
+
* which the joints below absorb exactly at rest: see `restAnchor`.
|
|
159
|
+
*/
|
|
160
|
+
const attach = new Int32Array(jointCount);
|
|
161
|
+
for (let j = 0; j < jointCount; j++) {
|
|
162
|
+
let a = j;
|
|
163
|
+
while ((bodyOf[a] ?? -1) < 0 && (parents[a] ?? -1) >= 0)
|
|
164
|
+
a = parents[a] ?? -1;
|
|
165
|
+
attach[j] = a;
|
|
166
|
+
}
|
|
167
|
+
/** Where a bone's parent end really hangs: its parent joint, seen through any bone too short. */
|
|
168
|
+
const hangsFrom = (j) => {
|
|
169
|
+
const p = parents[j] ?? -1;
|
|
170
|
+
return p < 0 ? -1 : (attach[p] ?? p);
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* Bone `j`'s body-frame point at joint `at`'s rest position, into `REST_ANCHOR`. Where `at` is
|
|
174
|
+
* the bone's own parent joint this is its head, `(0, -half, 0)`, exactly as before; through a
|
|
175
|
+
* short bone it is where the two bones meet at rest, so a joint anchored there moves nothing on
|
|
176
|
+
* the first tick.
|
|
177
|
+
*/
|
|
178
|
+
const restAnchor = (j, at) => {
|
|
179
|
+
const p = parents[j] ?? -1;
|
|
180
|
+
if (at === p) {
|
|
181
|
+
REST_ANCHOR[0] = 0;
|
|
182
|
+
REST_ANCHOR[1] = -(halfOf[j] ?? 0);
|
|
183
|
+
REST_ANCHOR[2] = 0;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const body = bodyOf[j] ?? 0;
|
|
187
|
+
const dx = (worldMatrices[at * 16 + 12] ?? 0) - (world.bodies.posX[body] ?? 0);
|
|
188
|
+
const dy = (worldMatrices[at * 16 + 13] ?? 0) - (world.bodies.posY[body] ?? 0);
|
|
189
|
+
const dz = (worldMatrices[at * 16 + 14] ?? 0) - (world.bodies.posZ[body] ?? 0);
|
|
190
|
+
/* Into the body's frame: turned by the conjugate of its rotation. */
|
|
191
|
+
const ux = -(world.bodies.rotX[body] ?? 0);
|
|
192
|
+
const uy = -(world.bodies.rotY[body] ?? 0);
|
|
193
|
+
const uz = -(world.bodies.rotZ[body] ?? 0);
|
|
194
|
+
const w = world.bodies.rotW[body] ?? 1;
|
|
195
|
+
const cx = uy * dz - uz * dy;
|
|
196
|
+
const cy = uz * dx - ux * dz;
|
|
197
|
+
const cz = ux * dy - uy * dx;
|
|
198
|
+
REST_ANCHOR[0] = dx + 2 * (w * cx + (uy * cz - uz * cy));
|
|
199
|
+
REST_ANCHOR[1] = dy + 2 * (w * cy + (uz * cx - ux * cz));
|
|
200
|
+
REST_ANCHOR[2] = dz + 2 * (w * cz + (ux * cy - uy * cx));
|
|
201
|
+
};
|
|
142
202
|
// Joint each bone to its parent's bone, at the shared end.
|
|
143
203
|
for (let j = 0; j < jointCount; j++) {
|
|
144
204
|
const body = bodyOf[j] ?? -1;
|
|
145
205
|
if (body < 0)
|
|
146
206
|
continue;
|
|
147
|
-
const p =
|
|
207
|
+
const p = hangsFrom(j);
|
|
148
208
|
const parentBody = p >= 0 ? (bodyOf[p] ?? -1) : -1;
|
|
149
209
|
if (parentBody < 0)
|
|
150
210
|
continue;
|
|
211
|
+
restAnchor(j, p);
|
|
151
212
|
world.addJoint({
|
|
152
213
|
type: JOINT_CONE_TWIST,
|
|
153
214
|
bodyA: parentBody,
|
|
@@ -174,7 +235,9 @@ export function ragdollFromBones(world, parents, worldMatrices, options = {}) {
|
|
|
174
235
|
* so the first tick still moves nothing.
|
|
175
236
|
*/
|
|
176
237
|
anchorAY: halfOf[p] ?? 0,
|
|
177
|
-
|
|
238
|
+
anchorBX: REST_ANCHOR[0],
|
|
239
|
+
anchorBY: REST_ANCHOR[1],
|
|
240
|
+
anchorBZ: REST_ANCHOR[2],
|
|
178
241
|
});
|
|
179
242
|
}
|
|
180
243
|
/*
|
|
@@ -191,7 +254,7 @@ export function ragdollFromBones(world, parents, worldMatrices, options = {}) {
|
|
|
191
254
|
const body = bodyOf[j] ?? -1;
|
|
192
255
|
if (body < 0)
|
|
193
256
|
continue;
|
|
194
|
-
const p =
|
|
257
|
+
const p = hangsFrom(j);
|
|
195
258
|
if (p < 0 || (bodyOf[p] ?? -1) >= 0)
|
|
196
259
|
continue;
|
|
197
260
|
const first = firstUnder[p] ?? -1;
|
|
@@ -199,6 +262,11 @@ export function ragdollFromBones(world, parents, worldMatrices, options = {}) {
|
|
|
199
262
|
firstUnder[p] = j;
|
|
200
263
|
continue;
|
|
201
264
|
}
|
|
265
|
+
restAnchor(first, p);
|
|
266
|
+
const ax = REST_ANCHOR[0];
|
|
267
|
+
const ay = REST_ANCHOR[1];
|
|
268
|
+
const az = REST_ANCHOR[2];
|
|
269
|
+
restAnchor(j, p);
|
|
202
270
|
world.addJoint({
|
|
203
271
|
type: JOINT_CONE_TWIST,
|
|
204
272
|
bodyA: bodyOf[first] ?? 0,
|
|
@@ -206,8 +274,12 @@ export function ragdollFromBones(world, parents, worldMatrices, options = {}) {
|
|
|
206
274
|
swingCos: limitAt(swingCos, Math.SQRT1_2, j),
|
|
207
275
|
twistSin: limitAt(twistSin, 0.3826834323650898, j),
|
|
208
276
|
axisY: 1,
|
|
209
|
-
|
|
210
|
-
|
|
277
|
+
anchorAX: ax,
|
|
278
|
+
anchorAY: ay,
|
|
279
|
+
anchorAZ: az,
|
|
280
|
+
anchorBX: REST_ANCHOR[0],
|
|
281
|
+
anchorBY: REST_ANCHOR[1],
|
|
282
|
+
anchorBZ: REST_ANCHOR[2],
|
|
211
283
|
});
|
|
212
284
|
}
|
|
213
285
|
/*
|
|
@@ -230,8 +302,9 @@ export function ragdollFromBones(world, parents, worldMatrices, options = {}) {
|
|
|
230
302
|
world.ignorePair(body, other);
|
|
231
303
|
continue;
|
|
232
304
|
}
|
|
233
|
-
|
|
234
|
-
const
|
|
305
|
+
/* Seen through any bone too short for a body, which is where they really meet. */
|
|
306
|
+
const pj = hangsFrom(j);
|
|
307
|
+
const pk = hangsFrom(k);
|
|
235
308
|
/* Jointed, either way round, or two bones hanging off the same parent joint: siblings share
|
|
236
309
|
a head the way a parent and child share a point, so they overlap there too. */
|
|
237
310
|
if (pk === j || pj === k || (pj === pk && pj >= 0))
|
|
@@ -284,10 +357,25 @@ export function ragdollFromBones(world, parents, worldMatrices, options = {}) {
|
|
|
284
357
|
}
|
|
285
358
|
if (p < 0)
|
|
286
359
|
continue;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
360
|
+
/*
|
|
361
|
+
* **Placed along its own bone as the body holds it**, at the rig's length: the body's +y,
|
|
362
|
+
* in the parent's frame. For the first bone under a joint that is the rig's offset exactly,
|
|
363
|
+
* because the joint turns with that body; for a second or third — a thigh beside the spine,
|
|
364
|
+
* a clavicle beside the neck — it is what keeps the branch on its own body. Placed by the
|
|
365
|
+
* rig's offset, every branch but the first was swung by the first one's turn: reported as
|
|
366
|
+
* a foot 0.24 m and an arm 0.17 m from their bodies after a fall. A joint with no bone of
|
|
367
|
+
* its own keeps the rig's offset. **What would make it wrong** is a bone the body
|
|
368
|
+
* stretches, which a rigid capsule cannot.
|
|
369
|
+
*/
|
|
370
|
+
const body = bodyOf[j] ?? -1;
|
|
371
|
+
if (body >= 0 && KNOWN[p] === 1) {
|
|
372
|
+
boneInParentFrame(world, body, 2 * (halfOf[j] ?? 0) * (restScale[j] ?? 1), WORLD_ROT, p * 4, out.translation, j * 3);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
out.translation[j * 3] = localRest[j * 3] ?? 0;
|
|
376
|
+
out.translation[j * 3 + 1] = localRest[j * 3 + 1] ?? 0;
|
|
377
|
+
out.translation[j * 3 + 2] = localRest[j * 3 + 2] ?? 0;
|
|
378
|
+
}
|
|
291
379
|
out.scale[j * 3] = 1;
|
|
292
380
|
out.scale[j * 3 + 1] = 1;
|
|
293
381
|
out.scale[j * 3 + 2] = 1;
|
|
@@ -402,6 +490,8 @@ const WORLD_ROT = new Float32Array(256 * 4);
|
|
|
402
490
|
/** Whether `writePose` recovered each joint's rotation this call: 1 if a body speaks for it. */
|
|
403
491
|
const KNOWN = new Uint8Array(256);
|
|
404
492
|
const START_ROT = new Float32Array(4);
|
|
493
|
+
/** One joint anchor in a body's own frame, filled by `restAnchor` and read at once. */
|
|
494
|
+
const REST_ANCHOR = new Float64Array(3);
|
|
405
495
|
const PARENT_ROT = new Float32Array(4);
|
|
406
496
|
const TARGET_ROT = new Float32Array(4);
|
|
407
497
|
const CURRENT_ROT = new Float32Array(4);
|
|
@@ -560,6 +650,31 @@ function multiplyByConjugate(a, b, bAt, out) {
|
|
|
560
650
|
out[2] = aw * bz + ax * by - ay * bx + az * bw;
|
|
561
651
|
out[3] = aw * bw - ax * bx - ay * by - az * bz;
|
|
562
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* A body's own +y at `length`, turned by the body and then back by the inverse of a parent's world
|
|
655
|
+
* rotation: the bone as the body holds it, in the parent's frame.
|
|
656
|
+
*/
|
|
657
|
+
function boneInParentFrame(world, body, length, parent, parentAt, out, outAt) {
|
|
658
|
+
const bx = world.bodies.rotX[body] ?? 0;
|
|
659
|
+
const by = world.bodies.rotY[body] ?? 0;
|
|
660
|
+
const bz = world.bodies.rotZ[body] ?? 0;
|
|
661
|
+
const bw = world.bodies.rotW[body] ?? 1;
|
|
662
|
+
/* The body's +y in the world, scaled. */
|
|
663
|
+
const vx = 2 * length * (bx * by - bw * bz);
|
|
664
|
+
const vy = length * (1 - 2 * (bx * bx + bz * bz));
|
|
665
|
+
const vz = 2 * length * (by * bz + bw * bx);
|
|
666
|
+
/* Turned by the conjugate of the parent's rotation: v + 2w(u×v) + 2u×(u×v), u = −q.xyz. */
|
|
667
|
+
const ux = -(parent[parentAt] ?? 0);
|
|
668
|
+
const uy = -(parent[parentAt + 1] ?? 0);
|
|
669
|
+
const uz = -(parent[parentAt + 2] ?? 0);
|
|
670
|
+
const w = parent[parentAt + 3] ?? 1;
|
|
671
|
+
const cx = uy * vz - uz * vy;
|
|
672
|
+
const cy = uz * vx - ux * vz;
|
|
673
|
+
const cz = ux * vy - uy * vx;
|
|
674
|
+
out[outAt] = vx + 2 * (w * cx + (uy * cz - uz * cy));
|
|
675
|
+
out[outAt + 1] = vy + 2 * (w * cy + (uz * cx - ux * cz));
|
|
676
|
+
out[outAt + 2] = vz + 2 * (w * cz + (ux * cy - uy * cx));
|
|
677
|
+
}
|
|
563
678
|
/** `conj(a) · b` into `out`. */
|
|
564
679
|
function multiplyConjugate(a, b, bAt, out, at) {
|
|
565
680
|
const ax = -a[0];
|
package/package.json
CHANGED
package/src/ragdoll.ts
CHANGED
|
@@ -230,6 +230,11 @@ export function ragdollFromBones(
|
|
|
230
230
|
* the bones meet.
|
|
231
231
|
*/
|
|
232
232
|
const halfOf = new Float32Array(jointCount);
|
|
233
|
+
/**
|
|
234
|
+
* Each bone's rig offset over its length: 1 for a rig at unit scale, and what turns a length in
|
|
235
|
+
* metres back into the parent's own units where the rig is scaled.
|
|
236
|
+
*/
|
|
237
|
+
const restScale = new Float32Array(jointCount).fill(1);
|
|
233
238
|
let boneCount = 0;
|
|
234
239
|
let firstBone = -1;
|
|
235
240
|
/** Half the first bone's length, which is how far its head sits from the body's centre. */
|
|
@@ -273,6 +278,10 @@ export function ragdollFromBones(
|
|
|
273
278
|
});
|
|
274
279
|
bodyOf[j] = body;
|
|
275
280
|
halfOf[j] = length / 2;
|
|
281
|
+
const rx = localRest[j * 3] ?? 0;
|
|
282
|
+
const ry = localRest[j * 3 + 1] ?? 0;
|
|
283
|
+
const rz = localRest[j * 3 + 2] ?? 0;
|
|
284
|
+
restScale[j] = Math.sqrt(rx * rx + ry * ry + rz * rz) / length;
|
|
276
285
|
// What takes the body's rotation back to each of its two joints', captured at rest. Parents
|
|
277
286
|
// come before children in a rig, so the start joint's rotation is already read.
|
|
278
287
|
multiplyConjugate(TMP, restRotation, j * 4, bodyToJoint, j * 4);
|
|
@@ -288,13 +297,65 @@ export function ragdollFromBones(
|
|
|
288
297
|
}
|
|
289
298
|
}
|
|
290
299
|
|
|
300
|
+
/*
|
|
301
|
+
* **A bone too short for a body is transparent.** Each joint resolves to the nearest one at or
|
|
302
|
+
* above it that a body ends at, or to its root: what hangs from a collar sitting on the chest
|
|
303
|
+
* joint hangs from the bone that ends at the chest. A rig puts one joint exactly on another as
|
|
304
|
+
* often as not, and a bone under `minLength` gets no body, so until 4.8.3 the spine above such a
|
|
305
|
+
* collar was jointed to nothing and collided with every bone it met there — compared by index,
|
|
306
|
+
* they shared no end. **What it gives up** is the skipped bone's own length, under `minLength`,
|
|
307
|
+
* which the joints below absorb exactly at rest: see `restAnchor`.
|
|
308
|
+
*/
|
|
309
|
+
const attach = new Int32Array(jointCount);
|
|
310
|
+
for (let j = 0; j < jointCount; j++) {
|
|
311
|
+
let a = j;
|
|
312
|
+
while ((bodyOf[a] ?? -1) < 0 && (parents[a] ?? -1) >= 0) a = parents[a] ?? -1;
|
|
313
|
+
attach[j] = a;
|
|
314
|
+
}
|
|
315
|
+
/** Where a bone's parent end really hangs: its parent joint, seen through any bone too short. */
|
|
316
|
+
const hangsFrom = (j: number): number => {
|
|
317
|
+
const p = parents[j] ?? -1;
|
|
318
|
+
return p < 0 ? -1 : (attach[p] ?? p);
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* Bone `j`'s body-frame point at joint `at`'s rest position, into `REST_ANCHOR`. Where `at` is
|
|
322
|
+
* the bone's own parent joint this is its head, `(0, -half, 0)`, exactly as before; through a
|
|
323
|
+
* short bone it is where the two bones meet at rest, so a joint anchored there moves nothing on
|
|
324
|
+
* the first tick.
|
|
325
|
+
*/
|
|
326
|
+
const restAnchor = (j: number, at: number): void => {
|
|
327
|
+
const p = parents[j] ?? -1;
|
|
328
|
+
if (at === p) {
|
|
329
|
+
REST_ANCHOR[0] = 0;
|
|
330
|
+
REST_ANCHOR[1] = -(halfOf[j] ?? 0);
|
|
331
|
+
REST_ANCHOR[2] = 0;
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const body = bodyOf[j] ?? 0;
|
|
335
|
+
const dx = (worldMatrices[at * 16 + 12] ?? 0) - (world.bodies.posX[body] ?? 0);
|
|
336
|
+
const dy = (worldMatrices[at * 16 + 13] ?? 0) - (world.bodies.posY[body] ?? 0);
|
|
337
|
+
const dz = (worldMatrices[at * 16 + 14] ?? 0) - (world.bodies.posZ[body] ?? 0);
|
|
338
|
+
/* Into the body's frame: turned by the conjugate of its rotation. */
|
|
339
|
+
const ux = -(world.bodies.rotX[body] ?? 0);
|
|
340
|
+
const uy = -(world.bodies.rotY[body] ?? 0);
|
|
341
|
+
const uz = -(world.bodies.rotZ[body] ?? 0);
|
|
342
|
+
const w = world.bodies.rotW[body] ?? 1;
|
|
343
|
+
const cx = uy * dz - uz * dy;
|
|
344
|
+
const cy = uz * dx - ux * dz;
|
|
345
|
+
const cz = ux * dy - uy * dx;
|
|
346
|
+
REST_ANCHOR[0] = dx + 2 * (w * cx + (uy * cz - uz * cy));
|
|
347
|
+
REST_ANCHOR[1] = dy + 2 * (w * cy + (uz * cx - ux * cz));
|
|
348
|
+
REST_ANCHOR[2] = dz + 2 * (w * cz + (ux * cy - uy * cx));
|
|
349
|
+
};
|
|
350
|
+
|
|
291
351
|
// Joint each bone to its parent's bone, at the shared end.
|
|
292
352
|
for (let j = 0; j < jointCount; j++) {
|
|
293
353
|
const body = bodyOf[j] ?? -1;
|
|
294
354
|
if (body < 0) continue;
|
|
295
|
-
const p =
|
|
355
|
+
const p = hangsFrom(j);
|
|
296
356
|
const parentBody = p >= 0 ? (bodyOf[p] ?? -1) : -1;
|
|
297
357
|
if (parentBody < 0) continue;
|
|
358
|
+
restAnchor(j, p);
|
|
298
359
|
world.addJoint({
|
|
299
360
|
type: JOINT_CONE_TWIST,
|
|
300
361
|
bodyA: parentBody,
|
|
@@ -321,7 +382,9 @@ export function ragdollFromBones(
|
|
|
321
382
|
* so the first tick still moves nothing.
|
|
322
383
|
*/
|
|
323
384
|
anchorAY: halfOf[p] ?? 0,
|
|
324
|
-
|
|
385
|
+
anchorBX: REST_ANCHOR[0],
|
|
386
|
+
anchorBY: REST_ANCHOR[1],
|
|
387
|
+
anchorBZ: REST_ANCHOR[2],
|
|
325
388
|
});
|
|
326
389
|
}
|
|
327
390
|
|
|
@@ -338,13 +401,18 @@ export function ragdollFromBones(
|
|
|
338
401
|
for (let j = 0; j < jointCount; j++) {
|
|
339
402
|
const body = bodyOf[j] ?? -1;
|
|
340
403
|
if (body < 0) continue;
|
|
341
|
-
const p =
|
|
404
|
+
const p = hangsFrom(j);
|
|
342
405
|
if (p < 0 || (bodyOf[p] ?? -1) >= 0) continue;
|
|
343
406
|
const first = firstUnder[p] ?? -1;
|
|
344
407
|
if (first < 0) {
|
|
345
408
|
firstUnder[p] = j;
|
|
346
409
|
continue;
|
|
347
410
|
}
|
|
411
|
+
restAnchor(first, p);
|
|
412
|
+
const ax = REST_ANCHOR[0];
|
|
413
|
+
const ay = REST_ANCHOR[1];
|
|
414
|
+
const az = REST_ANCHOR[2];
|
|
415
|
+
restAnchor(j, p);
|
|
348
416
|
world.addJoint({
|
|
349
417
|
type: JOINT_CONE_TWIST,
|
|
350
418
|
bodyA: bodyOf[first] ?? 0,
|
|
@@ -352,8 +420,12 @@ export function ragdollFromBones(
|
|
|
352
420
|
swingCos: limitAt(swingCos, Math.SQRT1_2, j),
|
|
353
421
|
twistSin: limitAt(twistSin, 0.3826834323650898, j),
|
|
354
422
|
axisY: 1,
|
|
355
|
-
|
|
356
|
-
|
|
423
|
+
anchorAX: ax,
|
|
424
|
+
anchorAY: ay,
|
|
425
|
+
anchorAZ: az,
|
|
426
|
+
anchorBX: REST_ANCHOR[0],
|
|
427
|
+
anchorBY: REST_ANCHOR[1],
|
|
428
|
+
anchorBZ: REST_ANCHOR[2],
|
|
357
429
|
});
|
|
358
430
|
}
|
|
359
431
|
|
|
@@ -375,8 +447,9 @@ export function ragdollFromBones(
|
|
|
375
447
|
world.ignorePair(body, other);
|
|
376
448
|
continue;
|
|
377
449
|
}
|
|
378
|
-
|
|
379
|
-
const
|
|
450
|
+
/* Seen through any bone too short for a body, which is where they really meet. */
|
|
451
|
+
const pj = hangsFrom(j);
|
|
452
|
+
const pk = hangsFrom(k);
|
|
380
453
|
/* Jointed, either way round, or two bones hanging off the same parent joint: siblings share
|
|
381
454
|
a head the way a parent and child share a point, so they overlap there too. */
|
|
382
455
|
if (pk === j || pj === k || (pj === pk && pj >= 0)) world.ignorePair(body, other);
|
|
@@ -427,10 +500,32 @@ export function ragdollFromBones(
|
|
|
427
500
|
copy4(WORLD_ROT, j * 4, out.rotation, j * 4);
|
|
428
501
|
}
|
|
429
502
|
if (p < 0) continue;
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
503
|
+
/*
|
|
504
|
+
* **Placed along its own bone as the body holds it**, at the rig's length: the body's +y,
|
|
505
|
+
* in the parent's frame. For the first bone under a joint that is the rig's offset exactly,
|
|
506
|
+
* because the joint turns with that body; for a second or third — a thigh beside the spine,
|
|
507
|
+
* a clavicle beside the neck — it is what keeps the branch on its own body. Placed by the
|
|
508
|
+
* rig's offset, every branch but the first was swung by the first one's turn: reported as
|
|
509
|
+
* a foot 0.24 m and an arm 0.17 m from their bodies after a fall. A joint with no bone of
|
|
510
|
+
* its own keeps the rig's offset. **What would make it wrong** is a bone the body
|
|
511
|
+
* stretches, which a rigid capsule cannot.
|
|
512
|
+
*/
|
|
513
|
+
const body = bodyOf[j] ?? -1;
|
|
514
|
+
if (body >= 0 && KNOWN[p] === 1) {
|
|
515
|
+
boneInParentFrame(
|
|
516
|
+
world,
|
|
517
|
+
body,
|
|
518
|
+
2 * (halfOf[j] ?? 0) * (restScale[j] ?? 1),
|
|
519
|
+
WORLD_ROT,
|
|
520
|
+
p * 4,
|
|
521
|
+
out.translation,
|
|
522
|
+
j * 3,
|
|
523
|
+
);
|
|
524
|
+
} else {
|
|
525
|
+
out.translation[j * 3] = localRest[j * 3] ?? 0;
|
|
526
|
+
out.translation[j * 3 + 1] = localRest[j * 3 + 1] ?? 0;
|
|
527
|
+
out.translation[j * 3 + 2] = localRest[j * 3 + 2] ?? 0;
|
|
528
|
+
}
|
|
434
529
|
out.scale[j * 3] = 1;
|
|
435
530
|
out.scale[j * 3 + 1] = 1;
|
|
436
531
|
out.scale[j * 3 + 2] = 1;
|
|
@@ -544,6 +639,8 @@ const WORLD_ROT = new Float32Array(256 * 4);
|
|
|
544
639
|
/** Whether `writePose` recovered each joint's rotation this call: 1 if a body speaks for it. */
|
|
545
640
|
const KNOWN = new Uint8Array(256);
|
|
546
641
|
const START_ROT = new Float32Array(4);
|
|
642
|
+
/** One joint anchor in a body's own frame, filled by `restAnchor` and read at once. */
|
|
643
|
+
const REST_ANCHOR = new Float64Array(3);
|
|
547
644
|
const PARENT_ROT = new Float32Array(4);
|
|
548
645
|
const TARGET_ROT = new Float32Array(4);
|
|
549
646
|
const CURRENT_ROT = new Float32Array(4);
|
|
@@ -721,6 +818,40 @@ function multiplyByConjugate(
|
|
|
721
818
|
out[3] = aw * bw - ax * bx - ay * by - az * bz;
|
|
722
819
|
}
|
|
723
820
|
|
|
821
|
+
/**
|
|
822
|
+
* A body's own +y at `length`, turned by the body and then back by the inverse of a parent's world
|
|
823
|
+
* rotation: the bone as the body holds it, in the parent's frame.
|
|
824
|
+
*/
|
|
825
|
+
function boneInParentFrame(
|
|
826
|
+
world: PhysicsWorld,
|
|
827
|
+
body: number,
|
|
828
|
+
length: number,
|
|
829
|
+
parent: Float32Array,
|
|
830
|
+
parentAt: number,
|
|
831
|
+
out: Float32Array,
|
|
832
|
+
outAt: number,
|
|
833
|
+
): void {
|
|
834
|
+
const bx = world.bodies.rotX[body] ?? 0;
|
|
835
|
+
const by = world.bodies.rotY[body] ?? 0;
|
|
836
|
+
const bz = world.bodies.rotZ[body] ?? 0;
|
|
837
|
+
const bw = world.bodies.rotW[body] ?? 1;
|
|
838
|
+
/* The body's +y in the world, scaled. */
|
|
839
|
+
const vx = 2 * length * (bx * by - bw * bz);
|
|
840
|
+
const vy = length * (1 - 2 * (bx * bx + bz * bz));
|
|
841
|
+
const vz = 2 * length * (by * bz + bw * bx);
|
|
842
|
+
/* Turned by the conjugate of the parent's rotation: v + 2w(u×v) + 2u×(u×v), u = −q.xyz. */
|
|
843
|
+
const ux = -(parent[parentAt] ?? 0);
|
|
844
|
+
const uy = -(parent[parentAt + 1] ?? 0);
|
|
845
|
+
const uz = -(parent[parentAt + 2] ?? 0);
|
|
846
|
+
const w = parent[parentAt + 3] ?? 1;
|
|
847
|
+
const cx = uy * vz - uz * vy;
|
|
848
|
+
const cy = uz * vx - ux * vz;
|
|
849
|
+
const cz = ux * vy - uy * vx;
|
|
850
|
+
out[outAt] = vx + 2 * (w * cx + (uy * cz - uz * cy));
|
|
851
|
+
out[outAt + 1] = vy + 2 * (w * cy + (uz * cx - ux * cz));
|
|
852
|
+
out[outAt + 2] = vz + 2 * (w * cz + (ux * cy - uy * cx));
|
|
853
|
+
}
|
|
854
|
+
|
|
724
855
|
/** `conj(a) · b` into `out`. */
|
|
725
856
|
function multiplyConjugate(
|
|
726
857
|
a: Float64Array,
|