@driftengine/physics 4.8.0 → 4.8.2
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/dist/controller.d.ts +2 -0
- package/dist/controller.js +81 -28
- package/dist/manifold.js +132 -58
- package/dist/query.js +28 -2
- package/dist/ragdoll.d.ts +9 -1
- package/dist/ragdoll.js +143 -37
- package/package.json +1 -1
- package/src/controller.ts +91 -26
- package/src/manifold.ts +154 -55
- package/src/query.ts +25 -2
- package/src/ragdoll.ts +163 -38
package/src/query.ts
CHANGED
|
@@ -858,7 +858,14 @@ export function shapecastWorld(
|
|
|
858
858
|
if (chosen === undefined) break;
|
|
859
859
|
copyManifold(chosen, manifold);
|
|
860
860
|
} else if (!collideShapes(shape, poseA, other, poseB, 1e9, manifold)) break;
|
|
861
|
-
|
|
861
|
+
/*
|
|
862
|
+
* **The nearest contact, not the first one written.** A capsule's manifold holds a point per
|
|
863
|
+
* end, and the first is its lower end: reading only that advanced the sweep by the distance
|
|
864
|
+
* from the lower ball while the upper one was the nearer, so a standing capsule swept clean
|
|
865
|
+
* through a beam at head height.
|
|
866
|
+
*/
|
|
867
|
+
let gap = Infinity;
|
|
868
|
+
for (let c = 0; c < manifold.count; c++) gap = Math.min(gap, manifold.separations[c] ?? gap);
|
|
862
869
|
if (gap < 1e-4) {
|
|
863
870
|
/*
|
|
864
871
|
* A shape already touching a surface it is travelling *along* has not run into it.
|
|
@@ -874,7 +881,23 @@ export function shapecastWorld(
|
|
|
874
881
|
touched = true;
|
|
875
882
|
break;
|
|
876
883
|
}
|
|
877
|
-
|
|
884
|
+
/*
|
|
885
|
+
* **How far the shapes may close along the contact normal, not along the travel.** The
|
|
886
|
+
* manifold's normal separates the two by `gap`, so they cannot meet before the sweep has
|
|
887
|
+
* closed that much along it. Dividing by the whole travel was always safe and crawled
|
|
888
|
+
* wherever the travel grazes the surface: a ball dropping past the lip of a step closes on it
|
|
889
|
+
* at a sixth of its speed, and twenty-four steps ended before it arrived, so the sweep said
|
|
890
|
+
* nothing was there. A mesh keeps the old step, since one triangle's normal does not separate
|
|
891
|
+
* the whole mesh.
|
|
892
|
+
*/
|
|
893
|
+
if (isMesh(shape) || isMesh(other)) {
|
|
894
|
+
t += gap / travel;
|
|
895
|
+
} else {
|
|
896
|
+
const closing = dx * manifold.nx + dy * manifold.ny + dz * manifold.nz;
|
|
897
|
+
// Not closing along the normal that separates them, so they never meet on this travel.
|
|
898
|
+
if (closing <= 1e-12) break;
|
|
899
|
+
t += gap / closing;
|
|
900
|
+
}
|
|
878
901
|
if (t > 1) break;
|
|
879
902
|
}
|
|
880
903
|
if (brushing || !touched || t > 1) continue;
|
package/src/ragdoll.ts
CHANGED
|
@@ -95,7 +95,15 @@ export interface Ragdoll {
|
|
|
95
95
|
readonly bodyOf: Int32Array;
|
|
96
96
|
/** How many bones were built. */
|
|
97
97
|
readonly boneCount: number;
|
|
98
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Write the ragdoll's current shape into a pose.
|
|
100
|
+
*
|
|
101
|
+
* Each joint is turned by the bone that **starts** at it, which is the joint a skinned limb
|
|
102
|
+
* follows — the forearm's body turns the elbow — and a joint with no bone below it rides the one
|
|
103
|
+
* it hangs off. Translations are the rig's own, in each parent's frame. A root's rotation is
|
|
104
|
+
* written in the frame the character's node is placed in, at `rootX`, `rootY` and `rootZ`, with
|
|
105
|
+
* no rotation of its own; a joint no bone reaches is left as the pose had it.
|
|
106
|
+
*/
|
|
99
107
|
writePose(out: PoseTarget): void;
|
|
100
108
|
/** Steer the bodies toward a pose. Weight 1 tracks it, weight 0 goes limp. */
|
|
101
109
|
drive(world: PhysicsWorld, pose: PoseTarget, weight: number): void;
|
|
@@ -185,8 +193,32 @@ export function ragdollFromBones(
|
|
|
185
193
|
};
|
|
186
194
|
|
|
187
195
|
const bodyOf = new Int32Array(jointCount).fill(-1);
|
|
188
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* `conj(bodyRotation) · jointWorldRotation` for the joint a bone **ends** at, so a joint with no
|
|
198
|
+
* bone of its own below it — a fingertip, a head's tip — can ride the bone it hangs off.
|
|
199
|
+
*/
|
|
189
200
|
const bodyToJoint = new Float32Array(jointCount * 4);
|
|
201
|
+
/**
|
|
202
|
+
* `conj(bodyRotation) · jointWorldRotation` for the joint a bone **starts** at, indexed like
|
|
203
|
+
* `bodyOf` — by the bone's own joint — and naming its parent's rotation.
|
|
204
|
+
*
|
|
205
|
+
* **This is the joint a skinned limb follows.** A forearm's vertices are weighted to the elbow,
|
|
206
|
+
* so the elbow's rotation is what points the forearm, and the body standing along the forearm is
|
|
207
|
+
* what carries it. Reading each joint off the bone that *ends* at it — the upper arm for the
|
|
208
|
+
* elbow — drew every limb one bone late: a hand 0.47 m from its body in a doll synced and never
|
|
209
|
+
* stepped, reported from a game. A bone is rigid in its start joint's frame, so the relation
|
|
210
|
+
* captured at rest holds for as long as the doll does.
|
|
211
|
+
*/
|
|
212
|
+
const bodyToStart = new Float32Array(jointCount * 4);
|
|
213
|
+
/** The first bone starting at each joint, by that bone's own index, or −1 where none does. */
|
|
214
|
+
const startBone = new Int32Array(jointCount).fill(-1);
|
|
215
|
+
/**
|
|
216
|
+
* Each joint's translation in its parent's frame at rest, which is what a pose holds.
|
|
217
|
+
*
|
|
218
|
+
* **In the parent's frame, not the world's.** It was the world offset between the two, which is
|
|
219
|
+
* the same number only where the parent is unrotated at rest — true of every chain this module's
|
|
220
|
+
* tests were built from, and of no rig a game loads.
|
|
221
|
+
*/
|
|
190
222
|
const localRest = new Float32Array(jointCount * 3);
|
|
191
223
|
const restRotation = new Float32Array(jointCount * 4);
|
|
192
224
|
/**
|
|
@@ -214,9 +246,7 @@ export function ragdollFromBones(
|
|
|
214
246
|
const tx = worldMatrices[j * 16 + 12] ?? 0;
|
|
215
247
|
const ty = worldMatrices[j * 16 + 13] ?? 0;
|
|
216
248
|
const tz = worldMatrices[j * 16 + 14] ?? 0;
|
|
217
|
-
|
|
218
|
-
localRest[j * 3 + 1] = ty - hy;
|
|
219
|
-
localRest[j * 3 + 2] = tz - hz;
|
|
249
|
+
intoParentFrame(worldMatrices, p, tx - hx, ty - hy, tz - hz, localRest, j * 3);
|
|
220
250
|
|
|
221
251
|
const dx = tx - hx;
|
|
222
252
|
const dy = ty - hy;
|
|
@@ -243,8 +273,12 @@ export function ragdollFromBones(
|
|
|
243
273
|
});
|
|
244
274
|
bodyOf[j] = body;
|
|
245
275
|
halfOf[j] = length / 2;
|
|
246
|
-
// What takes the body's rotation back to
|
|
276
|
+
// What takes the body's rotation back to each of its two joints', captured at rest. Parents
|
|
277
|
+
// come before children in a rig, so the start joint's rotation is already read.
|
|
247
278
|
multiplyConjugate(TMP, restRotation, j * 4, bodyToJoint, j * 4);
|
|
279
|
+
if (p >= j) matrixRotation(worldMatrices, p, restRotation, p * 4);
|
|
280
|
+
multiplyConjugate(TMP, restRotation, p * 4, bodyToStart, j * 4);
|
|
281
|
+
if ((startBone[p] ?? -1) < 0) startBone[p] = j;
|
|
248
282
|
boneCount++;
|
|
249
283
|
if (firstBone < 0) {
|
|
250
284
|
firstBone = j;
|
|
@@ -349,27 +383,50 @@ export function ragdollFromBones(
|
|
|
349
383
|
}
|
|
350
384
|
}
|
|
351
385
|
|
|
386
|
+
/**
|
|
387
|
+
* One joint's world rotation, from the body that speaks for it: the first bone starting at the
|
|
388
|
+
* joint, or — for a joint with none below it, a fingertip — the bone it hangs off, which it then
|
|
389
|
+
* rides rigidly. False where neither exists and nothing can be said.
|
|
390
|
+
*/
|
|
391
|
+
const recoverJoint = (w: PhysicsWorld, j: number, out: Float32Array, at: number): boolean => {
|
|
392
|
+
const start = startBone[j] ?? -1;
|
|
393
|
+
if (start >= 0) {
|
|
394
|
+
readBodyRotation(w, bodyOf[start] ?? 0, TMP);
|
|
395
|
+
multiplyInto(TMP, bodyToStart, start * 4, out, at);
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
const own = bodyOf[j] ?? -1;
|
|
399
|
+
if (own < 0) return false;
|
|
400
|
+
readBodyRotation(w, own, TMP);
|
|
401
|
+
multiplyInto(TMP, bodyToJoint, j * 4, out, at);
|
|
402
|
+
return true;
|
|
403
|
+
};
|
|
404
|
+
|
|
352
405
|
const root = firstBone;
|
|
353
406
|
return {
|
|
354
407
|
bodyOf,
|
|
355
408
|
boneCount,
|
|
356
409
|
writePose(out: PoseTarget): void {
|
|
410
|
+
/*
|
|
411
|
+
* Two passes, because a joint's local rotation needs its parent's world rotation and a rig
|
|
412
|
+
* is not obliged to list parents first. The first recovers every joint a body speaks for.
|
|
413
|
+
*/
|
|
414
|
+
for (let j = 0; j < jointCount; j++)
|
|
415
|
+
KNOWN[j] = recoverJoint(world, j, WORLD_ROT, j * 4) ? 1 : 0;
|
|
357
416
|
for (let j = 0; j < jointCount; j++) {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (body < 0) {
|
|
361
|
-
// No bone: leave the joint as the pose already had it.
|
|
417
|
+
if (KNOWN[j] !== 1) {
|
|
418
|
+
// No bone at either end: leave the joint as the pose already had it.
|
|
362
419
|
continue;
|
|
363
420
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
multiplyInto(TMP, bodyToJoint, j * 4, WORLD_ROT, j * 4);
|
|
367
|
-
const parentBody = p >= 0 ? (bodyOf[p] ?? -1) : -1;
|
|
368
|
-
if (parentBody >= 0) {
|
|
421
|
+
const p = parents[j] ?? -1;
|
|
422
|
+
if (p >= 0 && KNOWN[p] === 1) {
|
|
369
423
|
conjugateMultiply(WORLD_ROT, p * 4, WORLD_ROT, j * 4, out.rotation, j * 4);
|
|
370
424
|
} else {
|
|
425
|
+
/* A root, or a joint whose parent no bone speaks for: its rotation in the frame the
|
|
426
|
+
caller places the character's node in, which `rootX` and its two siblings answer. */
|
|
371
427
|
copy4(WORLD_ROT, j * 4, out.rotation, j * 4);
|
|
372
428
|
}
|
|
429
|
+
if (p < 0) continue;
|
|
373
430
|
// Bones are rigid, so translation stays what the rig was built with.
|
|
374
431
|
out.translation[j * 3] = localRest[j * 3] ?? 0;
|
|
375
432
|
out.translation[j * 3 + 1] = localRest[j * 3 + 1] ?? 0;
|
|
@@ -382,25 +439,29 @@ export function ragdollFromBones(
|
|
|
382
439
|
drive(w: PhysicsWorld, pose: PoseTarget, weight: number): void {
|
|
383
440
|
if (weight <= 0) return;
|
|
384
441
|
const k = weight > 1 ? 1 : weight;
|
|
385
|
-
for (let
|
|
386
|
-
const body = bodyOf[
|
|
442
|
+
for (let c = 0; c < jointCount; c++) {
|
|
443
|
+
const body = bodyOf[c] ?? -1;
|
|
387
444
|
if (body < 0) continue;
|
|
388
|
-
|
|
389
|
-
|
|
445
|
+
/*
|
|
446
|
+
* **A bone is steered by the joint it starts at**, the one whose rotation points it — the
|
|
447
|
+
* forearm by the elbow — which is the same joint `writePose` reads it back as. Steered by
|
|
448
|
+
* the joint it ends at, a pose's elbow bend went into the upper arm.
|
|
449
|
+
*/
|
|
450
|
+
const j = parents[c] ?? -1;
|
|
451
|
+
if (j < 0) continue;
|
|
390
452
|
/*
|
|
391
453
|
* The target is the pose's local rotation composed onto the parent's *current* world
|
|
392
454
|
* rotation, so a driven limb follows the animation relative to a body that may itself have
|
|
393
455
|
* been knocked aside — which is what a partial ragdoll is.
|
|
394
456
|
*/
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
multiplyInto(TMP, bodyToJoint, p * 4, PARENT_ROT, 0);
|
|
457
|
+
const p = parents[j] ?? -1;
|
|
458
|
+
if (p >= 0 && recoverJoint(w, p, PARENT_ROT, 0)) {
|
|
398
459
|
multiplyRaw(PARENT_ROT, 0, pose.rotation, j * 4, TARGET_ROT, 0);
|
|
399
460
|
} else {
|
|
400
461
|
copy4(pose.rotation, j * 4, TARGET_ROT, 0);
|
|
401
462
|
}
|
|
402
463
|
readBodyRotation(w, body, TMP);
|
|
403
|
-
multiplyInto(TMP,
|
|
464
|
+
multiplyInto(TMP, bodyToStart, c * 4, CURRENT_ROT, 0);
|
|
404
465
|
// Twice the vector part of target · conj(current) is the rotation that closes the gap.
|
|
405
466
|
conjugateMultiply(CURRENT_ROT, 0, TARGET_ROT, 0, ERROR_ROT, 0);
|
|
406
467
|
let ex = ERROR_ROT[0] ?? 0;
|
|
@@ -437,22 +498,23 @@ export function ragdollFromBones(
|
|
|
437
498
|
w.bodies.posZ[body] = (hz + tz) / 2;
|
|
438
499
|
|
|
439
500
|
/*
|
|
440
|
-
* The rotation is
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
* the bone
|
|
501
|
+
* **The rotation is the start joint's, carried through the relation captured at rest**,
|
|
502
|
+
* which is the inverse of what `writePose` reads: `jointRotation · conj(bodyToStart)`.
|
|
503
|
+
*
|
|
504
|
+
* It was the shortest arc from +y onto the bone, which points the capsule correctly and
|
|
505
|
+
* throws away the bone's twist about its own length — so a joint with two bones below it, a
|
|
506
|
+
* wrist with a thumb beside the fingers, came back turned about the first and put the
|
|
507
|
+
* second somewhere else. The joint's world matrix alone would be wrong too, wherever a
|
|
508
|
+
* rig's bind orientation is not the bone direction, which is most rigs; the captured
|
|
509
|
+
* relation is what absorbs that. **What would make it wrong** is a pose that stretches a
|
|
510
|
+
* bone, which a ragdoll's rigid bones cannot follow either way.
|
|
444
511
|
*/
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
w.bodies.rotX[body] = TMP[0] ?? 0;
|
|
452
|
-
w.bodies.rotY[body] = TMP[1] ?? 0;
|
|
453
|
-
w.bodies.rotZ[body] = TMP[2] ?? 0;
|
|
454
|
-
w.bodies.rotW[body] = TMP[3] ?? 1;
|
|
455
|
-
}
|
|
512
|
+
matrixRotation(worldMatrices, p, START_ROT, 0);
|
|
513
|
+
multiplyByConjugate(START_ROT, bodyToStart, j * 4, TMP);
|
|
514
|
+
w.bodies.rotX[body] = TMP[0] ?? 0;
|
|
515
|
+
w.bodies.rotY[body] = TMP[1] ?? 0;
|
|
516
|
+
w.bodies.rotZ[body] = TMP[2] ?? 0;
|
|
517
|
+
w.bodies.rotW[body] = TMP[3] ?? 1;
|
|
456
518
|
|
|
457
519
|
/* At rest, which is the half that makes it a reaction rather than a continuation: a body
|
|
458
520
|
carrying the speed of a two-second fall is thrown across the character the instant it
|
|
@@ -479,6 +541,9 @@ export function ragdollFromBones(
|
|
|
479
541
|
}
|
|
480
542
|
|
|
481
543
|
const WORLD_ROT = new Float32Array(256 * 4);
|
|
544
|
+
/** Whether `writePose` recovered each joint's rotation this call: 1 if a body speaks for it. */
|
|
545
|
+
const KNOWN = new Uint8Array(256);
|
|
546
|
+
const START_ROT = new Float32Array(4);
|
|
482
547
|
const PARENT_ROT = new Float32Array(4);
|
|
483
548
|
const TARGET_ROT = new Float32Array(4);
|
|
484
549
|
const CURRENT_ROT = new Float32Array(4);
|
|
@@ -596,6 +661,66 @@ function alignYTo(dx: number, dy: number, dz: number, out: Float64Array): void {
|
|
|
596
661
|
out[3] = w / len;
|
|
597
662
|
}
|
|
598
663
|
|
|
664
|
+
/**
|
|
665
|
+
* A world-space offset from joint `p`, expressed in `p`'s own frame: the inverse of the matrix's
|
|
666
|
+
* upper 3x3 applied to it, so a scaled joint is undone as well as a rotated one.
|
|
667
|
+
*/
|
|
668
|
+
function intoParentFrame(
|
|
669
|
+
m: Float32Array,
|
|
670
|
+
p: number,
|
|
671
|
+
dx: number,
|
|
672
|
+
dy: number,
|
|
673
|
+
dz: number,
|
|
674
|
+
out: Float32Array,
|
|
675
|
+
at: number,
|
|
676
|
+
): void {
|
|
677
|
+
const o = p * 16;
|
|
678
|
+
const a = m[o] ?? 1;
|
|
679
|
+
const b = m[o + 4] ?? 0;
|
|
680
|
+
const c = m[o + 8] ?? 0;
|
|
681
|
+
const d = m[o + 1] ?? 0;
|
|
682
|
+
const e = m[o + 5] ?? 1;
|
|
683
|
+
const f = m[o + 9] ?? 0;
|
|
684
|
+
const g = m[o + 2] ?? 0;
|
|
685
|
+
const h = m[o + 6] ?? 0;
|
|
686
|
+
const i = m[o + 10] ?? 1;
|
|
687
|
+
const ei = e * i - f * h;
|
|
688
|
+
const fg = f * g - d * i;
|
|
689
|
+
const dh = d * h - e * g;
|
|
690
|
+
const det = a * ei + b * fg + c * dh;
|
|
691
|
+
if (Math.abs(det) < 1e-12) {
|
|
692
|
+
out[at] = dx;
|
|
693
|
+
out[at + 1] = dy;
|
|
694
|
+
out[at + 2] = dz;
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const inv = 1 / det;
|
|
698
|
+
out[at] = (ei * dx + (c * h - b * i) * dy + (b * f - c * e) * dz) * inv;
|
|
699
|
+
out[at + 1] = (fg * dx + (a * i - c * g) * dy + (c * d - a * f) * dz) * inv;
|
|
700
|
+
out[at + 2] = (dh * dx + (b * g - a * h) * dy + (a * e - b * d) * dz) * inv;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/** `a · conj(b)` into a Float64 quaternion, for turning a joint's rotation back into a body's. */
|
|
704
|
+
function multiplyByConjugate(
|
|
705
|
+
a: Float32Array,
|
|
706
|
+
b: Float32Array,
|
|
707
|
+
bAt: number,
|
|
708
|
+
out: Float64Array,
|
|
709
|
+
): void {
|
|
710
|
+
const ax = a[0] ?? 0;
|
|
711
|
+
const ay = a[1] ?? 0;
|
|
712
|
+
const az = a[2] ?? 0;
|
|
713
|
+
const aw = a[3] ?? 1;
|
|
714
|
+
const bx = -(b[bAt] ?? 0);
|
|
715
|
+
const by = -(b[bAt + 1] ?? 0);
|
|
716
|
+
const bz = -(b[bAt + 2] ?? 0);
|
|
717
|
+
const bw = b[bAt + 3] ?? 1;
|
|
718
|
+
out[0] = aw * bx + ax * bw + ay * bz - az * by;
|
|
719
|
+
out[1] = aw * by - ax * bz + ay * bw + az * bx;
|
|
720
|
+
out[2] = aw * bz + ax * by - ay * bx + az * bw;
|
|
721
|
+
out[3] = aw * bw - ax * bx - ay * by - az * bz;
|
|
722
|
+
}
|
|
723
|
+
|
|
599
724
|
/** `conj(a) · b` into `out`. */
|
|
600
725
|
function multiplyConjugate(
|
|
601
726
|
a: Float64Array,
|