@driftengine/physics 4.8.3 → 4.8.4
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/clothBatches.d.ts +34 -0
- package/dist/clothBatches.js +91 -0
- package/dist/clothControl.d.ts +55 -0
- package/dist/clothControl.js +106 -0
- package/dist/clothLimits.d.ts +33 -0
- package/dist/clothLimits.js +108 -0
- package/dist/clothPose.d.ts +36 -0
- package/dist/clothPose.js +225 -0
- package/dist/clothTargets.d.ts +44 -0
- package/dist/clothTargets.js +124 -0
- package/dist/exact.d.ts +80 -0
- package/dist/exact.js +331 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +19 -0
- package/dist/skinnedCloth.d.ts +63 -0
- package/dist/skinnedCloth.js +214 -0
- package/dist/skinnedClothSetup.d.ts +160 -0
- package/dist/skinnedClothSetup.js +120 -0
- package/dist/skinnedClothSolve.d.ts +22 -0
- package/dist/skinnedClothSolve.js +230 -0
- package/package.json +1 -1
- package/src/clothBatches.ts +115 -0
- package/src/clothControl.ts +131 -0
- package/src/clothLimits.ts +133 -0
- package/src/clothPose.ts +272 -0
- package/src/clothTargets.ts +146 -0
- package/src/exact.ts +344 -0
- package/src/index.ts +32 -0
- package/src/skinnedCloth.ts +323 -0
- package/src/skinnedClothSetup.ts +273 -0
- package/src/skinnedClothSolve.ts +283 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a skeleton puts a skinned cloth: each particle's skinned position and normal, each
|
|
3
|
+
* collider's ends, and the share of the character's own motion a free particle is carried by.
|
|
4
|
+
*
|
|
5
|
+
* **From joint globals and inverse binds, not from a skin palette.** A palette is `global × inverse
|
|
6
|
+
* bind` and is all skinning needs, but a collider sits on a joint's own frame, which only the
|
|
7
|
+
* global gives; taking both pieces lets one pose serve both. Model space throughout, then the model
|
|
8
|
+
* matrix, so the cloth simulates in the world and a turn swings it.
|
|
9
|
+
*
|
|
10
|
+
* **Normals are carried by each joint's upper 3×3**, not its inverse transpose: right for the
|
|
11
|
+
* rotations and uniform scales a rig is made of, and wrong for a sheared or non-uniformly scaled
|
|
12
|
+
* joint, which a garment's rig does not have. Renormalised after blending.
|
|
13
|
+
*
|
|
14
|
+
* Allocation-free: everything writes into arrays handed in.
|
|
15
|
+
*/
|
|
16
|
+
import { exactCos } from './exact.js';
|
|
17
|
+
/** `a × b` into `out` at `at`, column-major 4×4. `out` may not alias either. */
|
|
18
|
+
export function multiply4(a, aAt, b, bAt, out, at) {
|
|
19
|
+
for (let c = 0; c < 4; c++) {
|
|
20
|
+
for (let r = 0; r < 4; r++) {
|
|
21
|
+
let sum = 0;
|
|
22
|
+
for (let k = 0; k < 4; k++) {
|
|
23
|
+
sum += a[aAt + k * 4 + r] * b[bAt + c * 4 + k];
|
|
24
|
+
}
|
|
25
|
+
out[at + c * 4 + r] = sum;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** `model × global_j × inverseBind_j` for every joint, into `skin`. */
|
|
30
|
+
export function skinMatrices(globals, inverseBind, model, scratch, skin) {
|
|
31
|
+
const joints = inverseBind.length / 16;
|
|
32
|
+
for (let j = 0; j < joints; j++) {
|
|
33
|
+
multiply4(globals, j * 16, inverseBind, j * 16, scratch, 0);
|
|
34
|
+
multiply4(model, 0, scratch, 0, skin, j * 16);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Each particle's skinned position and normal, by up to eight influences, into `position` and
|
|
39
|
+
* `normal`. Without a rig (`skin` null), the rest pose under the model matrix alone.
|
|
40
|
+
*/
|
|
41
|
+
export function skinParticles(rest, restNormal, joints, weights, joints2, weights2, skin, model, position, normal) {
|
|
42
|
+
const count = rest.length / 3;
|
|
43
|
+
for (let i = 0; i < count; i++) {
|
|
44
|
+
const x = rest[i * 3];
|
|
45
|
+
const y = rest[i * 3 + 1];
|
|
46
|
+
const z = rest[i * 3 + 2];
|
|
47
|
+
const nx = restNormal === null ? 0 : restNormal[i * 3];
|
|
48
|
+
const ny = restNormal === null ? 0 : restNormal[i * 3 + 1];
|
|
49
|
+
const nz = restNormal === null ? 1 : restNormal[i * 3 + 2];
|
|
50
|
+
let px = 0;
|
|
51
|
+
let py = 0;
|
|
52
|
+
let pz = 0;
|
|
53
|
+
let qx = 0;
|
|
54
|
+
let qy = 0;
|
|
55
|
+
let qz = 0;
|
|
56
|
+
if (skin === null || joints === null || weights === null) {
|
|
57
|
+
const m = model;
|
|
58
|
+
px = m[0] * x + m[4] * y + m[8] * z + m[12];
|
|
59
|
+
py = m[1] * x + m[5] * y + m[9] * z + m[13];
|
|
60
|
+
pz = m[2] * x + m[6] * y + m[10] * z + m[14];
|
|
61
|
+
qx = m[0] * nx + m[4] * ny + m[8] * nz;
|
|
62
|
+
qy = m[1] * nx + m[5] * ny + m[9] * nz;
|
|
63
|
+
qz = m[2] * nx + m[6] * ny + m[10] * nz;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
for (let set = 0; set < 2; set++) {
|
|
67
|
+
const js = set === 0 ? joints : joints2;
|
|
68
|
+
const ws = set === 0 ? weights : weights2;
|
|
69
|
+
if (js === null || ws === null)
|
|
70
|
+
continue;
|
|
71
|
+
for (let k = 0; k < 4; k++) {
|
|
72
|
+
const w = ws[i * 4 + k];
|
|
73
|
+
if (w === 0)
|
|
74
|
+
continue;
|
|
75
|
+
const m = js[i * 4 + k] * 16;
|
|
76
|
+
px +=
|
|
77
|
+
w *
|
|
78
|
+
(skin[m] * x +
|
|
79
|
+
skin[m + 4] * y +
|
|
80
|
+
skin[m + 8] * z +
|
|
81
|
+
skin[m + 12]);
|
|
82
|
+
py +=
|
|
83
|
+
w *
|
|
84
|
+
(skin[m + 1] * x +
|
|
85
|
+
skin[m + 5] * y +
|
|
86
|
+
skin[m + 9] * z +
|
|
87
|
+
skin[m + 13]);
|
|
88
|
+
pz +=
|
|
89
|
+
w *
|
|
90
|
+
(skin[m + 2] * x +
|
|
91
|
+
skin[m + 6] * y +
|
|
92
|
+
skin[m + 10] * z +
|
|
93
|
+
skin[m + 14]);
|
|
94
|
+
qx +=
|
|
95
|
+
w *
|
|
96
|
+
(skin[m] * nx +
|
|
97
|
+
skin[m + 4] * ny +
|
|
98
|
+
skin[m + 8] * nz);
|
|
99
|
+
qy +=
|
|
100
|
+
w *
|
|
101
|
+
(skin[m + 1] * nx +
|
|
102
|
+
skin[m + 5] * ny +
|
|
103
|
+
skin[m + 9] * nz);
|
|
104
|
+
qz +=
|
|
105
|
+
w *
|
|
106
|
+
(skin[m + 2] * nx +
|
|
107
|
+
skin[m + 6] * ny +
|
|
108
|
+
skin[m + 10] * nz);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
position[i * 3] = px;
|
|
113
|
+
position[i * 3 + 1] = py;
|
|
114
|
+
position[i * 3 + 2] = pz;
|
|
115
|
+
const length = Math.sqrt(qx * qx + qy * qy + qz * qz);
|
|
116
|
+
const inverse = length > 1e-12 ? 1 / length : 0;
|
|
117
|
+
normal[i * 3] = qx * inverse;
|
|
118
|
+
normal[i * 3 + 1] = qy * inverse;
|
|
119
|
+
normal[i * 3 + 2] = qz * inverse;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Each collider's two ends in the world, six floats a collider: `model × global × frame` applied to
|
|
124
|
+
* the frame's origin and to `length` along its +Z. A sphere's two ends are one point.
|
|
125
|
+
*/
|
|
126
|
+
export function placeColliders(colliders, globals, model, scratch, ends) {
|
|
127
|
+
for (let k = 0; k < colliders.length; k++) {
|
|
128
|
+
const collider = colliders[k];
|
|
129
|
+
multiply4(model, 0, globals, collider.joint * 16, scratch, 0);
|
|
130
|
+
const frame = collider.frame;
|
|
131
|
+
if (frame !== undefined) {
|
|
132
|
+
multiply4(scratch, 0, frame, 0, scratch, 16);
|
|
133
|
+
scratch.copyWithin(0, 16, 32);
|
|
134
|
+
}
|
|
135
|
+
const length = collider.length ?? 0;
|
|
136
|
+
for (let c = 0; c < 3; c++) {
|
|
137
|
+
const origin = scratch[12 + c];
|
|
138
|
+
ends[k * 6 + c] = origin;
|
|
139
|
+
ends[k * 6 + 3 + c] = origin + scratch[8 + c] * length;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Carry free particles by the share of the character's motion they do not keep as their own.
|
|
145
|
+
*
|
|
146
|
+
* The character moved by `next × previous⁻¹`: a rotation about its old origin and a move of that
|
|
147
|
+
* origin. A particle is moved by `1 − angularInertia` of the first and `1 − linearInertia` of the
|
|
148
|
+
* second, its velocity turned by the same share — so inertia 1 leaves the cloth in the world and 0
|
|
149
|
+
* moves it as if it were simulated in the character's space. **A blend of the rotated point, not a
|
|
150
|
+
* fraction of the angle**, which is exact at the two ends and, for the few degrees a character turns
|
|
151
|
+
* in a step, indistinguishable between them; it needs no trigonometry, so it is the same bits on
|
|
152
|
+
* every engine. Both matrices rigid.
|
|
153
|
+
*/
|
|
154
|
+
export function carryParticles(position, velocity, inverseMass, previous, next, linearInertia, angularInertia) {
|
|
155
|
+
const turn = 1 - angularInertia;
|
|
156
|
+
const move = 1 - linearInertia;
|
|
157
|
+
if (turn === 0 && move === 0)
|
|
158
|
+
return;
|
|
159
|
+
/* R = R_next × R_previousᵀ, row r column c at r + c·3. */
|
|
160
|
+
const r = ROTATION;
|
|
161
|
+
for (let row = 0; row < 3; row++) {
|
|
162
|
+
for (let col = 0; col < 3; col++) {
|
|
163
|
+
let sum = 0;
|
|
164
|
+
for (let k = 0; k < 3; k++)
|
|
165
|
+
sum += next[k * 4 + row] * previous[k * 4 + col];
|
|
166
|
+
r[row + col * 3] = sum;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const ox = previous[12];
|
|
170
|
+
const oy = previous[13];
|
|
171
|
+
const oz = previous[14];
|
|
172
|
+
const dx = (next[12] - ox) * move;
|
|
173
|
+
const dy = (next[13] - oy) * move;
|
|
174
|
+
const dz = (next[14] - oz) * move;
|
|
175
|
+
for (let i = 0; i < inverseMass.length; i++) {
|
|
176
|
+
if (inverseMass[i] === 0)
|
|
177
|
+
continue;
|
|
178
|
+
const at = i * 3;
|
|
179
|
+
const x = position[at] - ox;
|
|
180
|
+
const y = position[at + 1] - oy;
|
|
181
|
+
const z = position[at + 2] - oz;
|
|
182
|
+
position[at] =
|
|
183
|
+
position[at] +
|
|
184
|
+
(r[0] * x + r[3] * y + r[6] * z - x) * turn +
|
|
185
|
+
dx;
|
|
186
|
+
position[at + 1] =
|
|
187
|
+
position[at + 1] +
|
|
188
|
+
(r[1] * x + r[4] * y + r[7] * z - y) * turn +
|
|
189
|
+
dy;
|
|
190
|
+
position[at + 2] =
|
|
191
|
+
position[at + 2] +
|
|
192
|
+
(r[2] * x + r[5] * y + r[8] * z - z) * turn +
|
|
193
|
+
dz;
|
|
194
|
+
const vx = velocity[at];
|
|
195
|
+
const vy = velocity[at + 1];
|
|
196
|
+
const vz = velocity[at + 2];
|
|
197
|
+
velocity[at] =
|
|
198
|
+
vx + (r[0] * vx + r[3] * vy + r[6] * vz - vx) * turn;
|
|
199
|
+
velocity[at + 1] =
|
|
200
|
+
vy + (r[1] * vx + r[4] * vy + r[7] * vz - vy) * turn;
|
|
201
|
+
velocity[at + 2] =
|
|
202
|
+
vz + (r[2] * vx + r[5] * vy + r[8] * vz - vz) * turn;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const ROTATION = new Float64Array(9);
|
|
206
|
+
/**
|
|
207
|
+
* How far the character jumped between two poses: its origin's distance and its rotation's angle.
|
|
208
|
+
* The angle through `trace`, `cos θ = (tr R − 1) / 2`, compared as a cosine, through `exactCos` so
|
|
209
|
+
* every engine draws the line in one place.
|
|
210
|
+
*/
|
|
211
|
+
export function jumped(previous, next, distance, angle) {
|
|
212
|
+
const dx = next[12] - previous[12];
|
|
213
|
+
const dy = next[13] - previous[13];
|
|
214
|
+
const dz = next[14] - previous[14];
|
|
215
|
+
if (dx * dx + dy * dy + dz * dz > distance * distance)
|
|
216
|
+
return true;
|
|
217
|
+
if (!(angle < Math.PI))
|
|
218
|
+
return false;
|
|
219
|
+
let trace = 0;
|
|
220
|
+
for (let d = 0; d < 3; d++) {
|
|
221
|
+
for (let k = 0; k < 3; k++)
|
|
222
|
+
trace += next[k * 4 + d] * previous[k * 4 + d];
|
|
223
|
+
}
|
|
224
|
+
return (trace - 1) / 2 < exactCos(angle);
|
|
225
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SkinnedClothSetup } from './skinnedClothSetup.ts';
|
|
2
|
+
/** Skinned positions and normals from the last two poses, and the model matrices between. */
|
|
3
|
+
export declare class ClothTargets {
|
|
4
|
+
private readonly setup;
|
|
5
|
+
private readonly particlesOnCpu;
|
|
6
|
+
/** Where skinning puts each particle at the step being taken. */
|
|
7
|
+
readonly position: Float32Array;
|
|
8
|
+
/** Its skinned normal at the latest pose. */
|
|
9
|
+
readonly normal: Float32Array;
|
|
10
|
+
/** The model matrix at the step being taken, and at the one before it. */
|
|
11
|
+
readonly modelNow: Float32Array<ArrayBuffer>;
|
|
12
|
+
readonly modelBefore: Float32Array<ArrayBuffer>;
|
|
13
|
+
/** Each collider's two ends, six floats a collider, at the latest pose. */
|
|
14
|
+
readonly colliderEnds: Float32Array;
|
|
15
|
+
/** Each collider's two radii. */
|
|
16
|
+
readonly colliderRadii: Float32Array;
|
|
17
|
+
/** Whether a pose has been given at all. */
|
|
18
|
+
posed: boolean;
|
|
19
|
+
/** `model × global × inverse bind` a joint at the latest pose, or null for a cloth with no rig. */
|
|
20
|
+
readonly skin: Float32Array | null;
|
|
21
|
+
private readonly from;
|
|
22
|
+
private readonly to;
|
|
23
|
+
private readonly modelFrom;
|
|
24
|
+
private readonly modelTo;
|
|
25
|
+
private readonly scratch;
|
|
26
|
+
private readonly colliders;
|
|
27
|
+
constructor(setup: SkinnedClothSetup, particlesOnCpu?: boolean);
|
|
28
|
+
/** The model matrix the latest pose was given at. */
|
|
29
|
+
get model(): Float32Array;
|
|
30
|
+
/** Take a new pose: the old one becomes where this frame's steps start from. */
|
|
31
|
+
pose(globals: Float32Array, model: Float32Array): void;
|
|
32
|
+
/** Forget the pose before the latest: steps start where the latest one is. */
|
|
33
|
+
settle(): void;
|
|
34
|
+
/**
|
|
35
|
+
* The steps of a frame have run: the next frame's start from this pose, not the one before it, or
|
|
36
|
+
* a frame given no new pose would interpolate back from the old one and jump the cloth.
|
|
37
|
+
*/
|
|
38
|
+
consume(): void;
|
|
39
|
+
/** The skinned positions and the model matrix `fraction` of the way from the last pose to this one. */
|
|
40
|
+
at(fraction: number): void;
|
|
41
|
+
/** The latest skinned positions, which `reset` puts every particle at. */
|
|
42
|
+
get latest(): Float32Array;
|
|
43
|
+
private skinAt;
|
|
44
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A skinned cloth's pose: where skinning puts every particle at the last two poses, where it puts
|
|
3
|
+
* them at a step between, and where the colliders are.
|
|
4
|
+
*
|
|
5
|
+
* **Interpolated across the steps of one frame.** A frame that runs two steps moves its kinematic
|
|
6
|
+
* particles half way at the first and the rest at the second, rather than all at once — a fixed
|
|
7
|
+
* step must not turn a 120 Hz animation into a 60 Hz one with a jump in it. The model matrix is
|
|
8
|
+
* blended the same way for the inertia carry; a blend of two rigid matrices is not quite rigid, by
|
|
9
|
+
* the few thousandths of a degree a character turns between two steps.
|
|
10
|
+
*
|
|
11
|
+
* Before any pose the rest positions stand in, under an identity model, so a cloth that is never
|
|
12
|
+
* posed is the plain solver it was in H1's tests.
|
|
13
|
+
*
|
|
14
|
+
* **The particles' skinned positions can be left to a device.** A GPU solver skins them itself, from
|
|
15
|
+
* `skin`, so `particlesOnCpu` false keeps only what is per joint or per collider — the model
|
|
16
|
+
* matrices, the skin matrices and the colliders' ends — and `position`, `normal` and `latest` then
|
|
17
|
+
* hold the rest pose and are not to be read.
|
|
18
|
+
*/
|
|
19
|
+
import { placeColliders, skinMatrices, skinParticles } from './clothPose.js';
|
|
20
|
+
const IDENTITY = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
21
|
+
/** Skinned positions and normals from the last two poses, and the model matrices between. */
|
|
22
|
+
export class ClothTargets {
|
|
23
|
+
setup;
|
|
24
|
+
particlesOnCpu;
|
|
25
|
+
/** Where skinning puts each particle at the step being taken. */
|
|
26
|
+
position;
|
|
27
|
+
/** Its skinned normal at the latest pose. */
|
|
28
|
+
normal;
|
|
29
|
+
/** The model matrix at the step being taken, and at the one before it. */
|
|
30
|
+
modelNow = new Float32Array(IDENTITY);
|
|
31
|
+
modelBefore = new Float32Array(IDENTITY);
|
|
32
|
+
/** Each collider's two ends, six floats a collider, at the latest pose. */
|
|
33
|
+
colliderEnds;
|
|
34
|
+
/** Each collider's two radii. */
|
|
35
|
+
colliderRadii;
|
|
36
|
+
/** Whether a pose has been given at all. */
|
|
37
|
+
posed = false;
|
|
38
|
+
/** `model × global × inverse bind` a joint at the latest pose, or null for a cloth with no rig. */
|
|
39
|
+
skin;
|
|
40
|
+
from;
|
|
41
|
+
to;
|
|
42
|
+
modelFrom = new Float32Array(IDENTITY);
|
|
43
|
+
modelTo = new Float32Array(IDENTITY);
|
|
44
|
+
scratch = new Float32Array(32);
|
|
45
|
+
colliders;
|
|
46
|
+
constructor(setup, particlesOnCpu = true) {
|
|
47
|
+
this.setup = setup;
|
|
48
|
+
this.particlesOnCpu = particlesOnCpu;
|
|
49
|
+
const count = setup.positions.length;
|
|
50
|
+
this.from = new Float32Array(setup.positions);
|
|
51
|
+
this.to = new Float32Array(setup.positions);
|
|
52
|
+
this.position = new Float32Array(setup.positions);
|
|
53
|
+
this.normal = new Float32Array(count);
|
|
54
|
+
const bones = (setup.inverseBind?.length ?? 0) / 16;
|
|
55
|
+
this.skin = setup.joints === undefined || bones === 0 ? null : new Float32Array(bones * 16);
|
|
56
|
+
this.colliders = setup.colliders ?? [];
|
|
57
|
+
this.colliderEnds = new Float32Array(this.colliders.length * 6);
|
|
58
|
+
this.colliderRadii = new Float32Array(this.colliders.length * 2);
|
|
59
|
+
this.colliders.forEach((collider, k) => {
|
|
60
|
+
this.colliderRadii[k * 2] = collider.radius;
|
|
61
|
+
this.colliderRadii[k * 2 + 1] = collider.radius2 ?? collider.radius;
|
|
62
|
+
});
|
|
63
|
+
this.skinAt(new Float32Array(Math.max(16, bones * 16)), this.modelTo);
|
|
64
|
+
}
|
|
65
|
+
/** The model matrix the latest pose was given at. */
|
|
66
|
+
get model() {
|
|
67
|
+
return this.modelTo;
|
|
68
|
+
}
|
|
69
|
+
/** Take a new pose: the old one becomes where this frame's steps start from. */
|
|
70
|
+
pose(globals, model) {
|
|
71
|
+
this.from.set(this.to);
|
|
72
|
+
this.modelFrom.set(this.modelTo);
|
|
73
|
+
this.modelTo.set(model);
|
|
74
|
+
this.skinAt(globals, model);
|
|
75
|
+
if (this.colliders.length > 0) {
|
|
76
|
+
placeColliders(this.colliders, globals, model, this.scratch, this.colliderEnds);
|
|
77
|
+
}
|
|
78
|
+
this.posed = true;
|
|
79
|
+
}
|
|
80
|
+
/** Forget the pose before the latest: steps start where the latest one is. */
|
|
81
|
+
settle() {
|
|
82
|
+
this.from.set(this.to);
|
|
83
|
+
this.position.set(this.to);
|
|
84
|
+
this.modelFrom.set(this.modelTo);
|
|
85
|
+
this.modelNow.set(this.modelTo);
|
|
86
|
+
this.modelBefore.set(this.modelTo);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The steps of a frame have run: the next frame's start from this pose, not the one before it, or
|
|
90
|
+
* a frame given no new pose would interpolate back from the old one and jump the cloth.
|
|
91
|
+
*/
|
|
92
|
+
consume() {
|
|
93
|
+
this.from.set(this.to);
|
|
94
|
+
this.modelFrom.set(this.modelTo);
|
|
95
|
+
}
|
|
96
|
+
/** The skinned positions and the model matrix `fraction` of the way from the last pose to this one. */
|
|
97
|
+
at(fraction) {
|
|
98
|
+
const { from, to, position } = this;
|
|
99
|
+
if (this.particlesOnCpu) {
|
|
100
|
+
for (let i = 0; i < to.length; i++) {
|
|
101
|
+
const a = from[i];
|
|
102
|
+
position[i] = a + (to[i] - a) * fraction;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
this.modelBefore.set(this.modelNow);
|
|
106
|
+
for (let i = 0; i < 16; i++) {
|
|
107
|
+
const a = this.modelFrom[i];
|
|
108
|
+
this.modelNow[i] = a + (this.modelTo[i] - a) * fraction;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** The latest skinned positions, which `reset` puts every particle at. */
|
|
112
|
+
get latest() {
|
|
113
|
+
return this.to;
|
|
114
|
+
}
|
|
115
|
+
skinAt(globals, model) {
|
|
116
|
+
const { setup, skin } = this;
|
|
117
|
+
if (skin !== null && setup.inverseBind !== undefined) {
|
|
118
|
+
skinMatrices(globals, setup.inverseBind, model, this.scratch, skin);
|
|
119
|
+
}
|
|
120
|
+
if (!this.particlesOnCpu)
|
|
121
|
+
return;
|
|
122
|
+
skinParticles(setup.positions, setup.normals ?? null, setup.joints ?? null, setup.weights ?? null, setup.joints2 ?? null, setup.weights2 ?? null, skin, model, this.to, this.normal);
|
|
123
|
+
}
|
|
124
|
+
}
|
package/dist/exact.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcendentals that give the same bits on every JavaScript engine.
|
|
3
|
+
*
|
|
4
|
+
* **Not more accurate than `Math`, and that is not what they are for.** These are within a couple
|
|
5
|
+
* of units in the last place of correctly rounded, which is about what a platform `sin` gives you.
|
|
6
|
+
* What they are is *reproducible*: ECMAScript declines to specify `sin`, `cos`, `exp`, `acos` and
|
|
7
|
+
* eighteen more, so two engines may differ by an ulp, and an ulp is enough to make two peers running
|
|
8
|
+
* the same simulation disagree — first invisibly, then completely.
|
|
9
|
+
*
|
|
10
|
+
* **In physics rather than core since 4.8.4**: a skinned cloth's damping needs `exp` and its bending
|
|
11
|
+
* `acos`, the simulation set may not call the platform's, and physics imports no other package. Core
|
|
12
|
+
* re-exports physics wholesale, so `exactExp` from `@driftengine/core` is the same function.
|
|
13
|
+
*
|
|
14
|
+
* `scripts/determinism.mjs` is the gate that refuses the unspecified ones inside the simulation set
|
|
15
|
+
* and names the twenty-two. Its header carries the other half of the argument: `+ - * /` are IEEE
|
|
16
|
+
* 754 operations by specification, JavaScript permits no fused-multiply-add contraction, and
|
|
17
|
+
* `Math.sqrt` is required correctly rounded and is *not* on that list. **So everything below uses
|
|
18
|
+
* only those five operations**, which is the whole design: reproducibility by construction rather
|
|
19
|
+
* than by hoping two engines chose the same algorithm.
|
|
20
|
+
*
|
|
21
|
+
* ---
|
|
22
|
+
*
|
|
23
|
+
* ## What is here, and what is deliberately not
|
|
24
|
+
*
|
|
25
|
+
* `exactSin`, `exactCos`, `exactExp`, `exactAcos` and `exactLog`. Five, because five is what the
|
|
26
|
+
* engine and its first consumer actually reach for: the terrain binding's slope angle needs `acos`,
|
|
27
|
+
* a lockstep fixture rotating anything needs `sin` and `cos`, exponential decay needs `exp`, and a
|
|
28
|
+
* detector's box prior is a logit of the patch grid, which needs `log`.
|
|
29
|
+
*
|
|
30
|
+
* **`atan2` is absent.** It is the obvious fifth and nothing here needs one: `shape.ts` sorts by a
|
|
31
|
+
* comparison key computed from two in-plane axes rather than by an angle, and `joints.ts` compares
|
|
32
|
+
* a quaternion component against a half-angle's sine. *Reversed by* a site that genuinely needs a
|
|
33
|
+
* signed angle from two components, at which point it arrives with its own golden vectors.
|
|
34
|
+
*
|
|
35
|
+
* ## Nothing in core imports these
|
|
36
|
+
*
|
|
37
|
+
* Deliberately. `core-only` is the one size figure in this repository that every consumer pays, and
|
|
38
|
+
* a module reachable from `createRenderer` is in it whether anybody calls it or not. These are
|
|
39
|
+
* reached by the script bindings and by consumers who ask for them, so tree-shaking keeps them out
|
|
40
|
+
* of a bundle that does not, which is measured rather than assumed.
|
|
41
|
+
*
|
|
42
|
+
* ## The coefficients
|
|
43
|
+
*
|
|
44
|
+
* The polynomial and rational coefficients are fdlibm's, which is the ancestor of most platform
|
|
45
|
+
* libms and is public domain. They are written as literals: **a literal is the same number on
|
|
46
|
+
* every engine, which is the only property being bought.** Their provenance matters less than their
|
|
47
|
+
* fixity — a coefficient somebody re-derived to one more digit is a different function, and
|
|
48
|
+
* `exact.test.ts`'s golden bit patterns are what would catch that.
|
|
49
|
+
*/
|
|
50
|
+
/** `Math.sin`, reproducibly. NaN in, NaN out; an infinity answers NaN, as `Math.sin` does. */
|
|
51
|
+
export declare function exactSin(x: number): number;
|
|
52
|
+
/** `Math.cos`, reproducibly. */
|
|
53
|
+
export declare function exactCos(x: number): number;
|
|
54
|
+
/**
|
|
55
|
+
* `Math.exp`, reproducibly.
|
|
56
|
+
*
|
|
57
|
+
* Reduce by a multiple of ln 2 into [-ln2/2, ln2/2], sum the Taylor series there, and scale by a
|
|
58
|
+
* power of two. **A degree-14 series and not a shorter minimax**, because on that interval the
|
|
59
|
+
* fifteenth term is about 4e-20 — below the precision of a double — so the series is exact to the
|
|
60
|
+
* representation and there is nothing a fitted polynomial would buy.
|
|
61
|
+
*/
|
|
62
|
+
export declare function exactExp(x: number): number;
|
|
63
|
+
/**
|
|
64
|
+
* `Math.log`, reproducibly: fdlibm's algorithm, which is also V8's.
|
|
65
|
+
*
|
|
66
|
+
* Take the power of two out as `k` and leave a significand in [√2/2, √2), so that `f = m - 1` is
|
|
67
|
+
* small either side of zero; then log(1 + f) = f - f²/2 + s·(f²/2 + R(s²)) with s = f/(2 + f), and
|
|
68
|
+
* `k·ln 2` added back in two parts so the large part loses nothing. The exponent is read and the
|
|
69
|
+
* significand rescaled through the double's own words — integer operations, not transcendentals.
|
|
70
|
+
*/
|
|
71
|
+
export declare function exactLog(x: number): number;
|
|
72
|
+
/**
|
|
73
|
+
* `Math.acos`, reproducibly. Outside [-1, 1] this answers NaN, as `Math.acos` does.
|
|
74
|
+
*
|
|
75
|
+
* Three branches rather than one, and the split at ±0.5 is the reason it is accurate near the ends:
|
|
76
|
+
* `acos` has infinite derivative at ±1, so a polynomial in `x` loses its footing there. Substituting
|
|
77
|
+
* `s = sqrt((1 ∓ x)/2)` moves the awkward point to zero, where a polynomial is comfortable — which
|
|
78
|
+
* is also why `sqrt` being exactly rounded matters here rather than being a convenience.
|
|
79
|
+
*/
|
|
80
|
+
export declare function exactAcos(x: number): number;
|