@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,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skinned cloth's kernels: one substep's prediction, each kind of constraint over one batch, and
|
|
3
|
+
* the velocities a substep leaves.
|
|
4
|
+
*
|
|
5
|
+
* **Plain functions over flat arrays, one per GPU dispatch.** The WebGPU solver runs the same steps
|
|
6
|
+
* as compute passes over the same arrays, and its WGSL is written to be read beside this file: a
|
|
7
|
+
* kernel here is a thread's body there, the batch loop is the dispatch. Keeping them that shape is
|
|
8
|
+
* what lets the device parity check compare like with like.
|
|
9
|
+
*
|
|
10
|
+
* **XPBD throughout**: a constraint's compliance becomes `α = compliance / h²`, and its multiplier
|
|
11
|
+
* accumulates over the substep's iterations, so stiffness is a property of the cloth rather than of
|
|
12
|
+
* how many times it was iterated — §7 of the physics design, as `cloth.ts` states it.
|
|
13
|
+
*
|
|
14
|
+
* Allocation-free: every kernel writes into arrays it is handed.
|
|
15
|
+
*/
|
|
16
|
+
import { exactAcos } from './exact.ts';
|
|
17
|
+
|
|
18
|
+
/** A particle's predicted position: damping and drag, then gravity, then a substep of travel. */
|
|
19
|
+
export function predictParticles(
|
|
20
|
+
position: Float32Array,
|
|
21
|
+
previous: Float32Array,
|
|
22
|
+
velocity: Float32Array,
|
|
23
|
+
inverseMass: Float32Array,
|
|
24
|
+
h: number,
|
|
25
|
+
keep: number,
|
|
26
|
+
dragKeep: number,
|
|
27
|
+
gravity: readonly [number, number, number],
|
|
28
|
+
wind: readonly [number, number, number],
|
|
29
|
+
): void {
|
|
30
|
+
const count = inverseMass.length;
|
|
31
|
+
for (let i = 0; i < count; i++) {
|
|
32
|
+
const at = i * 3;
|
|
33
|
+
previous[at] = position[at] as number;
|
|
34
|
+
previous[at + 1] = position[at + 1] as number;
|
|
35
|
+
previous[at + 2] = position[at + 2] as number;
|
|
36
|
+
if ((inverseMass[i] as number) === 0) continue;
|
|
37
|
+
for (let c = 0; c < 3; c++) {
|
|
38
|
+
const air = wind[c] as number;
|
|
39
|
+
const v =
|
|
40
|
+
air + ((velocity[at + c] as number) * keep - air) * dragKeep + (gravity[c] as number) * h;
|
|
41
|
+
velocity[at + c] = v;
|
|
42
|
+
position[at + c] = (position[at + c] as number) + v * h;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Distance constraints `order[from]` to `order[to]`, one batch. */
|
|
48
|
+
export function solveDistanceBatch(
|
|
49
|
+
position: Float32Array,
|
|
50
|
+
inverseMass: Float32Array,
|
|
51
|
+
pairs: Uint32Array,
|
|
52
|
+
rest: Float32Array,
|
|
53
|
+
compliance: Float32Array,
|
|
54
|
+
lambda: Float32Array,
|
|
55
|
+
order: Uint32Array,
|
|
56
|
+
from: number,
|
|
57
|
+
to: number,
|
|
58
|
+
invH2: number,
|
|
59
|
+
): void {
|
|
60
|
+
for (let n = from; n < to; n++) {
|
|
61
|
+
const k = order[n] as number;
|
|
62
|
+
const a = pairs[k * 2] as number;
|
|
63
|
+
const b = pairs[k * 2 + 1] as number;
|
|
64
|
+
const wa = inverseMass[a] as number;
|
|
65
|
+
const wb = inverseMass[b] as number;
|
|
66
|
+
const w = wa + wb;
|
|
67
|
+
if (w === 0) continue;
|
|
68
|
+
const dx = (position[b * 3] as number) - (position[a * 3] as number);
|
|
69
|
+
const dy = (position[b * 3 + 1] as number) - (position[a * 3 + 1] as number);
|
|
70
|
+
const dz = (position[b * 3 + 2] as number) - (position[a * 3 + 2] as number);
|
|
71
|
+
const length = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
72
|
+
if (length < 1e-9) continue;
|
|
73
|
+
const alpha = (compliance[k] as number) * invH2;
|
|
74
|
+
const old = lambda[k] as number;
|
|
75
|
+
const delta = (-(length - (rest[k] as number)) - alpha * old) / (w + alpha);
|
|
76
|
+
lambda[k] = old + delta;
|
|
77
|
+
const s = delta / length;
|
|
78
|
+
position[a * 3] = (position[a * 3] as number) - dx * s * wa;
|
|
79
|
+
position[a * 3 + 1] = (position[a * 3 + 1] as number) - dy * s * wa;
|
|
80
|
+
position[a * 3 + 2] = (position[a * 3 + 2] as number) - dz * s * wa;
|
|
81
|
+
position[b * 3] = (position[b * 3] as number) + dx * s * wb;
|
|
82
|
+
position[b * 3 + 1] = (position[b * 3 + 1] as number) + dy * s * wb;
|
|
83
|
+
position[b * 3 + 2] = (position[b * 3 + 2] as number) + dz * s * wb;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Scratch for one bending constraint: relative positions, normals and the four gradients. */
|
|
88
|
+
const E = new Float64Array(3);
|
|
89
|
+
const P3 = new Float64Array(3);
|
|
90
|
+
const P4 = new Float64Array(3);
|
|
91
|
+
const N1 = new Float64Array(3);
|
|
92
|
+
const N2 = new Float64Array(3);
|
|
93
|
+
const Q = new Float64Array(12);
|
|
94
|
+
const W = new Float64Array(4);
|
|
95
|
+
const IDS = new Uint32Array(4);
|
|
96
|
+
|
|
97
|
+
/** A vector's length, through `Math.sqrt`, which every engine rounds the same. */
|
|
98
|
+
function length3(v: Float64Array): number {
|
|
99
|
+
const x = v[0] as number;
|
|
100
|
+
const y = v[1] as number;
|
|
101
|
+
const z = v[2] as number;
|
|
102
|
+
return Math.sqrt(x * x + y * y + z * z);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function cross(a: Float64Array, b: Float64Array, out: Float64Array, at = 0): void {
|
|
106
|
+
out[at] = (a[1] as number) * (b[2] as number) - (a[2] as number) * (b[1] as number);
|
|
107
|
+
out[at + 1] = (a[2] as number) * (b[0] as number) - (a[0] as number) * (b[2] as number);
|
|
108
|
+
out[at + 2] = (a[0] as number) * (b[1] as number) - (a[1] as number) * (b[0] as number);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Dihedral bending constraints of one batch: Müller et al.'s position-based bending constraint, the
|
|
113
|
+
* angle between the two triangles' normals held at its rest value (π is flat).
|
|
114
|
+
*
|
|
115
|
+
* The constraint is `acos(n1 · n2) − rest`, whose gradient is the paper's `q` vectors over
|
|
116
|
+
* `sin θ`: **the paper's `q` are the negative of the derivative of `n1 · n2`**, measured against a
|
|
117
|
+
* finite difference at every component, which is why its update carries a minus sign where a reader
|
|
118
|
+
* taking `q` for the derivative would put a plus — and why a first version here folded a hinge
|
|
119
|
+
* further instead of unfolding it. **Divided rather than multiplied through**, because near flat the `q` vectors vanish
|
|
120
|
+
* with `sin θ` and their ratio stays finite: the paper's own form, `sin θ · q`, slows to nothing just
|
|
121
|
+
* where a garment spends its time. A configuration within a millionth of flat or of folded shut is
|
|
122
|
+
* left for the next iteration rather than divided by.
|
|
123
|
+
*/
|
|
124
|
+
export function solveBendingBatch(
|
|
125
|
+
position: Float32Array,
|
|
126
|
+
inverseMass: Float32Array,
|
|
127
|
+
quads: Uint32Array,
|
|
128
|
+
rest: Float32Array,
|
|
129
|
+
compliance: Float32Array,
|
|
130
|
+
lambda: Float32Array,
|
|
131
|
+
order: Uint32Array,
|
|
132
|
+
from: number,
|
|
133
|
+
to: number,
|
|
134
|
+
invH2: number,
|
|
135
|
+
): void {
|
|
136
|
+
for (let n = from; n < to; n++) {
|
|
137
|
+
const k = order[n] as number;
|
|
138
|
+
const i1 = quads[k * 4] as number;
|
|
139
|
+
const i2 = quads[k * 4 + 1] as number;
|
|
140
|
+
const i3 = quads[k * 4 + 2] as number;
|
|
141
|
+
const i4 = quads[k * 4 + 3] as number;
|
|
142
|
+
const w1 = inverseMass[i1] as number;
|
|
143
|
+
const w2 = inverseMass[i2] as number;
|
|
144
|
+
const w3 = inverseMass[i3] as number;
|
|
145
|
+
const w4 = inverseMass[i4] as number;
|
|
146
|
+
if (w1 + w2 + w3 + w4 === 0) continue;
|
|
147
|
+
for (let c = 0; c < 3; c++) {
|
|
148
|
+
const origin = position[i1 * 3 + c] as number;
|
|
149
|
+
E[c] = (position[i2 * 3 + c] as number) - origin;
|
|
150
|
+
P3[c] = (position[i3 * 3 + c] as number) - origin;
|
|
151
|
+
P4[c] = (position[i4 * 3 + c] as number) - origin;
|
|
152
|
+
}
|
|
153
|
+
cross(E, P3, N1);
|
|
154
|
+
cross(E, P4, N2);
|
|
155
|
+
const l1 = length3(N1);
|
|
156
|
+
const l2 = length3(N2);
|
|
157
|
+
if (l1 < 1e-12 || l2 < 1e-12) continue;
|
|
158
|
+
for (let c = 0; c < 3; c++) {
|
|
159
|
+
N1[c] = (N1[c] as number) / l1;
|
|
160
|
+
N2[c] = (N2[c] as number) / l2;
|
|
161
|
+
}
|
|
162
|
+
let d = (N1[0] as number) * (N2[0] as number) + (N1[1] as number) * (N2[1] as number);
|
|
163
|
+
d += (N1[2] as number) * (N2[2] as number);
|
|
164
|
+
d = Math.min(1, Math.max(-1, d));
|
|
165
|
+
const sine = Math.sqrt(1 - d * d);
|
|
166
|
+
if (sine < 1e-6) continue;
|
|
167
|
+
/* q3 = (e × n2 + (n1 × e) d) / |e × p3|, q4 = (e × n1 + (n2 × e) d) / |e × p4|. */
|
|
168
|
+
crossInto(E, N2, N1, E, d, l1, Q, 6);
|
|
169
|
+
crossInto(E, N1, N2, E, d, l2, Q, 9);
|
|
170
|
+
/* q2 = −(p3 × n2 + (n1 × p3) d) / |e × p3| − (p4 × n1 + (n2 × p4) d) / |e × p4|. */
|
|
171
|
+
crossInto(P3, N2, N1, P3, d, l1, Q, 3);
|
|
172
|
+
const a0 = Q[3] as number;
|
|
173
|
+
const a1 = Q[4] as number;
|
|
174
|
+
const a2 = Q[5] as number;
|
|
175
|
+
crossInto(P4, N1, N2, P4, d, l2, Q, 3);
|
|
176
|
+
Q[3] = -(a0 + (Q[3] as number));
|
|
177
|
+
Q[4] = -(a1 + (Q[4] as number));
|
|
178
|
+
Q[5] = -(a2 + (Q[5] as number));
|
|
179
|
+
for (let c = 0; c < 3; c++) {
|
|
180
|
+
Q[c] = -((Q[3 + c] as number) + (Q[6 + c] as number) + (Q[9 + c] as number));
|
|
181
|
+
}
|
|
182
|
+
/* The gradient of acos(d) is q / sin θ (q being −∂d/∂p); its weighted squared length. */
|
|
183
|
+
const weights = W;
|
|
184
|
+
weights[0] = w1;
|
|
185
|
+
weights[1] = w2;
|
|
186
|
+
weights[2] = w3;
|
|
187
|
+
weights[3] = w4;
|
|
188
|
+
let sum = 0;
|
|
189
|
+
for (let j = 0; j < 4; j++) {
|
|
190
|
+
const qx = Q[j * 3] as number;
|
|
191
|
+
const qy = Q[j * 3 + 1] as number;
|
|
192
|
+
const qz = Q[j * 3 + 2] as number;
|
|
193
|
+
sum += (weights[j] as number) * (qx * qx + qy * qy + qz * qz);
|
|
194
|
+
}
|
|
195
|
+
sum /= sine * sine;
|
|
196
|
+
if (sum < 1e-12) continue;
|
|
197
|
+
const alpha = (compliance[k] as number) * invH2;
|
|
198
|
+
const old = lambda[k] as number;
|
|
199
|
+
const delta = (-(exactAcos(d) - (rest[k] as number)) - alpha * old) / (sum + alpha);
|
|
200
|
+
lambda[k] = old + delta;
|
|
201
|
+
const scale = delta / sine;
|
|
202
|
+
const ids = IDS;
|
|
203
|
+
ids[0] = i1;
|
|
204
|
+
ids[1] = i2;
|
|
205
|
+
ids[2] = i3;
|
|
206
|
+
ids[3] = i4;
|
|
207
|
+
for (let j = 0; j < 4; j++) {
|
|
208
|
+
const w = weights[j] as number;
|
|
209
|
+
if (w === 0) continue;
|
|
210
|
+
const p = (ids[j] as number) * 3;
|
|
211
|
+
position[p] = (position[p] as number) + w * scale * (Q[j * 3] as number);
|
|
212
|
+
position[p + 1] = (position[p + 1] as number) + w * scale * (Q[j * 3 + 1] as number);
|
|
213
|
+
position[p + 2] = (position[p + 2] as number) + w * scale * (Q[j * 3 + 2] as number);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** `(a × b + (c × e) · d) / length` into `out` at `at`. */
|
|
219
|
+
function crossInto(
|
|
220
|
+
a: Float64Array,
|
|
221
|
+
b: Float64Array,
|
|
222
|
+
c: Float64Array,
|
|
223
|
+
e: Float64Array,
|
|
224
|
+
d: number,
|
|
225
|
+
length: number,
|
|
226
|
+
out: Float64Array,
|
|
227
|
+
at: number,
|
|
228
|
+
): void {
|
|
229
|
+
const x1 = (a[1] as number) * (b[2] as number) - (a[2] as number) * (b[1] as number);
|
|
230
|
+
const y1 = (a[2] as number) * (b[0] as number) - (a[0] as number) * (b[2] as number);
|
|
231
|
+
const z1 = (a[0] as number) * (b[1] as number) - (a[1] as number) * (b[0] as number);
|
|
232
|
+
const x2 = (c[1] as number) * (e[2] as number) - (c[2] as number) * (e[1] as number);
|
|
233
|
+
const y2 = (c[2] as number) * (e[0] as number) - (c[0] as number) * (e[2] as number);
|
|
234
|
+
const z2 = (c[0] as number) * (e[1] as number) - (c[1] as number) * (e[0] as number);
|
|
235
|
+
out[at] = (x1 + x2 * d) / length;
|
|
236
|
+
out[at + 1] = (y1 + y2 * d) / length;
|
|
237
|
+
out[at + 2] = (z1 + z2 * d) / length;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Tethers: a particle further than its length from its kinematic anchor is brought back to it. */
|
|
241
|
+
export function solveTethers(
|
|
242
|
+
position: Float32Array,
|
|
243
|
+
particles: Uint32Array,
|
|
244
|
+
anchors: Uint32Array,
|
|
245
|
+
lengths: Float32Array,
|
|
246
|
+
): void {
|
|
247
|
+
for (let k = 0; k < lengths.length; k++) {
|
|
248
|
+
const p = (particles[k] as number) * 3;
|
|
249
|
+
const a = (anchors[k] as number) * 3;
|
|
250
|
+
const dx = (position[p] as number) - (position[a] as number);
|
|
251
|
+
const dy = (position[p + 1] as number) - (position[a + 1] as number);
|
|
252
|
+
const dz = (position[p + 2] as number) - (position[a + 2] as number);
|
|
253
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
254
|
+
const length = lengths[k] as number;
|
|
255
|
+
if (distance <= length || distance < 1e-9) continue;
|
|
256
|
+
const s = length / distance;
|
|
257
|
+
position[p] = (position[a] as number) + dx * s;
|
|
258
|
+
position[p + 1] = (position[a + 1] as number) + dy * s;
|
|
259
|
+
position[p + 2] = (position[a + 2] as number) + dz * s;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Velocities from where the substep moved each free particle. */
|
|
264
|
+
export function finishParticles(
|
|
265
|
+
position: Float32Array,
|
|
266
|
+
previous: Float32Array,
|
|
267
|
+
velocity: Float32Array,
|
|
268
|
+
inverseMass: Float32Array,
|
|
269
|
+
invH: number,
|
|
270
|
+
): void {
|
|
271
|
+
for (let i = 0; i < inverseMass.length; i++) {
|
|
272
|
+
const at = i * 3;
|
|
273
|
+
if ((inverseMass[i] as number) === 0) {
|
|
274
|
+
velocity[at] = 0;
|
|
275
|
+
velocity[at + 1] = 0;
|
|
276
|
+
velocity[at + 2] = 0;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
velocity[at] = ((position[at] as number) - (previous[at] as number)) * invH;
|
|
280
|
+
velocity[at + 1] = ((position[at + 1] as number) - (previous[at + 1] as number)) * invH;
|
|
281
|
+
velocity[at + 2] = ((position[at + 2] as number) - (previous[at + 2] as number)) * invH;
|
|
282
|
+
}
|
|
283
|
+
}
|