@driftengine/physics 4.8.3 → 4.8.5

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.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Graph-colour batches: constraints grouped so that no batch moves a particle twice.
3
+ *
4
+ * **What makes a batch parallel.** Every constraint in a batch reads and writes particles no other
5
+ * constraint in it writes, so the batch can be solved in any order — one GPU thread a constraint —
6
+ * and give the answer it gives in sequence. The batches themselves still run one after another,
7
+ * which keeps the solve a Gauss–Seidel one: each batch sees what the last one moved.
8
+ *
9
+ * **A kinematic particle may be shared freely.** Nothing writes it, so two constraints holding the
10
+ * same pinned particle cannot race; colouring around it would only add batches.
11
+ *
12
+ * Greedy, in the order the constraints are given: each takes the first colour none of its moving
13
+ * particles has yet. Not the fewest colours there are, which is a hard problem; a chain gets two and
14
+ * a triangle mesh's edges about six to nine, which is what a solver colouring cooked data gets too.
15
+ */
16
+ /** Constraints reordered by colour, and where each colour's run starts. */
17
+ export interface ClothBatches {
18
+ /** Constraint indices, batch by batch. */
19
+ readonly order: Uint32Array;
20
+ /** Offsets into `order`: batch `b` is `order[batches[b]]` up to `order[batches[b + 1]]`. */
21
+ readonly batches: Uint32Array;
22
+ }
23
+ /** Colour `indices`, `arity` particles a constraint, around the particles `inverseMass` pins. */
24
+ export declare function colourConstraints(indices: Uint32Array, arity: number, inverseMass: Float32Array): ClothBatches;
25
+ /**
26
+ * Given batches, checked: they cover every constraint once, in order, and none moves a particle
27
+ * twice. Refused by name, because an overlap is a race on the GPU and a wrong answer nowhere else.
28
+ */
29
+ export declare function givenBatches(name: string, indices: Uint32Array, arity: number, inverseMass: Float32Array, batches: Uint32Array): ClothBatches;
30
+ /**
31
+ * A kind of constraint's batches: the set-up's own, checked, where it gives them, and coloured here
32
+ * where it does not. One decision for both solvers, which must batch a garment the same way.
33
+ */
34
+ export declare function batchesOf(name: string, indices: Uint32Array, arity: number, inverseMass: Float32Array, given: Uint32Array | undefined): ClothBatches;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Graph-colour batches: constraints grouped so that no batch moves a particle twice.
3
+ *
4
+ * **What makes a batch parallel.** Every constraint in a batch reads and writes particles no other
5
+ * constraint in it writes, so the batch can be solved in any order — one GPU thread a constraint —
6
+ * and give the answer it gives in sequence. The batches themselves still run one after another,
7
+ * which keeps the solve a Gauss–Seidel one: each batch sees what the last one moved.
8
+ *
9
+ * **A kinematic particle may be shared freely.** Nothing writes it, so two constraints holding the
10
+ * same pinned particle cannot race; colouring around it would only add batches.
11
+ *
12
+ * Greedy, in the order the constraints are given: each takes the first colour none of its moving
13
+ * particles has yet. Not the fewest colours there are, which is a hard problem; a chain gets two and
14
+ * a triangle mesh's edges about six to nine, which is what a solver colouring cooked data gets too.
15
+ */
16
+ /** Colour `indices`, `arity` particles a constraint, around the particles `inverseMass` pins. */
17
+ export function colourConstraints(indices, arity, inverseMass) {
18
+ const constraints = indices.length / arity;
19
+ const colour = new Int32Array(constraints);
20
+ /* For each particle, a bit set of the colours already holding it, grown as colours are added. */
21
+ const used = [];
22
+ let colours = 0;
23
+ for (let k = 0; k < constraints; k++) {
24
+ let chosen = 0;
25
+ for (;; chosen++) {
26
+ let free = true;
27
+ for (let j = 0; j < arity && free; j++) {
28
+ const p = indices[k * arity + j];
29
+ if (inverseMass[p] === 0)
30
+ continue;
31
+ if (used[p]?.has(chosen) === true)
32
+ free = false;
33
+ }
34
+ if (free)
35
+ break;
36
+ }
37
+ colour[k] = chosen;
38
+ colours = Math.max(colours, chosen + 1);
39
+ for (let j = 0; j < arity; j++) {
40
+ const p = indices[k * arity + j];
41
+ if (inverseMass[p] === 0)
42
+ continue;
43
+ (used[p] ??= new Set()).add(chosen);
44
+ }
45
+ }
46
+ const batches = new Uint32Array(colours + 1);
47
+ for (let k = 0; k < constraints; k++)
48
+ batches[colour[k] + 1]++;
49
+ for (let c = 0; c < colours; c++)
50
+ batches[c + 1] = batches[c + 1] + batches[c];
51
+ const cursor = batches.slice(0, colours);
52
+ const order = new Uint32Array(constraints);
53
+ for (let k = 0; k < constraints; k++)
54
+ order[cursor[colour[k]]++] = k;
55
+ return { order, batches };
56
+ }
57
+ /**
58
+ * Given batches, checked: they cover every constraint once, in order, and none moves a particle
59
+ * twice. Refused by name, because an overlap is a race on the GPU and a wrong answer nowhere else.
60
+ */
61
+ export function givenBatches(name, indices, arity, inverseMass, batches) {
62
+ const constraints = indices.length / arity;
63
+ if (batches[0] !== 0 || batches[batches.length - 1] !== constraints) {
64
+ throw new Error(`skinned cloth: ${name} batches run from ${batches[0]} to ${batches[batches.length - 1]}, ` +
65
+ `and must cover constraints 0 to ${constraints}`);
66
+ }
67
+ const seen = new Int32Array(inverseMass.length).fill(-1);
68
+ for (let b = 0; b + 1 < batches.length; b++) {
69
+ for (let k = batches[b]; k < batches[b + 1]; k++) {
70
+ for (let j = 0; j < arity; j++) {
71
+ const p = indices[k * arity + j];
72
+ if (inverseMass[p] === 0)
73
+ continue;
74
+ if (seen[p] === b) {
75
+ throw new Error(`skinned cloth: ${name} batch ${b} moves particle ${p} twice, so it cannot run in parallel`);
76
+ }
77
+ seen[p] = b;
78
+ }
79
+ }
80
+ }
81
+ return { order: Uint32Array.from({ length: constraints }, (_, k) => k), batches };
82
+ }
83
+ /**
84
+ * A kind of constraint's batches: the set-up's own, checked, where it gives them, and coloured here
85
+ * where it does not. One decision for both solvers, which must batch a garment the same way.
86
+ */
87
+ export function batchesOf(name, indices, arity, inverseMass, given) {
88
+ return given === undefined
89
+ ? colourConstraints(indices, arity, inverseMass)
90
+ : givenBatches(name, indices, arity, inverseMass, given);
91
+ }
@@ -0,0 +1,55 @@
1
+ import { ClothTargets } from './clothTargets.ts';
2
+ import type { ResolvedClothParameters, SkinnedClothSetup } from './skinnedClothSetup.ts';
3
+ /** What moves a skinned cloth's particles: the CPU's arrays, or a GPU's buffers. */
4
+ export interface ClothDevice {
5
+ /**
6
+ * The targets changed what they hold — took a pose, settled on it, or consumed the one a frame
7
+ * stepped toward. A device that keeps its own copy of the skinned positions follows here.
8
+ */
9
+ targetsChanged(change: 'posed' | 'settled' | 'consumed'): void;
10
+ /** Every particle at its latest skinned position, at rest, its step start with it. */
11
+ rest(): void;
12
+ /**
13
+ * One fixed step, `fraction` of the way from the last pose to the latest. `blend` is the share of
14
+ * the simulation kept against the skinned pose after it, or 0 for no blend at all — not 1, which
15
+ * is a real last step of a blend and is applied.
16
+ */
17
+ step(fraction: number, blend: number): void;
18
+ }
19
+ /** A skinned cloth's schedule, over one device. */
20
+ export declare class ClothControl {
21
+ private readonly device;
22
+ readonly parameters: ResolvedClothParameters;
23
+ readonly targets: ClothTargets;
24
+ private accumulator;
25
+ /** Steps left in the blend from the skinned pose after a reset. */
26
+ private blendLeft;
27
+ /**
28
+ * `particlesOnCpu` false leaves the skinned particle positions to the device: the targets keep
29
+ * the model matrices, the skin matrices and the colliders, which is all a GPU needs from them.
30
+ */
31
+ constructor(setup: SkinnedClothSetup, device: ClothDevice, particlesOnCpu?: boolean);
32
+ /**
33
+ * The skeleton's pose for the steps to come: each joint's global matrix, model space, sixteen
34
+ * floats a joint, and the model matrix. The first pose, and one past the teleport thresholds,
35
+ * resets the cloth to it; any other is reached across the steps of the next `advance`.
36
+ */
37
+ setPose(globals: Float32Array, model: Float32Array): void;
38
+ /**
39
+ * Every particle at its skinned position, at rest; then the set-up's settle steps at once, and its
40
+ * blend steps from the skinned pose into the simulation over the steps that follow.
41
+ */
42
+ reset(): void;
43
+ /**
44
+ * Advance by `dt` seconds of the caller's time, in whole fixed steps; how many ran. The remainder
45
+ * is kept for the next call, and `alpha` says how far into the next step it reaches.
46
+ */
47
+ advance(dt: number): number;
48
+ /** How far the time handed to `advance` reaches into the next step, 0 to 1. */
49
+ get alpha(): number;
50
+ /** One fixed step, all the way to the latest pose: for a caller in the engine's fixed-step loop. */
51
+ step(): void;
52
+ private consume;
53
+ /** The targets moved to `fraction`, the share a blend keeps, then the device's step. */
54
+ private stepAt;
55
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * When a skinned cloth steps: its poses, whole fixed steps, the reset a teleport forces, the steps
3
+ * a reset settles and the blend back into the simulation after it — apart from what a step does to
4
+ * the particles, which a `ClothDevice` owns.
5
+ *
6
+ * **Apart so that two solvers take one decision.** `SkinnedCloth` here moves particles in arrays on
7
+ * the CPU; `@driftengine/core` moves them in buffers on a GPU. Both are driven by this, so a frame
8
+ * runs the same steps at the same fractions on either, a teleport resets both at the same pose, and
9
+ * a blend reaches the simulation over the same steps — which is what lets the device parity check
10
+ * compare positions rather than schedules.
11
+ *
12
+ * **What it gives up**: a device sees a step as a fraction and a blend share, not as the arrays it
13
+ * came from, so a device that wanted a different schedule — substeps chosen per frame, say — would
14
+ * have to grow this rather than decide it alone.
15
+ */
16
+ import { jumped } from './clothPose.js';
17
+ import { ClothTargets } from './clothTargets.js';
18
+ import { resolveClothParameters } from './skinnedClothSetup.js';
19
+ /** A skinned cloth's schedule, over one device. */
20
+ export class ClothControl {
21
+ device;
22
+ parameters;
23
+ targets;
24
+ accumulator = 0;
25
+ /** Steps left in the blend from the skinned pose after a reset. */
26
+ blendLeft = 0;
27
+ /**
28
+ * `particlesOnCpu` false leaves the skinned particle positions to the device: the targets keep
29
+ * the model matrices, the skin matrices and the colliders, which is all a GPU needs from them.
30
+ */
31
+ constructor(setup, device, particlesOnCpu = true) {
32
+ this.device = device;
33
+ this.parameters = resolveClothParameters(setup.parameters);
34
+ this.targets = new ClothTargets(setup, particlesOnCpu);
35
+ }
36
+ /**
37
+ * The skeleton's pose for the steps to come: each joint's global matrix, model space, sixteen
38
+ * floats a joint, and the model matrix. The first pose, and one past the teleport thresholds,
39
+ * resets the cloth to it; any other is reached across the steps of the next `advance`.
40
+ */
41
+ setPose(globals, model) {
42
+ const { teleportDistance, teleportAngle } = this.parameters;
43
+ const first = !this.targets.posed;
44
+ const jump = !first && jumped(this.targets.model, model, teleportDistance, teleportAngle);
45
+ this.targets.pose(globals, model);
46
+ this.device.targetsChanged('posed');
47
+ if (first || jump)
48
+ this.reset();
49
+ }
50
+ /**
51
+ * Every particle at its skinned position, at rest; then the set-up's settle steps at once, and its
52
+ * blend steps from the skinned pose into the simulation over the steps that follow.
53
+ */
54
+ reset() {
55
+ this.targets.settle();
56
+ this.device.targetsChanged('settled');
57
+ this.device.rest();
58
+ this.accumulator = 0;
59
+ this.blendLeft = 0;
60
+ for (let s = 0; s < this.parameters.settleSteps; s++)
61
+ this.stepAt(1);
62
+ this.blendLeft = this.parameters.blendSteps;
63
+ }
64
+ /**
65
+ * Advance by `dt` seconds of the caller's time, in whole fixed steps; how many ran. The remainder
66
+ * is kept for the next call, and `alpha` says how far into the next step it reaches.
67
+ */
68
+ advance(dt) {
69
+ this.accumulator += dt;
70
+ const step = this.parameters.step;
71
+ /* A millionth of a step of slack, so 1/30 is two steps of 1/60 and not one and a remainder. */
72
+ const steps = Math.floor((this.accumulator + step * 1e-6) / step);
73
+ for (let k = 0; k < steps; k++) {
74
+ this.accumulator -= step;
75
+ this.stepAt((k + 1) / steps);
76
+ }
77
+ if (steps > 0)
78
+ this.consume();
79
+ if (this.accumulator < 0)
80
+ this.accumulator = 0;
81
+ return steps;
82
+ }
83
+ /** How far the time handed to `advance` reaches into the next step, 0 to 1. */
84
+ get alpha() {
85
+ return Math.min(1, this.accumulator / this.parameters.step);
86
+ }
87
+ /** One fixed step, all the way to the latest pose: for a caller in the engine's fixed-step loop. */
88
+ step() {
89
+ this.stepAt(1);
90
+ this.consume();
91
+ }
92
+ consume() {
93
+ this.targets.consume();
94
+ this.device.targetsChanged('consumed');
95
+ }
96
+ /** The targets moved to `fraction`, the share a blend keeps, then the device's step. */
97
+ stepAt(fraction) {
98
+ this.targets.at(fraction);
99
+ let blend = 0;
100
+ if (this.blendLeft > 0) {
101
+ blend = (this.parameters.blendSteps - this.blendLeft + 1) / this.parameters.blendSteps;
102
+ this.blendLeft--;
103
+ }
104
+ this.device.step(fraction, blend);
105
+ }
106
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Where a skinned cloth's particles may not go: the limits measured from each particle's skinned
3
+ * position, and the colliders its skeleton carries.
4
+ *
5
+ * **Projections, not constraints with compliance.** A limit is a statement about the body — a
6
+ * garment does not pass through the hip it is cut for — so a particle past one is put back on its
7
+ * surface, at once and wholly. Applied after the constraint iterations of every substep, so a stiff
8
+ * garment pulling a particle into the body loses.
9
+ *
10
+ * Allocation-free, and each function a kernel the GPU solver mirrors.
11
+ */
12
+ /** A particle further than `maxDistance[i] · scale` from its skinned position is put back on that sphere. */
13
+ export declare function applyMaxDistance(position: Float32Array, inverseMass: Float32Array, target: Float32Array, maxDistance: Float32Array, scale: number): void;
14
+ /**
15
+ * A sphere the particle may not enter, on one side of its skinned position: behind it along the
16
+ * skinned normal for a backstop (`side` −1), in front for a frontstop (`side` +1). `stops` holds a
17
+ * distance and a radius a particle; the centre is `distance + radius` from the skinned position,
18
+ * so the sphere's near surface is `distance` from it. An infinite distance is no stop.
19
+ */
20
+ export declare function applyStops(position: Float32Array, inverseMass: Float32Array, target: Float32Array, normal: Float32Array, stops: Float32Array, side: number): void;
21
+ /**
22
+ * Every particle out of every collider: a sphere, or a tapered capsule from `ends[k·6]` to
23
+ * `ends[k·6+3]` whose radius runs from `radii[k·2]` to `radii[k·2+1]` — plus the particle's
24
+ * thickness and the cloth's margin.
25
+ *
26
+ * **A tapered capsule is pushed out from the nearest point on its axis**, at the radius interpolated
27
+ * there. That is the usual approximation of a cone-swept sphere and what character cloth solvers
28
+ * ship: exact where the two radii are equal, and where they differ it puts a particle a little off
29
+ * the true surface on the narrowing side — by a fraction of the taper over the capsule's length,
30
+ * which for a limb is millimetres. What would make it wrong is a collider tapering steeply over a
31
+ * short length, a cone more than a limb; the exact distance to a swept sphere is the fix then.
32
+ */
33
+ export declare function applyColliders(position: Float32Array, inverseMass: Float32Array, thickness: Float32Array | null, margin: number, ends: Float32Array, radii: Float32Array, colliders: number): void;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Where a skinned cloth's particles may not go: the limits measured from each particle's skinned
3
+ * position, and the colliders its skeleton carries.
4
+ *
5
+ * **Projections, not constraints with compliance.** A limit is a statement about the body — a
6
+ * garment does not pass through the hip it is cut for — so a particle past one is put back on its
7
+ * surface, at once and wholly. Applied after the constraint iterations of every substep, so a stiff
8
+ * garment pulling a particle into the body loses.
9
+ *
10
+ * Allocation-free, and each function a kernel the GPU solver mirrors.
11
+ */
12
+ /** A particle further than `maxDistance[i] · scale` from its skinned position is put back on that sphere. */
13
+ export function applyMaxDistance(position, inverseMass, target, maxDistance, scale) {
14
+ for (let i = 0; i < inverseMass.length; i++) {
15
+ if (inverseMass[i] === 0)
16
+ continue;
17
+ const limit = maxDistance[i] * scale;
18
+ if (!(limit < Infinity))
19
+ continue;
20
+ const at = i * 3;
21
+ const dx = position[at] - target[at];
22
+ const dy = position[at + 1] - target[at + 1];
23
+ const dz = position[at + 2] - target[at + 2];
24
+ const d2 = dx * dx + dy * dy + dz * dz;
25
+ if (d2 <= limit * limit)
26
+ continue;
27
+ const s = limit / Math.sqrt(d2);
28
+ position[at] = target[at] + dx * s;
29
+ position[at + 1] = target[at + 1] + dy * s;
30
+ position[at + 2] = target[at + 2] + dz * s;
31
+ }
32
+ }
33
+ /**
34
+ * A sphere the particle may not enter, on one side of its skinned position: behind it along the
35
+ * skinned normal for a backstop (`side` −1), in front for a frontstop (`side` +1). `stops` holds a
36
+ * distance and a radius a particle; the centre is `distance + radius` from the skinned position,
37
+ * so the sphere's near surface is `distance` from it. An infinite distance is no stop.
38
+ */
39
+ export function applyStops(position, inverseMass, target, normal, stops, side) {
40
+ for (let i = 0; i < inverseMass.length; i++) {
41
+ if (inverseMass[i] === 0)
42
+ continue;
43
+ const distance = stops[i * 2];
44
+ const radius = stops[i * 2 + 1];
45
+ if (!(distance < Infinity) || !(radius > 0))
46
+ continue;
47
+ const at = i * 3;
48
+ const reach = (distance + radius) * side;
49
+ const cx = target[at] + normal[at] * reach;
50
+ const cy = target[at + 1] + normal[at + 1] * reach;
51
+ const cz = target[at + 2] + normal[at + 2] * reach;
52
+ pushOut(position, at, cx, cy, cz, radius);
53
+ }
54
+ }
55
+ /** Put the point at `at` on the sphere's surface if it is inside. A point at the centre stays. */
56
+ function pushOut(position, at, cx, cy, cz, radius) {
57
+ const dx = position[at] - cx;
58
+ const dy = position[at + 1] - cy;
59
+ const dz = position[at + 2] - cz;
60
+ const d2 = dx * dx + dy * dy + dz * dz;
61
+ if (d2 >= radius * radius || d2 < 1e-18)
62
+ return;
63
+ const s = radius / Math.sqrt(d2);
64
+ position[at] = cx + dx * s;
65
+ position[at + 1] = cy + dy * s;
66
+ position[at + 2] = cz + dz * s;
67
+ }
68
+ /**
69
+ * Every particle out of every collider: a sphere, or a tapered capsule from `ends[k·6]` to
70
+ * `ends[k·6+3]` whose radius runs from `radii[k·2]` to `radii[k·2+1]` — plus the particle's
71
+ * thickness and the cloth's margin.
72
+ *
73
+ * **A tapered capsule is pushed out from the nearest point on its axis**, at the radius interpolated
74
+ * there. That is the usual approximation of a cone-swept sphere and what character cloth solvers
75
+ * ship: exact where the two radii are equal, and where they differ it puts a particle a little off
76
+ * the true surface on the narrowing side — by a fraction of the taper over the capsule's length,
77
+ * which for a limb is millimetres. What would make it wrong is a collider tapering steeply over a
78
+ * short length, a cone more than a limb; the exact distance to a swept sphere is the fix then.
79
+ */
80
+ export function applyColliders(position, inverseMass, thickness, margin, ends, radii, colliders) {
81
+ for (let i = 0; i < inverseMass.length; i++) {
82
+ if (inverseMass[i] === 0)
83
+ continue;
84
+ const at = i * 3;
85
+ const pad = (thickness === null ? 0 : thickness[i]) + margin;
86
+ for (let k = 0; k < colliders; k++) {
87
+ const ax = ends[k * 6];
88
+ const ay = ends[k * 6 + 1];
89
+ const az = ends[k * 6 + 2];
90
+ const ex = ends[k * 6 + 3] - ax;
91
+ const ey = ends[k * 6 + 4] - ay;
92
+ const ez = ends[k * 6 + 5] - az;
93
+ const e2 = ex * ex + ey * ey + ez * ez;
94
+ let t = 0;
95
+ if (e2 > 1e-18) {
96
+ t =
97
+ ((position[at] - ax) * ex +
98
+ (position[at + 1] - ay) * ey +
99
+ (position[at + 2] - az) * ez) /
100
+ e2;
101
+ t = Math.min(1, Math.max(0, t));
102
+ }
103
+ const r0 = radii[k * 2];
104
+ const r1 = radii[k * 2 + 1];
105
+ pushOut(position, at, ax + ex * t, ay + ey * t, az + ez * t, r0 + (r1 - r0) * t + pad);
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,36 @@
1
+ /** `a × b` into `out` at `at`, column-major 4×4. `out` may not alias either. */
2
+ export declare function multiply4(a: ArrayLike<number>, aAt: number, b: ArrayLike<number>, bAt: number, out: Float32Array, at: number): void;
3
+ /** `model × global_j × inverseBind_j` for every joint, into `skin`. */
4
+ export declare function skinMatrices(globals: Float32Array, inverseBind: Float32Array, model: Float32Array, scratch: Float32Array, skin: Float32Array): void;
5
+ /**
6
+ * Each particle's skinned position and normal, by up to eight influences, into `position` and
7
+ * `normal`. Without a rig (`skin` null), the rest pose under the model matrix alone.
8
+ */
9
+ export declare function skinParticles(rest: Float32Array, restNormal: Float32Array | null, joints: Float32Array | null, weights: Float32Array | null, joints2: Float32Array | null, weights2: Float32Array | null, skin: Float32Array | null, model: Float32Array, position: Float32Array, normal: Float32Array): void;
10
+ /**
11
+ * Each collider's two ends in the world, six floats a collider: `model × global × frame` applied to
12
+ * the frame's origin and to `length` along its +Z. A sphere's two ends are one point.
13
+ */
14
+ export declare function placeColliders(colliders: readonly {
15
+ readonly joint: number;
16
+ readonly length?: number;
17
+ readonly frame?: Float32Array;
18
+ }[], globals: Float32Array, model: Float32Array, scratch: Float32Array, ends: Float32Array): void;
19
+ /**
20
+ * Carry free particles by the share of the character's motion they do not keep as their own.
21
+ *
22
+ * The character moved by `next × previous⁻¹`: a rotation about its old origin and a move of that
23
+ * origin. A particle is moved by `1 − angularInertia` of the first and `1 − linearInertia` of the
24
+ * second, its velocity turned by the same share — so inertia 1 leaves the cloth in the world and 0
25
+ * moves it as if it were simulated in the character's space. **A blend of the rotated point, not a
26
+ * fraction of the angle**, which is exact at the two ends and, for the few degrees a character turns
27
+ * in a step, indistinguishable between them; it needs no trigonometry, so it is the same bits on
28
+ * every engine. Both matrices rigid.
29
+ */
30
+ export declare function carryParticles(position: Float32Array, velocity: Float32Array, inverseMass: Float32Array, previous: Float32Array, next: Float32Array, linearInertia: number, angularInertia: number): void;
31
+ /**
32
+ * How far the character jumped between two poses: its origin's distance and its rotation's angle.
33
+ * The angle through `trace`, `cos θ = (tr R − 1) / 2`, compared as a cosine, through `exactCos` so
34
+ * every engine draws the line in one place.
35
+ */
36
+ export declare function jumped(previous: Float32Array, next: Float32Array, distance: number, angle: number): boolean;