@driftengine/physics 4.13.0 → 4.13.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/clothLimits.d.ts +6 -0
- package/dist/clothLimits.js +16 -0
- package/dist/skinnedCloth.d.ts +4 -1
- package/dist/skinnedCloth.js +11 -4
- package/dist/skinnedClothSetup.d.ts +24 -0
- package/dist/skinnedClothSetup.js +5 -0
- package/package.json +1 -1
- package/src/clothLimits.ts +21 -0
- package/src/skinnedCloth.ts +10 -4
- package/src/skinnedClothSetup.ts +28 -0
package/dist/clothLimits.d.ts
CHANGED
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Allocation-free, and each function a kernel the GPU solver mirrors.
|
|
11
11
|
*/
|
|
12
|
+
/**
|
|
13
|
+
* Every free particle drawn toward its skinned position, keeping `keep` of its offset: the share a
|
|
14
|
+
* substep keeps under the garment's `pull`, `1 / (1 + pull · h²)`. A kinematic particle is already
|
|
15
|
+
* there.
|
|
16
|
+
*/
|
|
17
|
+
export declare function applyPull(position: Float32Array, inverseMass: Float32Array, target: Float32Array, keep: number): void;
|
|
12
18
|
/** A particle further than `maxDistance[i] · scale` from its skinned position is put back on that sphere. */
|
|
13
19
|
export declare function applyMaxDistance(position: Float32Array, inverseMass: Float32Array, target: Float32Array, maxDistance: Float32Array, scale: number): void;
|
|
14
20
|
/**
|
package/dist/clothLimits.js
CHANGED
|
@@ -9,6 +9,22 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Allocation-free, and each function a kernel the GPU solver mirrors.
|
|
11
11
|
*/
|
|
12
|
+
/**
|
|
13
|
+
* Every free particle drawn toward its skinned position, keeping `keep` of its offset: the share a
|
|
14
|
+
* substep keeps under the garment's `pull`, `1 / (1 + pull · h²)`. A kinematic particle is already
|
|
15
|
+
* there.
|
|
16
|
+
*/
|
|
17
|
+
export function applyPull(position, inverseMass, target, keep) {
|
|
18
|
+
for (let i = 0; i < inverseMass.length; i++) {
|
|
19
|
+
if (inverseMass[i] === 0)
|
|
20
|
+
continue;
|
|
21
|
+
const at = i * 3;
|
|
22
|
+
for (let k = at; k < at + 3; k++) {
|
|
23
|
+
const t = target[k];
|
|
24
|
+
position[k] = t + (position[k] - t) * keep;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
12
28
|
/** A particle further than `maxDistance[i] · scale` from its skinned position is put back on that sphere. */
|
|
13
29
|
export function applyMaxDistance(position, inverseMass, target, maxDistance, scale) {
|
|
14
30
|
for (let i = 0; i < inverseMass.length; i++) {
|
package/dist/skinnedCloth.d.ts
CHANGED
|
@@ -54,7 +54,10 @@ export declare class SkinnedCloth {
|
|
|
54
54
|
* each predicted, constrained, limited and turned into velocities.
|
|
55
55
|
*/
|
|
56
56
|
private stepAt;
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* The pull, the limits and the colliders, after the constraints, so a garment pulling into the body
|
|
59
|
+
* loses. The pull first, so the hard limits have the last word.
|
|
60
|
+
*/
|
|
58
61
|
private limit;
|
|
59
62
|
/** After a reset, the share of the simulation drawn grows from nothing to all over the blend. */
|
|
60
63
|
private blend;
|
package/dist/skinnedCloth.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { batchesOf } from './clothBatches.js';
|
|
19
19
|
import { ClothControl } from './clothControl.js';
|
|
20
|
-
import { applyColliders, applyMaxDistance, applyStops } from './clothLimits.js';
|
|
20
|
+
import { applyColliders, applyMaxDistance, applyPull, applyStops } from './clothLimits.js';
|
|
21
21
|
import { carryParticles } from './clothPose.js';
|
|
22
22
|
import { exactExp } from './exact.js';
|
|
23
23
|
import { finishParticles, predictParticles, solveBendingBatch, solveDistanceBatch, solveTethers, } from './skinnedClothSolve.js';
|
|
@@ -149,6 +149,8 @@ export class SkinnedCloth {
|
|
|
149
149
|
const invH2 = invH * invH;
|
|
150
150
|
const keep = exactExp(damping * h);
|
|
151
151
|
const dragKeep = exactExp(drag * h);
|
|
152
|
+
/* A compliant constraint's step toward the skinned place: see `SkinnedClothParameters.pull`. */
|
|
153
|
+
const pullKeep = 1 / (1 + this.parameters.pull * h * h);
|
|
152
154
|
for (let s = 0; s < substeps; s++) {
|
|
153
155
|
predictParticles(this.positions, this.substepStart, this.velocities, this.inverseMass, h, keep, dragKeep, gravity, wind);
|
|
154
156
|
this.distance.lambda.fill(0);
|
|
@@ -162,16 +164,21 @@ export class SkinnedCloth {
|
|
|
162
164
|
solveTethers(this.positions, tethers.particles, tethers.anchors, tethers.lengths);
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
|
-
this.limit();
|
|
167
|
+
this.limit(pullKeep);
|
|
166
168
|
finishParticles(this.positions, this.substepStart, this.velocities, this.inverseMass, invH);
|
|
167
169
|
}
|
|
168
170
|
if (blend > 0)
|
|
169
171
|
this.blend(blend);
|
|
170
172
|
}
|
|
171
|
-
/**
|
|
172
|
-
|
|
173
|
+
/**
|
|
174
|
+
* The pull, the limits and the colliders, after the constraints, so a garment pulling into the body
|
|
175
|
+
* loses. The pull first, so the hard limits have the last word.
|
|
176
|
+
*/
|
|
177
|
+
limit(pullKeep) {
|
|
173
178
|
const { positions, inverseMass, targets, limits } = this;
|
|
174
179
|
const { maxDistanceScale, margin } = this.parameters;
|
|
180
|
+
if (pullKeep < 1)
|
|
181
|
+
applyPull(positions, inverseMass, targets.position, pullKeep);
|
|
175
182
|
if (limits.maxDistance !== undefined) {
|
|
176
183
|
applyMaxDistance(positions, inverseMass, targets.position, limits.maxDistance, maxDistanceScale);
|
|
177
184
|
}
|
|
@@ -97,6 +97,29 @@ export interface SkinnedClothParameters {
|
|
|
97
97
|
readonly damping?: number;
|
|
98
98
|
/** The same, of the velocity relative to the wind: how fast the air takes the cloth with it. */
|
|
99
99
|
readonly drag?: number;
|
|
100
|
+
/**
|
|
101
|
+
* How hard each particle is drawn back toward where skinning puts it, as a stiffness: metres a
|
|
102
|
+
* second squared toward its skinned position for every metre it is away from it. Zero, the
|
|
103
|
+
* default, is none. A particle at rest under gravity alone sits `9.81 / pull` metres below its
|
|
104
|
+
* skinned place, whatever the step.
|
|
105
|
+
*
|
|
106
|
+
* **For a garment made to hold a shape gravity would undo**: styled hair held by a few pinned
|
|
107
|
+
* particles at its roots keeps the shape it was made in at rest and swings out of it only as the
|
|
108
|
+
* body moves, then settles back. The hard limits cannot do that: a max distance is a sphere a
|
|
109
|
+
* particle may fall anywhere inside, so hair falls to its edge and hangs.
|
|
110
|
+
*
|
|
111
|
+
* **A stiffness, not a share of the offset kept a second as `damping` is.** Applied first among
|
|
112
|
+
* the limits, after the constraints, each substep keeping `1 / (1 + pull · h²)` of the offset: a
|
|
113
|
+
* compliant constraint's step, so the shape it holds against gravity is one length at every step.
|
|
114
|
+
* A share scaled by the step as `damping` is would hold a sag proportional to the step: hair
|
|
115
|
+
* authored to keep 0.7 of its offset a 1/60 step sags 6.36, 3.49 and 1.83 mm at 1/60, 1/120 and
|
|
116
|
+
* 1/240 that way, where this holds 6.36 mm at all three. A share `k` kept a step at a step `s` is `(1 / k − 1) / s²` here, and keeps
|
|
117
|
+
* exactly `k` at that step. **What it gives up**: one pull for the whole garment, so roots that
|
|
118
|
+
* should hold harder than tips hold them as hard; it is a pull rather than a limit, so a hard blow
|
|
119
|
+
* still takes a particle out of shape for a moment; and with no `damping` a stiff one rings, at a
|
|
120
|
+
* rate a coarse step damps more than a fine one does.
|
|
121
|
+
*/
|
|
122
|
+
readonly pull?: number;
|
|
100
123
|
/**
|
|
101
124
|
* Metres a second, world space: the air the cloth starts in. Default still air. A garment in a
|
|
102
125
|
* scene follows the scene's wind a frame at a time, through `setWind`.
|
|
@@ -153,6 +176,7 @@ export interface ResolvedClothParameters {
|
|
|
153
176
|
readonly gravity: readonly [number, number, number];
|
|
154
177
|
readonly damping: number;
|
|
155
178
|
readonly drag: number;
|
|
179
|
+
readonly pull: number;
|
|
156
180
|
readonly wind: readonly [number, number, number];
|
|
157
181
|
readonly linearInertia: number;
|
|
158
182
|
readonly angularInertia: number;
|
|
@@ -20,6 +20,7 @@ export function resolveClothParameters(parameters) {
|
|
|
20
20
|
gravity: parameters.gravity ?? [0, -9.81, 0],
|
|
21
21
|
damping: parameters.damping ?? 0,
|
|
22
22
|
drag: parameters.drag ?? 0,
|
|
23
|
+
pull: parameters.pull ?? 0,
|
|
23
24
|
wind: parameters.wind ?? [0, 0, 0],
|
|
24
25
|
linearInertia: Math.min(1, Math.max(0, parameters.linearInertia ?? 1)),
|
|
25
26
|
angularInertia: Math.min(1, Math.max(0, parameters.angularInertia ?? 1)),
|
|
@@ -76,6 +77,10 @@ export function validateClothSetup(setup) {
|
|
|
76
77
|
const step = parameters.step ?? 1 / 60;
|
|
77
78
|
if (!(step > 0))
|
|
78
79
|
fail(`a step of ${step} seconds`);
|
|
80
|
+
/* A stiffness below zero pushes a particle away from its skinned place rather than toward it. */
|
|
81
|
+
const pull = parameters.pull ?? 0;
|
|
82
|
+
if (!(pull >= 0 && pull < Infinity))
|
|
83
|
+
fail(`a pull of ${pull}: a stiffness, zero or above`);
|
|
79
84
|
const per = (name, values, width) => {
|
|
80
85
|
if (values !== undefined && values.length !== count * width) {
|
|
81
86
|
fail(`${name} has ${values.length} entries for ${count} particles of ${width}`);
|
package/package.json
CHANGED
package/src/clothLimits.ts
CHANGED
|
@@ -10,6 +10,27 @@
|
|
|
10
10
|
* Allocation-free, and each function a kernel the GPU solver mirrors.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Every free particle drawn toward its skinned position, keeping `keep` of its offset: the share a
|
|
15
|
+
* substep keeps under the garment's `pull`, `1 / (1 + pull · h²)`. A kinematic particle is already
|
|
16
|
+
* there.
|
|
17
|
+
*/
|
|
18
|
+
export function applyPull(
|
|
19
|
+
position: Float32Array,
|
|
20
|
+
inverseMass: Float32Array,
|
|
21
|
+
target: Float32Array,
|
|
22
|
+
keep: number,
|
|
23
|
+
): void {
|
|
24
|
+
for (let i = 0; i < inverseMass.length; i++) {
|
|
25
|
+
if ((inverseMass[i] as number) === 0) continue;
|
|
26
|
+
const at = i * 3;
|
|
27
|
+
for (let k = at; k < at + 3; k++) {
|
|
28
|
+
const t = target[k] as number;
|
|
29
|
+
position[k] = t + ((position[k] as number) - t) * keep;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
13
34
|
/** A particle further than `maxDistance[i] · scale` from its skinned position is put back on that sphere. */
|
|
14
35
|
export function applyMaxDistance(
|
|
15
36
|
position: Float32Array,
|
package/src/skinnedCloth.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { batchesOf } from './clothBatches.ts';
|
|
19
19
|
import { ClothControl } from './clothControl.ts';
|
|
20
20
|
import type { ClothDevice } from './clothControl.ts';
|
|
21
|
-
import { applyColliders, applyMaxDistance, applyStops } from './clothLimits.ts';
|
|
21
|
+
import { applyColliders, applyMaxDistance, applyPull, applyStops } from './clothLimits.ts';
|
|
22
22
|
import { carryParticles } from './clothPose.ts';
|
|
23
23
|
import type { ClothTargets } from './clothTargets.ts';
|
|
24
24
|
import { exactExp } from './exact.ts';
|
|
@@ -218,6 +218,8 @@ export class SkinnedCloth {
|
|
|
218
218
|
const invH2 = invH * invH;
|
|
219
219
|
const keep = exactExp(damping * h);
|
|
220
220
|
const dragKeep = exactExp(drag * h);
|
|
221
|
+
/* A compliant constraint's step toward the skinned place: see `SkinnedClothParameters.pull`. */
|
|
222
|
+
const pullKeep = 1 / (1 + this.parameters.pull * h * h);
|
|
221
223
|
for (let s = 0; s < substeps; s++) {
|
|
222
224
|
predictParticles(
|
|
223
225
|
this.positions,
|
|
@@ -240,16 +242,20 @@ export class SkinnedCloth {
|
|
|
240
242
|
solveTethers(this.positions, tethers.particles, tethers.anchors, tethers.lengths);
|
|
241
243
|
}
|
|
242
244
|
}
|
|
243
|
-
this.limit();
|
|
245
|
+
this.limit(pullKeep);
|
|
244
246
|
finishParticles(this.positions, this.substepStart, this.velocities, this.inverseMass, invH);
|
|
245
247
|
}
|
|
246
248
|
if (blend > 0) this.blend(blend);
|
|
247
249
|
}
|
|
248
250
|
|
|
249
|
-
/**
|
|
250
|
-
|
|
251
|
+
/**
|
|
252
|
+
* The pull, the limits and the colliders, after the constraints, so a garment pulling into the body
|
|
253
|
+
* loses. The pull first, so the hard limits have the last word.
|
|
254
|
+
*/
|
|
255
|
+
private limit(pullKeep: number): void {
|
|
251
256
|
const { positions, inverseMass, targets, limits } = this;
|
|
252
257
|
const { maxDistanceScale, margin } = this.parameters;
|
|
258
|
+
if (pullKeep < 1) applyPull(positions, inverseMass, targets.position, pullKeep);
|
|
253
259
|
if (limits.maxDistance !== undefined) {
|
|
254
260
|
applyMaxDistance(
|
|
255
261
|
positions,
|
package/src/skinnedClothSetup.ts
CHANGED
|
@@ -103,6 +103,29 @@ export interface SkinnedClothParameters {
|
|
|
103
103
|
readonly damping?: number;
|
|
104
104
|
/** The same, of the velocity relative to the wind: how fast the air takes the cloth with it. */
|
|
105
105
|
readonly drag?: number;
|
|
106
|
+
/**
|
|
107
|
+
* How hard each particle is drawn back toward where skinning puts it, as a stiffness: metres a
|
|
108
|
+
* second squared toward its skinned position for every metre it is away from it. Zero, the
|
|
109
|
+
* default, is none. A particle at rest under gravity alone sits `9.81 / pull` metres below its
|
|
110
|
+
* skinned place, whatever the step.
|
|
111
|
+
*
|
|
112
|
+
* **For a garment made to hold a shape gravity would undo**: styled hair held by a few pinned
|
|
113
|
+
* particles at its roots keeps the shape it was made in at rest and swings out of it only as the
|
|
114
|
+
* body moves, then settles back. The hard limits cannot do that: a max distance is a sphere a
|
|
115
|
+
* particle may fall anywhere inside, so hair falls to its edge and hangs.
|
|
116
|
+
*
|
|
117
|
+
* **A stiffness, not a share of the offset kept a second as `damping` is.** Applied first among
|
|
118
|
+
* the limits, after the constraints, each substep keeping `1 / (1 + pull · h²)` of the offset: a
|
|
119
|
+
* compliant constraint's step, so the shape it holds against gravity is one length at every step.
|
|
120
|
+
* A share scaled by the step as `damping` is would hold a sag proportional to the step: hair
|
|
121
|
+
* authored to keep 0.7 of its offset a 1/60 step sags 6.36, 3.49 and 1.83 mm at 1/60, 1/120 and
|
|
122
|
+
* 1/240 that way, where this holds 6.36 mm at all three. A share `k` kept a step at a step `s` is `(1 / k − 1) / s²` here, and keeps
|
|
123
|
+
* exactly `k` at that step. **What it gives up**: one pull for the whole garment, so roots that
|
|
124
|
+
* should hold harder than tips hold them as hard; it is a pull rather than a limit, so a hard blow
|
|
125
|
+
* still takes a particle out of shape for a moment; and with no `damping` a stiff one rings, at a
|
|
126
|
+
* rate a coarse step damps more than a fine one does.
|
|
127
|
+
*/
|
|
128
|
+
readonly pull?: number;
|
|
106
129
|
/**
|
|
107
130
|
* Metres a second, world space: the air the cloth starts in. Default still air. A garment in a
|
|
108
131
|
* scene follows the scene's wind a frame at a time, through `setWind`.
|
|
@@ -161,6 +184,7 @@ export interface ResolvedClothParameters {
|
|
|
161
184
|
readonly gravity: readonly [number, number, number];
|
|
162
185
|
readonly damping: number;
|
|
163
186
|
readonly drag: number;
|
|
187
|
+
readonly pull: number;
|
|
164
188
|
readonly wind: readonly [number, number, number];
|
|
165
189
|
readonly linearInertia: number;
|
|
166
190
|
readonly angularInertia: number;
|
|
@@ -183,6 +207,7 @@ export function resolveClothParameters(
|
|
|
183
207
|
gravity: parameters.gravity ?? [0, -9.81, 0],
|
|
184
208
|
damping: parameters.damping ?? 0,
|
|
185
209
|
drag: parameters.drag ?? 0,
|
|
210
|
+
pull: parameters.pull ?? 0,
|
|
186
211
|
wind: parameters.wind ?? [0, 0, 0],
|
|
187
212
|
linearInertia: Math.min(1, Math.max(0, parameters.linearInertia ?? 1)),
|
|
188
213
|
angularInertia: Math.min(1, Math.max(0, parameters.angularInertia ?? 1)),
|
|
@@ -241,6 +266,9 @@ export function validateClothSetup(setup: SkinnedClothSetup): void {
|
|
|
241
266
|
}
|
|
242
267
|
const step = parameters.step ?? 1 / 60;
|
|
243
268
|
if (!(step > 0)) fail(`a step of ${step} seconds`);
|
|
269
|
+
/* A stiffness below zero pushes a particle away from its skinned place rather than toward it. */
|
|
270
|
+
const pull = parameters.pull ?? 0;
|
|
271
|
+
if (!(pull >= 0 && pull < Infinity)) fail(`a pull of ${pull}: a stiffness, zero or above`);
|
|
244
272
|
const per = (name: string, values: ArrayLike<number> | undefined, width: number): void => {
|
|
245
273
|
if (values !== undefined && values.length !== count * width) {
|
|
246
274
|
fail(`${name} has ${values.length} entries for ${count} particles of ${width}`);
|