@woosh/meep-engine 3.25.0 → 3.26.0
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/package.json +1 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +59 -0
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.js +285 -0
- package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PosedShape3D.js +57 -0
- package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/TransformedShape3D.js +102 -0
- package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnionShape3D.js +64 -0
- package/src/engine/physics/cloth/MEASUREMENTS.md +582 -0
- package/src/engine/physics/cloth/PLAN.md +330 -49
- package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +84 -17
- package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -1
- package/src/engine/physics/cloth/collider/ClothColliderIndex.js +451 -42
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +65 -7
- package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothInstance.js +72 -8
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts +25 -4
- package/src/engine/physics/cloth/ecs/ClothSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothSystem.js +75 -8
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts +41 -4
- package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/ClothWorld.js +207 -63
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts +10 -0
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/WorkerClothSystem.js +17 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts +27 -3
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_dynamics_map.js +28 -3
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +75 -10
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +131 -12
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts +19 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_seed_rig.js +249 -201
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts +21 -0
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_seed_subtree.js +62 -1
- package/src/engine/physics/cloth/ecs/cloth_write_back.d.ts.map +1 -1
- package/src/engine/physics/cloth/ecs/cloth_write_back.js +18 -10
- package/src/engine/physics/cloth/playground/collide_main.js +1 -1
- package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts +29 -8
- package/src/engine/physics/cloth/solver/cloth_contact_find.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_contact_find.js +37 -9
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts +12 -2
- package/src/engine/physics/cloth/solver/cloth_solver_constants.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/cloth_solver_constants.js +12 -2
- package/src/engine/physics/cloth/solver/cloth_step_instance.js +1 -1
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts +9 -2
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.d.ts.map +1 -1
- package/src/engine/physics/cloth/solver/constraint/cloth_backstop_row.js +9 -2
- package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts +23 -9
- package/src/engine/physics/cloth/wind/AbstractClothWind.d.ts.map +1 -1
- package/src/engine/physics/cloth/wind/AbstractClothWind.js +23 -9
- package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts +7 -5
- package/src/engine/physics/cloth/wind/ClothAmbientWind.d.ts.map +1 -1
- package/src/engine/physics/cloth/wind/ClothAmbientWind.js +12 -9
- package/src/engine/physics/cloth/wind/ClothFluidWind.d.ts.map +1 -1
- package/src/engine/physics/cloth/wind/ClothFluidWind.js +5 -2
- package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts +7 -2
- package/src/engine/physics/cloth/wind/cloth_sample_air.d.ts.map +1 -1
- package/src/engine/physics/cloth/wind/cloth_sample_air.js +12 -5
|
@@ -3,6 +3,7 @@ import { quat3_multiply } from "../../../../core/geom/3d/quaternion/quat3_multip
|
|
|
3
3
|
import { quat3_nlerp } from "../../../../core/geom/3d/quaternion/quat3_nlerp.js";
|
|
4
4
|
import { v3_quaternion_apply } from "../../../../core/geom/vec3/v3_quaternion_apply.js";
|
|
5
5
|
import { v3_quaternion_apply_inverse } from "../../../../core/geom/vec3/v3_quaternion_apply_inverse.js";
|
|
6
|
+
import { EntityObserver } from "../../../ecs/EntityObserver.js";
|
|
6
7
|
import {
|
|
7
8
|
TransformAttachment
|
|
8
9
|
} from "../../../ecs/transform-attachment/TransformAttachment.js";
|
|
@@ -30,6 +31,7 @@ import { ClothRig } from "./ClothRig.js";
|
|
|
30
31
|
import {
|
|
31
32
|
CLOTH_ANCHOR_ROTATION_LIMIT,
|
|
32
33
|
CLOTH_ANCHOR_TRAVEL_REACH,
|
|
34
|
+
CLOTH_SLEEP_DISPLACEMENT_RATIO,
|
|
33
35
|
CLOTH_SLEEP_SPEED_RATIO,
|
|
34
36
|
CLOTH_SLEEP_STEPS,
|
|
35
37
|
CLOTH_STABILISE_STEPS,
|
|
@@ -41,14 +43,17 @@ import {
|
|
|
41
43
|
} from "./cloth_dynamics_map.js";
|
|
42
44
|
import {
|
|
43
45
|
cloth_colliders_moved,
|
|
44
|
-
cloth_gather_colliders
|
|
46
|
+
cloth_gather_colliders,
|
|
47
|
+
cloth_gather_reach,
|
|
48
|
+
cloth_remember_colliders
|
|
45
49
|
} from "./cloth_gather_colliders.js";
|
|
46
50
|
import {
|
|
47
51
|
cloth_dynamics_change_needs_reseed,
|
|
48
52
|
cloth_reparameterise
|
|
49
53
|
} from "./cloth_build_rows.js";
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
54
|
+
import { Cloth } from "./Cloth.js";
|
|
55
|
+
import { cloth_release_subtree, cloth_seed_subtree } from "./cloth_seed_subtree.js";
|
|
56
|
+
import { cloth_release_rig, cloth_seed_rig } from "./cloth_seed_rig.js";
|
|
52
57
|
import { cloth_write_back } from "./cloth_write_back.js";
|
|
53
58
|
import { cloth_write_back_rig } from "./cloth_write_back_rig.js";
|
|
54
59
|
|
|
@@ -143,8 +148,16 @@ export class ClothWorld {
|
|
|
143
148
|
gravity = new Float64Array([0, -9.81, 0]);
|
|
144
149
|
|
|
145
150
|
/**
|
|
146
|
-
* Live cloths,
|
|
147
|
-
*
|
|
151
|
+
* Live cloths, in the order they were linked.
|
|
152
|
+
*
|
|
153
|
+
* **Not sorted by hierarchy depth**, and it used to be. The sort existed so
|
|
154
|
+
* that a nested cloth advanced against an anchor the cloth above it had
|
|
155
|
+
* already written this step — which `WorkerClothSystem` cannot do, because
|
|
156
|
+
* the step it is writing back happened on another thread a tick ago. Both
|
|
157
|
+
* systems now advance every cloth, then step every cloth, then finish every
|
|
158
|
+
* cloth, so a nested cloth reads its parent one fixed step stale on both
|
|
159
|
+
* sides and the sort has nothing left to decide. `ClothSystem#fixedUpdate`
|
|
160
|
+
* carries the argument; `MEASUREMENTS.md` §56 carries the measurement.
|
|
148
161
|
* @type {ClothInstance[]}
|
|
149
162
|
*/
|
|
150
163
|
instances = [];
|
|
@@ -252,10 +265,28 @@ export class ClothWorld {
|
|
|
252
265
|
__by_entity = new Map();
|
|
253
266
|
|
|
254
267
|
/**
|
|
255
|
-
* @
|
|
268
|
+
* Watches `Cloth` + {@link ClothRig} so the rig path is a **live**
|
|
269
|
+
* discriminator rather than one read once.
|
|
270
|
+
*
|
|
271
|
+
* `ClothSystem#dependencies` is `[Cloth, Transform64]` — the tuple that
|
|
272
|
+
* makes something a cloth — so a `ClothRig` added to a live cloth completes
|
|
273
|
+
* no tuple the system observes and a `ClothRig` removed from one breaks
|
|
274
|
+
* none. §3.1's "the discriminator is a component's presence" then holds only
|
|
275
|
+
* for a character spawned whole, which is the one case a spec constructs and
|
|
276
|
+
* the least likely one in a scene where a model loads asynchronously.
|
|
277
|
+
*
|
|
278
|
+
* Both edges re-seed. The removing edge also gives the joints back first,
|
|
279
|
+
* because the instance that knows which they were is about to stop being a
|
|
280
|
+
* rig.
|
|
281
|
+
* @type {EntityObserver}
|
|
256
282
|
* @private
|
|
257
283
|
*/
|
|
258
|
-
|
|
284
|
+
__rig_observer = new EntityObserver(
|
|
285
|
+
[Cloth, ClothRig],
|
|
286
|
+
(cloth, rig, entity) => this.reseed(entity),
|
|
287
|
+
(cloth, rig, entity) => this.#rig_removed(entity),
|
|
288
|
+
this
|
|
289
|
+
);
|
|
259
290
|
|
|
260
291
|
/**
|
|
261
292
|
* Resolve the systems a cloth world reads. Called from the owning system's
|
|
@@ -299,11 +330,30 @@ export class ClothWorld {
|
|
|
299
330
|
this.instances.push(instance);
|
|
300
331
|
this.__by_entity.set(entity, instance);
|
|
301
332
|
|
|
302
|
-
this.__order_dirty = true;
|
|
303
|
-
|
|
304
333
|
return instance;
|
|
305
334
|
}
|
|
306
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Watch this dataset for a {@link ClothRig} arriving at or leaving a live
|
|
338
|
+
* cloth. Called from the owning system's `handleDatasetAttached`; see
|
|
339
|
+
* {@link ClothWorld#__rig_observer} for why the system's own dependency
|
|
340
|
+
* tuple cannot answer this.
|
|
341
|
+
*
|
|
342
|
+
* @param {EntityComponentDataset} dataset
|
|
343
|
+
* @returns {void}
|
|
344
|
+
*/
|
|
345
|
+
attach_dataset(dataset) {
|
|
346
|
+
dataset.addObserver(this.__rig_observer, true);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* @param {EntityComponentDataset} dataset
|
|
351
|
+
* @returns {void}
|
|
352
|
+
*/
|
|
353
|
+
detach_dataset(dataset) {
|
|
354
|
+
dataset.removeObserver(this.__rig_observer, false);
|
|
355
|
+
}
|
|
356
|
+
|
|
307
357
|
/**
|
|
308
358
|
* @param {number} entity
|
|
309
359
|
* @returns {ClothInstance|undefined} the instance that was removed
|
|
@@ -315,6 +365,14 @@ export class ClothWorld {
|
|
|
315
365
|
return undefined;
|
|
316
366
|
}
|
|
317
367
|
|
|
368
|
+
// Give back what the seeding took. Seeding is not a read: it revokes
|
|
369
|
+
// the GPU's authority over a rig's joints, and it leaves the entity
|
|
370
|
+
// path's authored local transforms nowhere but inside the cloth,
|
|
371
|
+
// because `cloth_write_back` overwrites them every step. A cloth
|
|
372
|
+
// removed without this leaves joints nothing is writing and a subtree
|
|
373
|
+
// frozen in the shape the solver stopped in.
|
|
374
|
+
this.#release(instance);
|
|
375
|
+
|
|
318
376
|
this.__by_entity.delete(entity);
|
|
319
377
|
|
|
320
378
|
const index = this.instances.indexOf(instance);
|
|
@@ -326,6 +384,51 @@ export class ClothWorld {
|
|
|
326
384
|
return instance;
|
|
327
385
|
}
|
|
328
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Hand back whatever this instance's seeding took from the scene.
|
|
389
|
+
*
|
|
390
|
+
* @param {ClothInstance} instance
|
|
391
|
+
* @private
|
|
392
|
+
*/
|
|
393
|
+
#release(instance) {
|
|
394
|
+
if (!instance.seeded) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const ecd = this.entityManager === null ? null : this.entityManager.dataset;
|
|
399
|
+
|
|
400
|
+
if (ecd === null || ecd === undefined) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (instance.rig === null) {
|
|
405
|
+
cloth_release_subtree(instance, ecd);
|
|
406
|
+
} else {
|
|
407
|
+
cloth_release_rig(instance, ecd);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* A {@link ClothRig} has left a live cloth: give its joints back, then
|
|
413
|
+
* re-seed, which without a rig is the entity path.
|
|
414
|
+
*
|
|
415
|
+
* @param {number} entity
|
|
416
|
+
* @private
|
|
417
|
+
*/
|
|
418
|
+
#rig_removed(entity) {
|
|
419
|
+
const instance = this.__by_entity.get(entity);
|
|
420
|
+
|
|
421
|
+
if (instance === undefined) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.#release(instance);
|
|
426
|
+
|
|
427
|
+
instance.rig = null;
|
|
428
|
+
|
|
429
|
+
this.reseed(entity);
|
|
430
|
+
}
|
|
431
|
+
|
|
329
432
|
/**
|
|
330
433
|
* Throw away a cloth's binding so the next step walks its subtree again.
|
|
331
434
|
*
|
|
@@ -348,8 +451,6 @@ export class ClothWorld {
|
|
|
348
451
|
instance.seeded = false;
|
|
349
452
|
instance.anchor_valid = false;
|
|
350
453
|
|
|
351
|
-
this.__order_dirty = true;
|
|
352
|
-
|
|
353
454
|
return instance;
|
|
354
455
|
}
|
|
355
456
|
|
|
@@ -385,10 +486,6 @@ export class ClothWorld {
|
|
|
385
486
|
* @param {number} dt seconds
|
|
386
487
|
*/
|
|
387
488
|
begin(ecd, dt) {
|
|
388
|
-
if (this.__order_dirty) {
|
|
389
|
-
this.#reorder(ecd);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
489
|
// Owed to the clock, and spent by the first `advance` — see {@link time}
|
|
393
490
|
// for why the two are not the same moment. Assigned rather than set, so a
|
|
394
491
|
// world whose source is taken away does not carry a debt.
|
|
@@ -457,10 +554,8 @@ export class ClothWorld {
|
|
|
457
554
|
instance.contact_count = result[ClothStepResult.CONTACT_COUNT];
|
|
458
555
|
instance.self_pairs = result[ClothStepResult.SELF_PAIRS];
|
|
459
556
|
|
|
460
|
-
const speed = result[ClothStepResult.SPEED];
|
|
461
|
-
|
|
462
557
|
if ((instance.cloth.flags & ClothFlags.NeverSleep) === 0) {
|
|
463
|
-
if (
|
|
558
|
+
if (cloth_inside_sleep_window(instance)) {
|
|
464
559
|
instance.still_steps++;
|
|
465
560
|
|
|
466
561
|
if (instance.still_steps >= CLOTH_SLEEP_STEPS) {
|
|
@@ -502,7 +597,7 @@ export class ClothWorld {
|
|
|
502
597
|
#seed(instance, ecd, dt) {
|
|
503
598
|
const rig = ecd.getComponent(instance.entity, ClothRig);
|
|
504
599
|
|
|
505
|
-
if (rig === undefined || rig === null
|
|
600
|
+
if (rig === undefined || rig === null) {
|
|
506
601
|
instance.rig = null;
|
|
507
602
|
|
|
508
603
|
cloth_seed_subtree(instance, ecd, this.attachments, dt);
|
|
@@ -510,6 +605,17 @@ export class ClothWorld {
|
|
|
510
605
|
return;
|
|
511
606
|
}
|
|
512
607
|
|
|
608
|
+
if (rig.proxy === null) {
|
|
609
|
+
// Present and not ready. The same branch as a model that has not
|
|
610
|
+
// loaded, and for the same reason: a proxy filled in a few ticks
|
|
611
|
+
// after the component is a rig that is arriving, not a rig that is
|
|
612
|
+
// absent. Treating it as absent walks the character's own transform
|
|
613
|
+
// subtree — which under a spawned model is the whole skeleton — and
|
|
614
|
+
// seeds a skeleton as one rope, throwing in a dev build through
|
|
615
|
+
// `gpu_authority_assert_cpu` and simulating it in a shipped one.
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
513
619
|
if (this.models === null) {
|
|
514
620
|
this.#refuse(instance, `entity ${instance.entity} has a ClothRig and nothing hands out model instances`);
|
|
515
621
|
|
|
@@ -555,44 +661,6 @@ export class ClothWorld {
|
|
|
555
661
|
console.warn(`ClothSystem: ${message}`);
|
|
556
662
|
}
|
|
557
663
|
|
|
558
|
-
/**
|
|
559
|
-
* Depth-first order by hierarchy depth, so a nested cloth reads an anchor
|
|
560
|
-
* the cloth above it has already written.
|
|
561
|
-
*
|
|
562
|
-
* @param {EntityComponentDataset} ecd
|
|
563
|
-
* @private
|
|
564
|
-
*/
|
|
565
|
-
#reorder(ecd) {
|
|
566
|
-
const instances = this.instances;
|
|
567
|
-
|
|
568
|
-
for (let i = 0; i < instances.length; i++) {
|
|
569
|
-
const instance = instances[i];
|
|
570
|
-
|
|
571
|
-
let depth = 0;
|
|
572
|
-
let entity = instance.entity;
|
|
573
|
-
|
|
574
|
-
// Bounded by the number of entities; a cycle in the attachment
|
|
575
|
-
// graph is not this system's to diagnose, but it is this loop's to
|
|
576
|
-
// survive.
|
|
577
|
-
for (let guard = 0; guard < 4096; guard++) {
|
|
578
|
-
const attachment = ecd.getComponent(entity, TransformAttachment);
|
|
579
|
-
|
|
580
|
-
if (attachment === undefined) {
|
|
581
|
-
break;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
entity = attachment.parent;
|
|
585
|
-
depth++;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
instance.depth = depth;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
instances.sort((a, b) => a.depth - b.depth);
|
|
592
|
-
|
|
593
|
-
this.__order_dirty = false;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
664
|
/**
|
|
597
665
|
* Carry one seeded cloth to the edge of its step and write the parameters
|
|
598
666
|
* the step will run on: whatever the anchor did to the local frame, where
|
|
@@ -606,6 +674,11 @@ export class ClothWorld {
|
|
|
606
674
|
* that is asleep, and then nothing has been written to `params`
|
|
607
675
|
*/
|
|
608
676
|
advance(instance, dt, params, offset) {
|
|
677
|
+
// This cloth's own clock, moved before anything reads it — including
|
|
678
|
+
// the wake tests below, which are asking about the step that would
|
|
679
|
+
// follow. See {@link ClothInstance#age}.
|
|
680
|
+
instance.age += dt;
|
|
681
|
+
|
|
609
682
|
if (this.__wind_pending !== 0) {
|
|
610
683
|
this.time += this.__wind_pending;
|
|
611
684
|
|
|
@@ -686,7 +759,8 @@ export class ClothWorld {
|
|
|
686
759
|
if (cloth_air_changed(
|
|
687
760
|
instance.state, this.wind,
|
|
688
761
|
instance.anchor_translation, instance.anchor_rotation,
|
|
689
|
-
instance.air_seen, CLOTH_SLEEP_SPEED_RATIO * instance.reach
|
|
762
|
+
instance.air_seen, CLOTH_SLEEP_SPEED_RATIO * instance.reach,
|
|
763
|
+
instance.age
|
|
690
764
|
)) {
|
|
691
765
|
instance.asleep = false;
|
|
692
766
|
instance.still_steps = 0;
|
|
@@ -749,12 +823,23 @@ export class ClothWorld {
|
|
|
749
823
|
state.contact_radius = dynamics.thickness;
|
|
750
824
|
|
|
751
825
|
if (this.colliders !== null) {
|
|
752
|
-
// The gather's margin is
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
|
|
826
|
+
// The gather's margin is what the narrowphase will reach for and
|
|
827
|
+
// nothing beyond it — `cloth_gather_reach` carries the derivation,
|
|
828
|
+
// and why the velocity clamp that used to stand here was both far
|
|
829
|
+
// too wide and missing two terms.
|
|
830
|
+
const reach = cloth_gather_reach(state, this.colliders.index, dt);
|
|
831
|
+
|
|
832
|
+
cloth_gather_colliders(instance, this.colliders.index, reach);
|
|
833
|
+
|
|
834
|
+
// What this cloth is about to step against, for the next wake test
|
|
835
|
+
// to compare with. Here rather than inside the gather because the
|
|
836
|
+
// wake test gathers too, and what it asks about is the set the last
|
|
837
|
+
// *step* saw.
|
|
838
|
+
cloth_remember_colliders(instance, reach);
|
|
756
839
|
} else {
|
|
757
840
|
state.collider_count = 0;
|
|
841
|
+
|
|
842
|
+
instance.collider_key_count = 0;
|
|
758
843
|
}
|
|
759
844
|
|
|
760
845
|
// §10. The one piece of a cloth's step that has to read the world and
|
|
@@ -770,7 +855,7 @@ export class ClothWorld {
|
|
|
770
855
|
instance.air_varies = cloth_sample_air(
|
|
771
856
|
state, this.wind,
|
|
772
857
|
instance.anchor_translation, instance.anchor_rotation,
|
|
773
|
-
instance.air_seen
|
|
858
|
+
instance.air_seen, instance.age
|
|
774
859
|
);
|
|
775
860
|
} else {
|
|
776
861
|
instance.air_varies = false;
|
|
@@ -1059,6 +1144,65 @@ function move_local_frame(
|
|
|
1059
1144
|
*/
|
|
1060
1145
|
const CARRIED = new Float64Array(3);
|
|
1061
1146
|
|
|
1147
|
+
/**
|
|
1148
|
+
* Whether every particle is still within the sleep window's own anchor — §7
|
|
1149
|
+
* P5's "maximum particle displacement over a window", and the reason the test
|
|
1150
|
+
* is not the instantaneous speed it used to be.
|
|
1151
|
+
*
|
|
1152
|
+
* A cloth **resting on a collider** does not stop: its contact rows carry a
|
|
1153
|
+
* penalty that `γ = 1` never decays, and what it settles into is a bounded
|
|
1154
|
+
* limit cycle. Measured on an 8-link rope on a floor, that is a tenth of a
|
|
1155
|
+
* millimetre of travel a step — nothing anyone can see — at a peak speed of
|
|
1156
|
+
* 1.5e-2 m/s, eighteen times the threshold a speed test allowed. So the speed
|
|
1157
|
+
* test held four of the seven library fabrics awake indefinitely on a floor
|
|
1158
|
+
* while the same fabrics slept in 283 steps hanging free. Distance covered
|
|
1159
|
+
* since the window opened tells those two apart; a speed sampled at one instant
|
|
1160
|
+
* cannot. `MEASUREMENTS.md` §53.
|
|
1161
|
+
*
|
|
1162
|
+
* The anchor is re-taken whenever a particle leaves the threshold, so the
|
|
1163
|
+
* window measures "how far has anything moved since the cloth last moved"
|
|
1164
|
+
* rather than chopping time into fixed epochs a slow drift could hide inside.
|
|
1165
|
+
*
|
|
1166
|
+
* @param {ClothInstance} instance
|
|
1167
|
+
* @returns {boolean}
|
|
1168
|
+
* @private
|
|
1169
|
+
*/
|
|
1170
|
+
function cloth_inside_sleep_window(instance) {
|
|
1171
|
+
const position = instance.state.position;
|
|
1172
|
+
|
|
1173
|
+
let reference = instance.sleep_reference;
|
|
1174
|
+
|
|
1175
|
+
if (reference === null || reference.length !== position.length) {
|
|
1176
|
+
// First step, or a re-seed that changed the particle count. There is no
|
|
1177
|
+
// window yet, so this step opens one.
|
|
1178
|
+
instance.sleep_reference = Float32Array.from(position);
|
|
1179
|
+
|
|
1180
|
+
return false;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const threshold = CLOTH_SLEEP_DISPLACEMENT_RATIO * instance.reach;
|
|
1184
|
+
|
|
1185
|
+
const threshold_squared = threshold * threshold;
|
|
1186
|
+
|
|
1187
|
+
const particle_count = instance.state.particle_count;
|
|
1188
|
+
|
|
1189
|
+
for (let i = 0; i < particle_count; i++) {
|
|
1190
|
+
const p = i * 3;
|
|
1191
|
+
|
|
1192
|
+
const dx = position[p] - reference[p];
|
|
1193
|
+
const dy = position[p + 1] - reference[p + 1];
|
|
1194
|
+
const dz = position[p + 2] - reference[p + 2];
|
|
1195
|
+
|
|
1196
|
+
if (dx * dx + dy * dy + dz * dz > threshold_squared) {
|
|
1197
|
+
reference.set(position);
|
|
1198
|
+
|
|
1199
|
+
return false;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1062
1206
|
/**
|
|
1063
1207
|
* Point every leash row at where it measures from: a slack row at the
|
|
1064
1208
|
* particle's animated position, a tether at the anchor.
|
|
@@ -133,6 +133,16 @@ export class WorkerClothSystem extends WorkerSystem {
|
|
|
133
133
|
get models(): {
|
|
134
134
|
instance_of: (arg0: number) => (PrefabInstance | null);
|
|
135
135
|
};
|
|
136
|
+
/**
|
|
137
|
+
* @param {EntityComponentDataset} dataset
|
|
138
|
+
* @returns {void}
|
|
139
|
+
*/
|
|
140
|
+
handleDatasetAttached(dataset: EntityComponentDataset): void;
|
|
141
|
+
/**
|
|
142
|
+
* @param {EntityComponentDataset} dataset
|
|
143
|
+
* @returns {void}
|
|
144
|
+
*/
|
|
145
|
+
handleDatasetDetached(dataset: EntityComponentDataset): void;
|
|
136
146
|
/**
|
|
137
147
|
* @param {Cloth} cloth
|
|
138
148
|
* @param {Transform64} transform
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkerClothSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/ecs/WorkerClothSystem.js"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AACH;IAoEI;;;;;;;;OAQG;IACH;QAPiC,cAAc,EAApC,MAAM,MAAM;QAIK,eAAe,GAAhC,MAAM;OAYhB;IApFD,oDAAoC;IAEpC,iUAOE;IAEF;;;;;OAKG;IACH,OAFU,UAAU,CAEK;IA6EzB;;OAEG;IACH,iCAEC;IAbD;;;OAGG;IACH,4BAEC;IAoBD;;OAEG;IACH,qBAEC;IAhBD;;;;;;OAMG;IACH,gBAEC;IASD;;OAEG;IACH,iCAEC;IASD;;OAEG;IACH;4BAToC,MAAM,KAAG,CAAC,iBAAe,IAAI,CAAC;OAWjE;IAZD;;OAEG;IACH;4BAFoC,MAAM,KAAG,CAAC,iBAAe,IAAI,CAAC;MAIjE;
|
|
1
|
+
{"version":3,"file":"WorkerClothSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/ecs/WorkerClothSystem.js"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AACH;IAoEI;;;;;;;;OAQG;IACH;QAPiC,cAAc,EAApC,MAAM,MAAM;QAIK,eAAe,GAAhC,MAAM;OAYhB;IApFD,oDAAoC;IAEpC,iUAOE;IAEF;;;;;OAKG;IACH,OAFU,UAAU,CAEK;IA6EzB;;OAEG;IACH,iCAEC;IAbD;;;OAGG;IACH,4BAEC;IAoBD;;OAEG;IACH,qBAEC;IAhBD;;;;;;OAMG;IACH,gBAEC;IASD;;OAEG;IACH,iCAEC;IASD;;OAEG;IACH;4BAToC,MAAM,KAAG,CAAC,iBAAe,IAAI,CAAC;OAWjE;IAZD;;OAEG;IACH;4BAFoC,MAAM,KAAG,CAAC,iBAAe,IAAI,CAAC;MAIjE;IAiCD;;;OAGG;IACH,wDAFa,IAAI,CAIhB;IAED;;;OAGG;IACH,wDAFa,IAAI,CAIhB;IAcD;;;;OAIG;IACH,YAJW,KAAK,aACL,WAAW,UACX,MAAM,QAIhB;IAED;;;;OAIG;IACH,cAJW,KAAK,aACL,WAAW,UACX,MAAM,QAQhB;IAED;;;;OAIG;IACH,eAHW,MAAM,GACJ,IAAI,CAIhB;IAED;;;;OAIG;IACH,aAHW,MAAM,GACJ,IAAI,CAIhB;IAED;;;OAGG;IACH,mBAHW,MAAM,GACJ,gBAAc,SAAS,CAInC;IAED;;;;OAIG;IACH,iCAEC;;CAmOJ;6BA/jB4B,oCAAoC;4BAIrC,uCAAuC;sBAE7C,YAAY;4CAPU,uDAAuD;oCAI5F,0DAA0D;8BAInC,oBAAoB;6BACrB,mBAAmB;yBACvB,eAAe;2BACb,iBAAiB"}
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
* which is allocated once at startup and cannot grow.
|
|
23
23
|
* @type {number}
|
|
24
24
|
*/
|
|
25
|
-
const DEFAULT_MAX_CLOTH_COUNT =
|
|
25
|
+
const DEFAULT_MAX_CLOTH_COUNT = 64;
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Drop-in alternative to {@link ClothSystem} that runs the solve on a worker
|
|
@@ -265,6 +265,22 @@ export class WorkerClothSystem extends WorkerSystem {
|
|
|
265
265
|
await super.startup(entityManager);
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
/**
|
|
269
|
+
* @param {EntityComponentDataset} dataset
|
|
270
|
+
* @returns {void}
|
|
271
|
+
*/
|
|
272
|
+
handleDatasetAttached(dataset) {
|
|
273
|
+
this.world.attach_dataset(dataset);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {EntityComponentDataset} dataset
|
|
278
|
+
* @returns {void}
|
|
279
|
+
*/
|
|
280
|
+
handleDatasetDetached(dataset) {
|
|
281
|
+
this.world.detach_dataset(dataset);
|
|
282
|
+
}
|
|
283
|
+
|
|
268
284
|
/**
|
|
269
285
|
* @param {EntityManager} entityManager
|
|
270
286
|
* @returns {Promise<void>}
|
|
@@ -277,12 +277,36 @@ export const CLOTH_STABILISE_STEPS: number;
|
|
|
277
277
|
*/
|
|
278
278
|
export const CLOTH_DRAG_RATE_MAX: number;
|
|
279
279
|
/**
|
|
280
|
-
* Speed below which
|
|
281
|
-
* reach per second
|
|
282
|
-
*
|
|
280
|
+
* Speed below which the air counts as unchanged, as a fraction of the cloth's
|
|
281
|
+
* reach per second — §10's wake test, which asks whether a gust has arrived.
|
|
282
|
+
*
|
|
283
|
+
* This was the sleep test's own threshold until the sleep test stopped reading
|
|
284
|
+
* a speed; see {@link CLOTH_SLEEP_DISPLACEMENT_RATIO} for why.
|
|
283
285
|
* @type {number}
|
|
284
286
|
*/
|
|
285
287
|
export const CLOTH_SLEEP_SPEED_RATIO: number;
|
|
288
|
+
/**
|
|
289
|
+
* How far a particle may travel from the window's anchor and still count as
|
|
290
|
+
* still, as a fraction of the cloth's reach (§7 P5).
|
|
291
|
+
*
|
|
292
|
+
* **Displacement rather than speed, and a contact is why.** A cloth resting on
|
|
293
|
+
* a collider carries contact rows whose penalty `γ = 1` never decays, and the
|
|
294
|
+
* configuration it settles into is a *bounded limit cycle* rather than a rest
|
|
295
|
+
* state: measured on an 8-link rope on a floor, a tenth of a millimetre of
|
|
296
|
+
* travel per step — invisible — at a peak particle speed of 1.5e-2 m/s, which
|
|
297
|
+
* is eighteen times what the old speed threshold allowed. An instantaneous
|
|
298
|
+
* speed cannot tell a cloth vibrating in place apart from one going somewhere;
|
|
299
|
+
* the distance it has covered since the window opened can, and that is what
|
|
300
|
+
* §7 P5's own text has always described.
|
|
301
|
+
*
|
|
302
|
+
* `5e-4` rather than {@link CLOTH_SLEEP_SPEED_RATIO}'s `1e-3` so the gate is no
|
|
303
|
+
* *looser* than the speed test it replaces: over the {@link CLOTH_SLEEP_STEPS}
|
|
304
|
+
* window at the engine's 60 Hz fixed step, half a thousandth of a reach is the
|
|
305
|
+
* same millimetre a second. Every library entry on a floor or a sphere sleeps
|
|
306
|
+
* inside 961 steps at this value. `MEASUREMENTS.md` §53.
|
|
307
|
+
* @type {number}
|
|
308
|
+
*/
|
|
309
|
+
export const CLOTH_SLEEP_DISPLACEMENT_RATIO: number;
|
|
286
310
|
/**
|
|
287
311
|
* How many consecutive still steps put a cloth to sleep (§7 P5). Half a second
|
|
288
312
|
* at 60 Hz, which is long enough that a cloth pausing at the top of a swing
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloth_dynamics_map.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/ecs/cloth_dynamics_map.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cloth_dynamics_map.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/cloth/ecs/cloth_dynamics_map.js"],"names":[],"mappings":"AAmQA;;;;;;;;GAQG;AACH,gDAHW,MAAM,GACJ,MAAM,CAMlB;AAED;;;;;;;;GAQG;AACH,uCAHW,MAAM,GACJ,MAAM,CAUlB;AAED;;;;;;;;;;;GAWG;AACH,mDAPW,MAAM,YAEN,MAAM,cACN,MAAM,MACN,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,0CAJW,MAAM,MACN,MAAM,GACJ,MAAM,CAYlB;AAED;;;;;;;;GAQG;AACH,kDAJW,MAAM,MACN,MAAM,GACJ,MAAM,CAQlB;AA7WD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;GAKG;AACH,+BAFU,MAAM,CAEqB;AAErC;;;;;GAKG;AACH,+BAFU,MAAM,CAEqB;AAErC;;;;;;;;;;;GAWG;AACH,mCAFU,MAAM,CAEsB;AAEtC;;;;;GAKG;AACH,mCAFU,MAAM,CAEyB;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,+CAFU,MAAM,CAEkC;AAElD;;;;;;;;GAQG;AACH,iCAFU,MAAM,CAEqB;AAErC;;;;;;;;;;;;;GAaG;AACH,wCAFU,MAAM,CAE6B;AAE7C;;;;;;;;GAQG;AACH,0CAFU,MAAM,CAE+B;AAE/C;;;;;;;;GAQG;AACH,mCAFU,MAAM,CAEsB;AAEtC;;;;;;;;GAQG;AACH,mCAFU,MAAM,CAEsB;AAEtC;;;;;;;GAOG;AACH,oCAFU,MAAM,CAEwB;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,kCAFU,MAAM,CAEsB;AAEtC;;;;;;;GAOG;AACH,sCAFU,MAAM,CAE4B;AAE5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,6CAFU,MAAM,CAEmC;AAEnD;;;;;GAKG;AACH,gCAFU,MAAM,CAEoB"}
|
|
@@ -217,13 +217,38 @@ export const CLOTH_STABILISE_STEPS = 20;
|
|
|
217
217
|
export const CLOTH_DRAG_RATE_MAX = 60;
|
|
218
218
|
|
|
219
219
|
/**
|
|
220
|
-
* Speed below which
|
|
221
|
-
* reach per second
|
|
222
|
-
*
|
|
220
|
+
* Speed below which the air counts as unchanged, as a fraction of the cloth's
|
|
221
|
+
* reach per second — §10's wake test, which asks whether a gust has arrived.
|
|
222
|
+
*
|
|
223
|
+
* This was the sleep test's own threshold until the sleep test stopped reading
|
|
224
|
+
* a speed; see {@link CLOTH_SLEEP_DISPLACEMENT_RATIO} for why.
|
|
223
225
|
* @type {number}
|
|
224
226
|
*/
|
|
225
227
|
export const CLOTH_SLEEP_SPEED_RATIO = 1e-3;
|
|
226
228
|
|
|
229
|
+
/**
|
|
230
|
+
* How far a particle may travel from the window's anchor and still count as
|
|
231
|
+
* still, as a fraction of the cloth's reach (§7 P5).
|
|
232
|
+
*
|
|
233
|
+
* **Displacement rather than speed, and a contact is why.** A cloth resting on
|
|
234
|
+
* a collider carries contact rows whose penalty `γ = 1` never decays, and the
|
|
235
|
+
* configuration it settles into is a *bounded limit cycle* rather than a rest
|
|
236
|
+
* state: measured on an 8-link rope on a floor, a tenth of a millimetre of
|
|
237
|
+
* travel per step — invisible — at a peak particle speed of 1.5e-2 m/s, which
|
|
238
|
+
* is eighteen times what the old speed threshold allowed. An instantaneous
|
|
239
|
+
* speed cannot tell a cloth vibrating in place apart from one going somewhere;
|
|
240
|
+
* the distance it has covered since the window opened can, and that is what
|
|
241
|
+
* §7 P5's own text has always described.
|
|
242
|
+
*
|
|
243
|
+
* `5e-4` rather than {@link CLOTH_SLEEP_SPEED_RATIO}'s `1e-3` so the gate is no
|
|
244
|
+
* *looser* than the speed test it replaces: over the {@link CLOTH_SLEEP_STEPS}
|
|
245
|
+
* window at the engine's 60 Hz fixed step, half a thousandth of a reach is the
|
|
246
|
+
* same millimetre a second. Every library entry on a floor or a sphere sleeps
|
|
247
|
+
* inside 961 steps at this value. `MEASUREMENTS.md` §53.
|
|
248
|
+
* @type {number}
|
|
249
|
+
*/
|
|
250
|
+
export const CLOTH_SLEEP_DISPLACEMENT_RATIO = 5e-4;
|
|
251
|
+
|
|
227
252
|
/**
|
|
228
253
|
* How many consecutive still steps put a cloth to sleep (§7 P5). Half a second
|
|
229
254
|
* at 60 Hz, which is long enough that a cloth pausing at the top of a swing
|