@woosh/meep-engine 2.143.0 → 2.145.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/bvh2/bvh3/BVH.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/BVH.js +158 -4
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts +56 -0
- package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/CylinderShape3D.js +223 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts +1 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PointShape3D.js +11 -0
- package/src/core/geom/3d/shape/SphereShape3D.d.ts +1 -0
- package/src/core/geom/3d/shape/SphereShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/SphereShape3D.js +4 -0
- package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/shape_to_type.js +3 -0
- package/src/core/geom/3d/shape/json/type_adapters.d.ts +15 -0
- package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/type_adapters.js +16 -0
- package/src/engine/control/first-person/DESIGN_COLLISION.md +314 -217
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +104 -58
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1828 -1789
- package/src/engine/control/first-person/TODO.md +17 -32
- package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/WallRun.js +18 -35
- package/src/engine/control/first-person/collision/KinematicMover.d.ts +206 -0
- package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -0
- package/src/engine/control/first-person/collision/KinematicMover.js +592 -0
- package/src/engine/control/first-person/prototype_first_person_controller.js +65 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +18 -9
- package/src/engine/physics/PLAN.md +145 -41
- package/src/engine/physics/contact/ManifoldStore.d.ts +28 -2
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +37 -3
- package/src/engine/physics/contact/combine_material.d.ts +30 -0
- package/src/engine/physics/contact/combine_material.d.ts.map +1 -0
- package/src/engine/physics/contact/combine_material.js +35 -0
- package/src/engine/physics/ecs/Collider.d.ts +15 -0
- package/src/engine/physics/ecs/Collider.d.ts.map +1 -1
- package/src/engine/physics/ecs/Collider.js +34 -0
- package/src/engine/physics/ecs/Joint.d.ts +18 -0
- package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
- package/src/engine/physics/ecs/Joint.js +70 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +9 -4
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +9 -4
- package/src/engine/physics/ecs/RigidBody.d.ts +15 -0
- package/src/engine/physics/ecs/RigidBody.d.ts.map +1 -1
- package/src/engine/physics/ecs/RigidBody.js +46 -0
- package/src/engine/physics/narrowphase/compute_penetration.d.ts +41 -41
- package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/compute_penetration.js +96 -169
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts +52 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +130 -3
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +10 -21
|
@@ -199,8 +199,6 @@ const SAOShader = {
|
|
|
199
199
|
// Roberts' R3 low-discrepancy additive recurrence: ( 1/g, 1/g^2, 1/g^3 ), g^4 = g + 1
|
|
200
200
|
const vec3 R3 = vec3( 0.8191725134, 0.6710436067, 0.5497004779 );
|
|
201
201
|
|
|
202
|
-
const float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES );
|
|
203
|
-
|
|
204
202
|
// normal-bias as a fraction of kernelRadius: lifts the sample origin off the surface to avoid
|
|
205
203
|
// self-occlusion. Larger removes acne but lets near-surface contact occlusion leak.
|
|
206
204
|
const float SURFACE_BIAS = 0.05;
|
|
@@ -246,6 +244,7 @@ const SAOShader = {
|
|
|
246
244
|
float falloffAdd = falloffFrom / falloffRange + 1.0;
|
|
247
245
|
|
|
248
246
|
float occlusion = 0.0;
|
|
247
|
+
float weightSum = 0.0;
|
|
249
248
|
|
|
250
249
|
for ( int i = 0; i < NUM_SAMPLES; i ++ ) {
|
|
251
250
|
// fixed R3 low-discrepancy point; per-pixel variation comes from the rotated frame
|
|
@@ -260,6 +259,10 @@ const SAOShader = {
|
|
|
260
259
|
// sampleDistributionPower) so the near occluders that dominate AO are favoured
|
|
261
260
|
float radius = pow( fract( q.z + noise.y ), sampleDistributionPower );
|
|
262
261
|
|
|
262
|
+
// falloff weight from the probe's own distance -- defined for every sample, so the
|
|
263
|
+
// weighted normaliser is well posed; distant probes are less reliable (GTAO)
|
|
264
|
+
float weight = saturate( radius * kernelRadius * falloffMul + falloffAdd );
|
|
265
|
+
|
|
263
266
|
vec3 samplePositionVS = sampleOrigin + dir * ( radius * kernelRadius );
|
|
264
267
|
|
|
265
268
|
// project the sample into screen space
|
|
@@ -283,6 +286,9 @@ const SAOShader = {
|
|
|
283
286
|
continue;
|
|
284
287
|
}
|
|
285
288
|
|
|
289
|
+
// a valid (on-screen) sample contributes its weight to the normaliser, occluded or not
|
|
290
|
+
weightSum += weight;
|
|
291
|
+
|
|
286
292
|
float sampleDepth = getDepth( sampleUv );
|
|
287
293
|
|
|
288
294
|
// skip the sky / far plane
|
|
@@ -302,17 +308,16 @@ const SAOShader = {
|
|
|
302
308
|
// ...and the tapped surface must actually be within the sampling radius, otherwise we
|
|
303
309
|
// have hit distant geometry through the depth buffer that does not occlude this point
|
|
304
310
|
vec3 occluderVS = getViewPosition( sampleUv, sampleDepth, sceneViewZ );
|
|
305
|
-
|
|
306
|
-
if ( sampleDist > kernelRadius ) {
|
|
311
|
+
if ( distance( centerViewPosition, occluderVS ) > kernelRadius ) {
|
|
307
312
|
continue;
|
|
308
313
|
}
|
|
309
314
|
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
occlusion += saturate( sampleDist * falloffMul + falloffAdd );
|
|
315
|
+
// occluded: add this sample's weight to the numerator
|
|
316
|
+
occlusion += weight;
|
|
313
317
|
}
|
|
314
318
|
|
|
315
|
-
|
|
319
|
+
// weighted occlusion fraction; off-screen samples were excluded so screen borders are unbiased
|
|
320
|
+
return weightSum > 0.0 ? occlusion / weightSum : 0.0;
|
|
316
321
|
}
|
|
317
322
|
|
|
318
323
|
void main() {
|
|
@@ -323,7 +328,11 @@ const SAOShader = {
|
|
|
323
328
|
|
|
324
329
|
float centerDepth = getDepth( centerUv );
|
|
325
330
|
if( centerDepth >= ( 1.0 - EPSILON ) ) {
|
|
326
|
-
|
|
331
|
+
// background / far plane is fully visible. Write white rather than discarding, so the
|
|
332
|
+
// half-res buffer never leaves the clear value for the upscale to read back as occlusion
|
|
333
|
+
// (that is the foreground-edge halo over background).
|
|
334
|
+
out_occlusion = 1.0;
|
|
335
|
+
return;
|
|
327
336
|
}
|
|
328
337
|
|
|
329
338
|
float centerViewZ = getViewZ( centerDepth );
|
|
@@ -169,21 +169,35 @@ Architectural references for design choices:
|
|
|
169
169
|
kinematic bodies (character controllers, AOE selection).
|
|
170
170
|
|
|
171
171
|
### Standalone narrowphase utilities
|
|
172
|
+
- `deepest_pair_penetration(out_normal, shapeA, posA, rotA, shapeB, posB,
|
|
173
|
+
rotB)` (exported from `narrowphase_step.js`) — runs the **same**
|
|
174
|
+
`dispatch_pair` the contact solver consumes for one posed shape pair and
|
|
175
|
+
returns the DEEPEST contact's depth + world normal (B → A). The single
|
|
176
|
+
source of truth for "minimum-translation between two posed shapes", reused by
|
|
177
|
+
`compute_penetration` (and available to any other query).
|
|
172
178
|
- `compute_penetration(out_direction, shape_a, pos_a, rot_a, shape_b,
|
|
173
179
|
pos_b, rot_b)` — non-system geometry primitive: positive penetration
|
|
174
180
|
depth + outward direction (B → A convention) on overlap, 0 otherwise.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
**Hardened** — delegates to `deepest_pair_penetration`, so it is correct
|
|
182
|
+
(not "correct sometimes") for every shape pair the engine can build:
|
|
183
|
+
- sphere / box / capsule pairs → exact closed-form (box-box via SAT, so a
|
|
184
|
+
small body resting on a large floor reports the few-cm near-face overlap,
|
|
185
|
+
NOT the metres-deep "exit through the far side" that MPR's centroid-seeded
|
|
186
|
+
portal used to return);
|
|
187
|
+
- general convex pairs → GJK + EPA (exact for polytopes; curved shapes never
|
|
188
|
+
reach it — they have closed forms);
|
|
189
|
+
- convex × concave → triangle decomposition + the closed-form per-triangle
|
|
190
|
+
solvers, bounded to each triangle's true 2-D extent (the old closed-mesh
|
|
191
|
+
side-face over-report is gone).
|
|
192
|
+
The previous per-triangle half-space test is retained ONLY as a recovery
|
|
193
|
+
fallback for the one case the one-sided closed forms can't resolve: a convex
|
|
194
|
+
shape that has fully tunnelled to the *inner* side of a concave surface (a
|
|
195
|
+
depenetration query must still push it back out — exact for heightmap terrain,
|
|
196
|
+
a valid outward push for closed meshes). Concave × concave throws (M×N
|
|
197
|
+
triangle pairs out of scope). The spec asserts an "applying out_direction ×
|
|
198
|
+
depth separates the shapes" invariant across every convex+convex pair type and
|
|
199
|
+
convex+concave, plus exact per-type depths and the small-box-on-huge-floor
|
|
200
|
+
regression (3 m → 0.05 m).
|
|
187
201
|
|
|
188
202
|
### Determinism
|
|
189
203
|
- Direct typed-array writes on hot paths (bypassing `Vector3#set`'s observer
|
|
@@ -224,11 +238,14 @@ Architectural references for design choices:
|
|
|
224
238
|
|
|
225
239
|
## Limitations / Known caveats
|
|
226
240
|
|
|
227
|
-
- **Multi-collider material precision
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
241
|
+
- **Multi-collider material precision** — *resolved for contact materials.* The
|
|
242
|
+
narrowphase now combines the specific source-collider pair's friction /
|
|
243
|
+
restitution per contact and stores them in the manifold (CONTACT_STRIDE
|
|
244
|
+
offsets 14/15); the solver reads them per contact, so mixed-material compound
|
|
245
|
+
bodies are accurate (regression test: an asymmetric-friction body yaws when
|
|
246
|
+
shoved). Still primary-collider only: the contact-filter callback's
|
|
247
|
+
`colliderA/B` arguments and the body-level sensor / concave-dispatch flags —
|
|
248
|
+
a smaller follow-up.
|
|
232
249
|
- **EPA on smooth shapes**: degenerates (no flat face to converge on).
|
|
233
250
|
Mitigated by closed-form paths for sphere/cube/capsule pairs and by the
|
|
234
251
|
**MPR fallback** on EPA non-convergence; exotic convex shapes vs spheres can
|
|
@@ -239,12 +256,20 @@ Architectural references for design choices:
|
|
|
239
256
|
for those primitives, so a sphere/box/capsule on a heightmap or mesh decelerates
|
|
240
257
|
and settles correctly; the `narrowphase_concave.spec.js` "drop and settle"
|
|
241
258
|
cases and the mesh torus-knot settle test are **un-skipped**. Per-triangle
|
|
242
|
-
GJK+EPA remains only as the fallback for *other* convex shapes vs triangles
|
|
243
|
-
(
|
|
259
|
+
GJK+EPA remains only as the fallback for *other* convex shapes vs triangles.
|
|
260
|
+
(`compute_penetration` now routes through that same dispatch via
|
|
261
|
+
`deepest_pair_penetration` — see *Standalone narrowphase utilities* — instead
|
|
262
|
+
of its old half-space pre-test; the half-space test survives only as a
|
|
263
|
+
tunnel-recovery fallback.)
|
|
244
264
|
- **Box-box edge-edge contact**: a single point at the true closest-pair of the
|
|
245
265
|
two edges (P3.2), not the old body-centre midpoint. This is geometrically
|
|
246
|
-
correct
|
|
247
|
-
|
|
266
|
+
correct — and an empirical SAT-source sweep confirms the edge-cross branch
|
|
267
|
+
*only* fires for **transverse** edge crossings (inter-edge angle ≈ 83-90°),
|
|
268
|
+
where two skew lines meet at a unique point. Near-parallel edge contacts
|
|
269
|
+
cannot reach this branch (a near-parallel `edgeA × edgeB` never wins the SAT
|
|
270
|
+
minimum) — they resolve through the multi-point face-clipping path. So the
|
|
271
|
+
once-planned "multi-point edge contact for near-parallel edges" refinement is
|
|
272
|
+
**moot**; see the resolved Stability backlog entry.
|
|
248
273
|
- **CCD floor only**: speculative margin via the fattened AABB prevents
|
|
249
274
|
most tunnelling. No per-body swept shape-cast for very fast objects.
|
|
250
275
|
- **Cross-runtime determinism is not guaranteed**: `Math.sin/cos/exp/log`
|
|
@@ -530,28 +555,93 @@ scaffolding is in place.
|
|
|
530
555
|
those primitives. Un-skipped the `narrowphase_concave.spec.js` ball-on-
|
|
531
556
|
heightmap / mesh-cube settle tests and the `PhysicsSystem.spec.js`
|
|
532
557
|
torus-knot test. Per-triangle GJK+EPA remains only as the fallback for
|
|
533
|
-
*other* convex shapes vs triangles
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
558
|
+
*other* convex shapes vs triangles. `compute_penetration` now routes
|
|
559
|
+
through the shared narrowphase dispatch (`deepest_pair_penetration`), so it
|
|
560
|
+
uses the closed-form per-triangle solvers too — the old closed-mesh
|
|
561
|
+
over-report is gone; the half-space test is retained only as a
|
|
562
|
+
tunnel-recovery fallback.
|
|
563
|
+
- [x] **Edge-edge multi-point manifold** — *resolved by design (no code
|
|
564
|
+
change needed).* An empirical SAT-source sweep over a wide range of
|
|
565
|
+
box-box orientations shows the single-point edge-cross branch only ever
|
|
566
|
+
wins for **transverse** edge crossings (inter-edge angle ≈ 83-90°), where
|
|
567
|
+
a single closest-pair point is geometrically exact. A near-parallel edge
|
|
568
|
+
pair gives a near-degenerate `edgeA × edgeB` that never becomes the SAT
|
|
569
|
+
minimum, so near-parallel ("line") edge contacts resolve through the
|
|
570
|
+
multi-point **face-clipping** path instead — confirmed by regression
|
|
571
|
+
tests in `box_box_manifold.spec.js` (near-parallel tilted boxes → ≥ 2
|
|
572
|
+
points; transverse crossing → exactly 1 exact point). The originally
|
|
573
|
+
planned refinement targeted a case the geometry can't produce, so it is
|
|
574
|
+
closed rather than implemented.
|
|
575
|
+
- [x] **Per-contact source-collider tracking (materials)** — multi-material
|
|
576
|
+
compound bodies now get accurate per-contact friction / restitution. The
|
|
577
|
+
narrowphase combines the specific (colliderA, colliderB) pair's
|
|
578
|
+
coefficients at dispatch time (the only place that knows the source
|
|
579
|
+
collider on each side — `contact/combine_material.js`) and stamps them
|
|
580
|
+
into the manifold (CONTACT_STRIDE grown 14 → 16, offsets 14/15); the
|
|
581
|
+
solver reads them per contact instead of from the body's primary collider.
|
|
582
|
+
Regression test: an asymmetric-friction compound body yaws when shoved
|
|
583
|
+
(the grippy collider drags), and a symmetric control does not. Still
|
|
584
|
+
primary-collider-only: the contact-filter callback's collider args and the
|
|
585
|
+
body-level sensor / concave flags (smaller follow-up).
|
|
586
|
+
- [ ] **Joint-aware island sleep (ragdoll settle quality).** A draped,
|
|
587
|
+
self-colliding 10-joint ragdoll does not fully sleep in 10 s — surfaced by
|
|
588
|
+
a 1000-seed Monte-Carlo sweep (`PhysicsSystem.ragdoll.spec.js`, `.skip`):
|
|
589
|
+
for unlucky seeds a distal limb sustains a settled limit cycle (settled
|
|
590
|
+
finite-difference accel up to ~1094 m/s² / ~1479 rad/s² at a limb end vs a
|
|
591
|
+
~55 m/s² median — bounded, non-growing, penetration-free, so a quality gap
|
|
592
|
+
not a divergence). The sleep test today is per-body `|v|²+|ω|²`; an island
|
|
593
|
+
over-constrained by cone-twist limits + self-contacts keeps small residual
|
|
594
|
+
jiggle above the per-body threshold so it never crosses into sleep.
|
|
595
|
+
Candidate fixes: sleep a jointed/contacting island on its AGGREGATE motion
|
|
596
|
+
rather than the per-body minimum, and/or a settled-regime relaxation (zero
|
|
597
|
+
restitution + extra position iterations) once an island's energy is low.
|
|
598
|
+
The sweep flags the worst seeds for replay. (Test infra also adds
|
|
599
|
+
per-point kinematics tracking — joint anchors + limb ends, with
|
|
600
|
+
displacement→velocity→acceleration and the angular equivalents.)
|
|
542
601
|
|
|
543
602
|
### Performance / Scale
|
|
544
603
|
- [ ] **Per-body linear CCD shape-cast**: optional opt-in for fast-moving
|
|
545
604
|
bodies where speculative margin isn't enough. The bench's falling
|
|
546
605
|
tower (1km drop onto a 1cm floor) is the concrete reproducer —
|
|
547
606
|
180 / 1000 bodies tunnel.
|
|
548
|
-
- [
|
|
549
|
-
(`
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
607
|
+
- [x] **Broadphase BVH balance — SAH rotation.** The dynamic AABB tree
|
|
608
|
+
(`core/bvh2/bvh3/BVH.js`, a Box2D port) used SAH-cost insertion but a
|
|
609
|
+
*height-only* AVL rotation (`balance_height`): height-balanced yet not
|
|
610
|
+
SAH-balanced, so queries walked more nodes than needed. Replaced the
|
|
611
|
+
rotation in `bubble_up_update` with `balance_rotate` — the Box2D-v3 /
|
|
612
|
+
Kensler SAH-reducing rotation (for node A with children B, C, evaluate the
|
|
613
|
+
four child↔grandchild swaps and apply the one that most reduces the
|
|
614
|
+
surface-area cost). Deterministic; identical pair set.
|
|
615
|
+
- Measured (same-session A/B, heavy benches): raycast **−9%**
|
|
616
|
+
(28.2→25.6 µs/ray), falling-tower median **−10%**, settling-grid
|
|
617
|
+
median **−12%**, and the **990/1000-churn stress −27%**
|
|
618
|
+
(63.95→46.68 ms mean over 10k ticks) — biggest where the tree churns
|
|
619
|
+
hardest. Determinism (8-trial bit-identical) holds.
|
|
620
|
+
- **Insertion cost (measured):** `balance_rotate` does 4 surface-area
|
|
621
|
+
evaluations per bubble-up level vs `balance_height`'s single height
|
|
622
|
+
compare, so *pure bulk insertion* is **~1.4–1.5× slower** — the 100k
|
|
623
|
+
synthetic insert bench (`BVH.spec.js`, drift-controlled interleaved
|
|
624
|
+
A/B) drops from **~37k → ~25k inserts/sec** (~27→~40 µs/insert). This
|
|
625
|
+
is the balancer's worst case (insert-only, zero queries/refits to
|
|
626
|
+
amortise against). It does not show up end-to-end: static trees are
|
|
627
|
+
built once then queried forever, dynamic bodies insert once then
|
|
628
|
+
refit/query every frame, and even the 990/1000-swap stress test — the
|
|
629
|
+
maximal insert-churn workload — is net **−27%**. Accepted.
|
|
630
|
+
- **Tradeoff (documented):** the contact solver's Gauss-Seidel order
|
|
631
|
+
follows broadphase traversal order (see `generate_pairs`), so the
|
|
632
|
+
different tree shape shifts convergence on near-aligned stacks — the
|
|
633
|
+
synthetic 128-cube wall now sleeps at ~10 s (was ~6.9 s). It still
|
|
634
|
+
settles, doesn't creep / topple (all bug-guard assertions hold); only
|
|
635
|
+
the sleep *time* moved (that test's budget was bumped 9→11 s with a
|
|
636
|
+
note). Random-shape scenes (falling tower) were faster *and* settled
|
|
637
|
+
fine.
|
|
638
|
+
- **Follow-up:** decouple the solve order from tree shape — sort the
|
|
639
|
+
broadphase pair list by `(idA, idB)` before narrowphase so contact
|
|
640
|
+
order is body-id-deterministic regardless of tree shape. Then no tree
|
|
641
|
+
change can affect convergence (and the stack settles identically under
|
|
642
|
+
either balancer). Has a per-step sort cost + wide test re-baseline, so
|
|
643
|
+
it's its own task. `balance_height` is retained for comparison /
|
|
644
|
+
fallback.
|
|
555
645
|
- [ ] **Per-island parallel solve**: today's island data layout would
|
|
556
646
|
allow worker-based solving once `SharedArrayBuffer` is available.
|
|
557
647
|
Out-of-scope unless / until SAB is universally usable.
|
|
@@ -578,8 +668,19 @@ scaffolding is in place.
|
|
|
578
668
|
- [ ] **Convex hull shape** with eigen-based principal-axes inertia
|
|
579
669
|
derivation. Hooks `matrix_eigenvalues_in_place` from the existing
|
|
580
670
|
linalg layer.
|
|
581
|
-
- [
|
|
582
|
-
|
|
671
|
+
- [~] **Cylinder / cone shapes.**
|
|
672
|
+
- [x] **`CylinderShape3D`** — Y-aligned solid cylinder (radius + full
|
|
673
|
+
height, flat caps; the capsule's flat-cap sibling). Exact `support`,
|
|
674
|
+
capped-cylinder SDF, bounds, `contains` / `nearest_point` /
|
|
675
|
+
volume-sampling, equals/hash, `'cylinder'` JSON tag, `isCylinderShape3D`
|
|
676
|
+
marker. Convex → routes through the narrowphase **GJK + EPA** fallback
|
|
677
|
+
(no marker dispatch needed); spec asserts overlap-detected +
|
|
678
|
+
MTV-separates vs sphere/box. Closed-form cylinder-vs-X contact pairs
|
|
679
|
+
are a future refinement (the curved side is the usual smooth-support
|
|
680
|
+
EPA case — same status as pre-closed-form sphere/capsule).
|
|
681
|
+
- [ ] Closed-form cylinder contact pairs (cylinder × box / sphere / capsule
|
|
682
|
+
/ plane) for multi-point cap manifolds + stable resting.
|
|
683
|
+
- [ ] **Cone shape** (+ closed-form / GJK fallback).
|
|
583
684
|
|
|
584
685
|
### API polish
|
|
585
686
|
- [x] **`overlap(shape, position, rotation, output, output_offset,
|
|
@@ -598,8 +699,11 @@ scaffolding is in place.
|
|
|
598
699
|
- [x] **`compute_penetration(out_direction, shape_a, pos_a, rot_a,
|
|
599
700
|
shape_b, pos_b, rot_b)`** — standalone geometry primitive (no
|
|
600
701
|
PhysicsSystem) for resolving overlap between two shapes at given
|
|
601
|
-
poses. Returns depth + outward direction.
|
|
602
|
-
|
|
702
|
+
poses. Returns depth + outward direction. **Hardened** to route through
|
|
703
|
+
the shared narrowphase dispatch (`deepest_pair_penetration`): exact
|
|
704
|
+
closed-form for sphere/box/capsule pairs (box-box via SAT), GJK+EPA for
|
|
705
|
+
general convex, closed-form per-triangle for convex × concave; the
|
|
706
|
+
half-space test is retained only for tunnel recovery.
|
|
603
707
|
|
|
604
708
|
### Raycast narrowphase (done)
|
|
605
709
|
|
|
@@ -19,11 +19,19 @@ export const MAX_CONTACTS_PER_MANIFOLD: number;
|
|
|
19
19
|
* 13 : feature_id (uint32 packed as f64 — stable cross-frame ID of the
|
|
20
20
|
* geometric feature pair that produced this contact;
|
|
21
21
|
* 0 means "no info, fall back to position matching")
|
|
22
|
+
* 14 : friction (already-combined coefficient for THIS contact's
|
|
23
|
+
* specific source-collider pair — see combine_material)
|
|
24
|
+
* 15 : restitution(already-combined coefficient for this contact)
|
|
22
25
|
*
|
|
23
26
|
* Solver uses `(world_a + world_b) * 0.5` as the application point; storing
|
|
24
27
|
* both surface points enables the per-frame warm-start matcher in the
|
|
25
28
|
* narrowphase to compare contact-point positions when feature_id is 0.
|
|
26
29
|
*
|
|
30
|
+
* Friction / restitution are stored **per contact** (not per body pair) so a
|
|
31
|
+
* compound body whose colliders have different materials gets the right
|
|
32
|
+
* coefficient at each contact: the narrowphase knows the exact source collider
|
|
33
|
+
* on each side of every contact and combines there.
|
|
34
|
+
*
|
|
27
35
|
* @type {number}
|
|
28
36
|
*/
|
|
29
37
|
export const CONTACT_STRIDE: number;
|
|
@@ -166,7 +174,8 @@ export class ManifoldStore {
|
|
|
166
174
|
* Write a contact point into a slot. Increments contact_count if `idx`
|
|
167
175
|
* exceeds the current count.
|
|
168
176
|
*
|
|
169
|
-
* Writes geometry (offsets 0..9)
|
|
177
|
+
* Writes geometry (offsets 0..9), the feature_id at offset 13, and the
|
|
178
|
+
* per-contact combined friction / restitution at offsets 14 / 15.
|
|
170
179
|
* Deliberately does NOT touch the warm-start impulse fields (offsets
|
|
171
180
|
* 10..12) — the narrowphase match-and-merge pass uses this to refill
|
|
172
181
|
* a slot while preserving the cached impulses that the solver's
|
|
@@ -187,8 +196,25 @@ export class ManifoldStore {
|
|
|
187
196
|
* @param {number} feature_id stable cross-frame identifier of the
|
|
188
197
|
* geometric feature pair that produced this contact; 0 means
|
|
189
198
|
* "no info, fall back to position matching"
|
|
199
|
+
* @param {number} friction combined friction for this contact's specific
|
|
200
|
+
* source-collider pair
|
|
201
|
+
* @param {number} restitution combined restitution for this contact
|
|
202
|
+
*/
|
|
203
|
+
set_contact(slot: number, idx: number, lax: number, lay: number, laz: number, lbx: number, lby: number, lbz: number, nx: number, ny: number, nz: number, depth: number, feature_id?: number, friction?: number, restitution?: number): void;
|
|
204
|
+
/**
|
|
205
|
+
* Read the per-contact combined friction stored at one contact.
|
|
206
|
+
* @param {number} slot
|
|
207
|
+
* @param {number} idx
|
|
208
|
+
* @returns {number}
|
|
209
|
+
*/
|
|
210
|
+
friction_of(slot: number, idx: number): number;
|
|
211
|
+
/**
|
|
212
|
+
* Read the per-contact combined restitution stored at one contact.
|
|
213
|
+
* @param {number} slot
|
|
214
|
+
* @param {number} idx
|
|
215
|
+
* @returns {number}
|
|
190
216
|
*/
|
|
191
|
-
|
|
217
|
+
restitution_of(slot: number, idx: number): number;
|
|
192
218
|
/**
|
|
193
219
|
* Zero the warm-start impulse fields (j_n, j_t1, j_t2) for one contact
|
|
194
220
|
* index inside a slot. Called by the narrowphase match-and-merge pass
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ManifoldStore.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/contact/ManifoldStore.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wCAFU,MAAM,CAE2B;AAE3C
|
|
1
|
+
{"version":3,"file":"ManifoldStore.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/contact/ManifoldStore.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wCAFU,MAAM,CAE2B;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,6BAFU,MAAM,CAEiB;AAEjC;;;;GAIG;AACH,wCAFU,MAAM,CAE4B;AAE5C;;;GAGG;AACH,+BAFU,MAAM,CAE2D;AAyB3E;;;GAGG;AACH,mCAFU,MAAM,CAEuB;AAIvC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAEI;;OAEG;IACH,+BAFW,MAAM,EA6BhB;IAzBG,mBAA+C;IAC/C,qBAAqB;IAErB,qBAAkE;IAClE,oBAAiE;IAQjE,0BAAwD;IAExD,4BAA0D;IAG1D,0BAAoD;IACpD,uBAAiD;IAEjD,qBAAqB;IAGrB,yBAAmD;IACnD,qBAAqB;IAGzB;;OAEG;IACH,oBAEC;IAED;;OAEG;IACH,uBAEC;IAED;;;;;;;OAOG;IACH,UAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAKlB;IAED;;;;;;;;OAQG;IACH,aAJW,MAAM,OACN,MAAM,GACJ,MAAM,CA+BlB;IAED;;;OAGG;IACH,YAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,YAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,oBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,iBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,uBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;OAKG;IACH,WAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;OAKG;IACH,qBAFW,MAAM,QAQhB;IAED;;;;;;;;;;;;;OAaG;IACH,mBAFW,MAAM,QAKhB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,kBAnBW,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,SACN,MAAM,eACN,MAAM,aAGN,MAAM,gBAEN,MAAM,QAwBhB;IAED;;;;;OAKG;IACH,kBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;OAKG;IACH,qBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;;;;;;;;;OAeG;IACH,qBAHW,MAAM,OACN,MAAM,QAOhB;IAED;;;;;OAKG;IACH,oBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;OAMG;IACH,kBALW,MAAM,OACN,MAAM,OACN,MAAM,EAAE,GAAC,YAAY,cACrB,MAAM,QAOhB;IAED;;;;;;OAMG;IACH,uBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED,iEAAiE;IACjE,uBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED,iEAAiE;IACjE,uBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED,iEAAiE;IACjE,oBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED;;;;;OAKG;IACH,gCAEC;IAED;;;;;;OAMG;IACH,qCAEC;IAED;;;;;OAKG;IACH,uBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;OAMG;IACH,uBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,sBA6BC;IAED;;;OAGG;IACH,wBAcC;IAED;;;OAGG;IACH,uBAkBC;IAED;;OAEG;IACH,eA+BC;IAED;;;OAGG;IACH,oBAaC;IAED;;;OAGG;IACH,mBAqBC;CACJ;8BAhoBqD,2CAA2C"}
|
|
@@ -23,14 +23,22 @@ export const MAX_CONTACTS_PER_MANIFOLD = 4;
|
|
|
23
23
|
* 13 : feature_id (uint32 packed as f64 — stable cross-frame ID of the
|
|
24
24
|
* geometric feature pair that produced this contact;
|
|
25
25
|
* 0 means "no info, fall back to position matching")
|
|
26
|
+
* 14 : friction (already-combined coefficient for THIS contact's
|
|
27
|
+
* specific source-collider pair — see combine_material)
|
|
28
|
+
* 15 : restitution(already-combined coefficient for this contact)
|
|
26
29
|
*
|
|
27
30
|
* Solver uses `(world_a + world_b) * 0.5` as the application point; storing
|
|
28
31
|
* both surface points enables the per-frame warm-start matcher in the
|
|
29
32
|
* narrowphase to compare contact-point positions when feature_id is 0.
|
|
30
33
|
*
|
|
34
|
+
* Friction / restitution are stored **per contact** (not per body pair) so a
|
|
35
|
+
* compound body whose colliders have different materials gets the right
|
|
36
|
+
* coefficient at each contact: the narrowphase knows the exact source collider
|
|
37
|
+
* on each side of every contact and combines there.
|
|
38
|
+
*
|
|
31
39
|
* @type {number}
|
|
32
40
|
*/
|
|
33
|
-
export const CONTACT_STRIDE =
|
|
41
|
+
export const CONTACT_STRIDE = 16;
|
|
34
42
|
|
|
35
43
|
/**
|
|
36
44
|
* Per-contact feature_id offset within {@link CONTACT_STRIDE}.
|
|
@@ -286,7 +294,8 @@ export class ManifoldStore {
|
|
|
286
294
|
* Write a contact point into a slot. Increments contact_count if `idx`
|
|
287
295
|
* exceeds the current count.
|
|
288
296
|
*
|
|
289
|
-
* Writes geometry (offsets 0..9)
|
|
297
|
+
* Writes geometry (offsets 0..9), the feature_id at offset 13, and the
|
|
298
|
+
* per-contact combined friction / restitution at offsets 14 / 15.
|
|
290
299
|
* Deliberately does NOT touch the warm-start impulse fields (offsets
|
|
291
300
|
* 10..12) — the narrowphase match-and-merge pass uses this to refill
|
|
292
301
|
* a slot while preserving the cached impulses that the solver's
|
|
@@ -307,8 +316,11 @@ export class ManifoldStore {
|
|
|
307
316
|
* @param {number} feature_id stable cross-frame identifier of the
|
|
308
317
|
* geometric feature pair that produced this contact; 0 means
|
|
309
318
|
* "no info, fall back to position matching"
|
|
319
|
+
* @param {number} friction combined friction for this contact's specific
|
|
320
|
+
* source-collider pair
|
|
321
|
+
* @param {number} restitution combined restitution for this contact
|
|
310
322
|
*/
|
|
311
|
-
set_contact(slot, idx, lax, lay, laz, lbx, lby, lbz, nx, ny, nz, depth, feature_id = 0) {
|
|
323
|
+
set_contact(slot, idx, lax, lay, laz, lbx, lby, lbz, nx, ny, nz, depth, feature_id = 0, friction = 0, restitution = 0) {
|
|
312
324
|
const off = slot * SLOT_DATA_STRIDE + idx * CONTACT_STRIDE;
|
|
313
325
|
this.__data[off] = lax;
|
|
314
326
|
this.__data[off + 1] = lay;
|
|
@@ -322,6 +334,8 @@ export class ManifoldStore {
|
|
|
322
334
|
this.__data[off + 9] = depth;
|
|
323
335
|
// j_n, j_t1, j_t2 (offsets 10..12) are warm-start; preserved across calls.
|
|
324
336
|
this.__data[off + 13] = feature_id;
|
|
337
|
+
this.__data[off + 14] = friction;
|
|
338
|
+
this.__data[off + 15] = restitution;
|
|
325
339
|
|
|
326
340
|
const meta_off = slot * SLOT_META_STRIDE;
|
|
327
341
|
const count_now = this.__meta[meta_off + 2] & COUNT_MASK;
|
|
@@ -330,6 +344,26 @@ export class ManifoldStore {
|
|
|
330
344
|
}
|
|
331
345
|
}
|
|
332
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Read the per-contact combined friction stored at one contact.
|
|
349
|
+
* @param {number} slot
|
|
350
|
+
* @param {number} idx
|
|
351
|
+
* @returns {number}
|
|
352
|
+
*/
|
|
353
|
+
friction_of(slot, idx) {
|
|
354
|
+
return this.__data[slot * SLOT_DATA_STRIDE + idx * CONTACT_STRIDE + 14];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Read the per-contact combined restitution stored at one contact.
|
|
359
|
+
* @param {number} slot
|
|
360
|
+
* @param {number} idx
|
|
361
|
+
* @returns {number}
|
|
362
|
+
*/
|
|
363
|
+
restitution_of(slot, idx) {
|
|
364
|
+
return this.__data[slot * SLOT_DATA_STRIDE + idx * CONTACT_STRIDE + 15];
|
|
365
|
+
}
|
|
366
|
+
|
|
333
367
|
/**
|
|
334
368
|
* Zero the warm-start impulse fields (j_n, j_t1, j_t2) for one contact
|
|
335
369
|
* index inside a slot. Called by the narrowphase match-and-merge pass
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface-material combine rules — how the friction / restitution of the two
|
|
3
|
+
* colliders in contact are merged into the single coefficient the solver uses
|
|
4
|
+
* for that contact.
|
|
5
|
+
*
|
|
6
|
+
* These live in a shared leaf module because the combine happens in the
|
|
7
|
+
* **narrowphase** (which knows the specific source collider on each side of
|
|
8
|
+
* every contact, so a compound body's per-collider materials are honoured
|
|
9
|
+
* per-contact) and the result is stored in the manifold and read by the
|
|
10
|
+
* **solver**. Both sides import from here, keeping one source of truth for the
|
|
11
|
+
* policy.
|
|
12
|
+
*
|
|
13
|
+
* @author Alex Goldring
|
|
14
|
+
* @copyright Company Named Limited (c) 2026
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Combine two friction coefficients — geometric mean (Bullet / PhysX default).
|
|
18
|
+
* @param {number} a
|
|
19
|
+
* @param {number} b
|
|
20
|
+
* @returns {number}
|
|
21
|
+
*/
|
|
22
|
+
export function combine_friction(a: number, b: number): number;
|
|
23
|
+
/**
|
|
24
|
+
* Combine two restitution coefficients — maximum (Unity / common default).
|
|
25
|
+
* @param {number} a
|
|
26
|
+
* @param {number} b
|
|
27
|
+
* @returns {number}
|
|
28
|
+
*/
|
|
29
|
+
export function combine_restitution(a: number, b: number): number;
|
|
30
|
+
//# sourceMappingURL=combine_material.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combine_material.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/contact/combine_material.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;GAKG;AACH,oCAJW,MAAM,KACN,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,uCAJW,MAAM,KACN,MAAM,GACJ,MAAM,CAIlB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface-material combine rules — how the friction / restitution of the two
|
|
3
|
+
* colliders in contact are merged into the single coefficient the solver uses
|
|
4
|
+
* for that contact.
|
|
5
|
+
*
|
|
6
|
+
* These live in a shared leaf module because the combine happens in the
|
|
7
|
+
* **narrowphase** (which knows the specific source collider on each side of
|
|
8
|
+
* every contact, so a compound body's per-collider materials are honoured
|
|
9
|
+
* per-contact) and the result is stored in the manifold and read by the
|
|
10
|
+
* **solver**. Both sides import from here, keeping one source of truth for the
|
|
11
|
+
* policy.
|
|
12
|
+
*
|
|
13
|
+
* @author Alex Goldring
|
|
14
|
+
* @copyright Company Named Limited (c) 2026
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Combine two friction coefficients — geometric mean (Bullet / PhysX default).
|
|
19
|
+
* @param {number} a
|
|
20
|
+
* @param {number} b
|
|
21
|
+
* @returns {number}
|
|
22
|
+
*/
|
|
23
|
+
export function combine_friction(a, b) {
|
|
24
|
+
return Math.sqrt(a * b);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Combine two restitution coefficients — maximum (Unity / common default).
|
|
29
|
+
* @param {number} a
|
|
30
|
+
* @param {number} b
|
|
31
|
+
* @returns {number}
|
|
32
|
+
*/
|
|
33
|
+
export function combine_restitution(a, b) {
|
|
34
|
+
return a > b ? a : b;
|
|
35
|
+
}
|
|
@@ -84,6 +84,21 @@ export class Collider {
|
|
|
84
84
|
flags: number;
|
|
85
85
|
};
|
|
86
86
|
fromJSON(json: any): void;
|
|
87
|
+
/**
|
|
88
|
+
* Value equality over the shape and the surface material. System-private
|
|
89
|
+
* fields (`_bvhNode`, `_bodyId`) are excluded. Shapes are compared by value
|
|
90
|
+
* (`shape.equals`), so two colliders sharing one shape instance or holding
|
|
91
|
+
* equal shapes both compare equal.
|
|
92
|
+
*
|
|
93
|
+
* @param {Collider} other
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
equals(other: Collider): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Hash over the same state as {@link equals}. Equal colliders hash equal.
|
|
99
|
+
* @returns {number}
|
|
100
|
+
*/
|
|
101
|
+
hash(): number;
|
|
87
102
|
/**
|
|
88
103
|
* @readonly
|
|
89
104
|
* @type {boolean}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Collider.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/ecs/Collider.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Collider.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/ecs/Collider.js"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,+BAFU,MAAM,CAEmB;AAEnC;;;;;;;;;;GAUG;AACH;IAEI;;;OAGG;IACH,OAFU,eAAe,CAEU;IAEnC;;;;OAIG;IACH,UAFU,MAAM,CAED;IAEf;;;;OAIG;IACH,aAFU,MAAM,CAEA;IAEhB;;;;OAIG;IACH,SAFU,MAAM,CAEJ;IAEZ;;OAEG;IACH,OAFU,aAAa,GAAC,MAAM,CAEH;IAE3B;;;OAGG;IACH,UAFU,MAAM,CAEY;IAE5B;;;;OAIG;IACH,SAFU,MAAM,CAEH;IAEb;;;OAGG;IACH,cAFW,MAAM,GAAC,aAAa,QAI9B;IAED;;;OAGG;IACH,gBAFW,MAAM,GAAC,aAAa,QAI9B;IAED;;;;OAIG;IACH,cAHW,MAAM,GAAC,aAAa,GAClB,OAAO,CAInB;IAED;;;;OAIG;IACH,gBAHW,MAAM,GAAC,aAAa,SACpB,OAAO,QAQjB;IAED;;;;;MAOC;IAED,0BAKC;IAED;;;;;;;;OAQG;IACH,cAHW,QAAQ,GACN,OAAO,CAWnB;IAED;;;OAGG;IACH,QAFa,MAAM,CASlB;IASL;;;OAGG;IACH,qBAFU,OAAO,CAEY;CAZ5B;;kBAIS,MAAM;;gCAhKgB,gDAAgD;8BAElD,oBAAoB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { computeHashFloat } from "../../../core/primitives/numbers/computeHashFloat.js";
|
|
1
2
|
import { AbstractShape3D } from "../../../core/geom/3d/shape/AbstractShape3D.js";
|
|
2
3
|
import { UnitSphereShape3D } from "../../../core/geom/3d/shape/UnitSphereShape3D.js";
|
|
3
4
|
import { ColliderFlags } from "./ColliderFlags.js";
|
|
@@ -121,6 +122,39 @@ export class Collider {
|
|
|
121
122
|
if (json.density !== undefined) this.density = json.density;
|
|
122
123
|
if (json.flags !== undefined) this.flags = json.flags;
|
|
123
124
|
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Value equality over the shape and the surface material. System-private
|
|
128
|
+
* fields (`_bvhNode`, `_bodyId`) are excluded. Shapes are compared by value
|
|
129
|
+
* (`shape.equals`), so two colliders sharing one shape instance or holding
|
|
130
|
+
* equal shapes both compare equal.
|
|
131
|
+
*
|
|
132
|
+
* @param {Collider} other
|
|
133
|
+
* @returns {boolean}
|
|
134
|
+
*/
|
|
135
|
+
equals(other) {
|
|
136
|
+
if (other === this) return true;
|
|
137
|
+
if (other === null || other === undefined || other.isCollider !== true) return false;
|
|
138
|
+
|
|
139
|
+
return this.friction === other.friction
|
|
140
|
+
&& this.restitution === other.restitution
|
|
141
|
+
&& this.density === other.density
|
|
142
|
+
&& this.flags === other.flags
|
|
143
|
+
&& this.shape.equals(other.shape);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Hash over the same state as {@link equals}. Equal colliders hash equal.
|
|
148
|
+
* @returns {number}
|
|
149
|
+
*/
|
|
150
|
+
hash() {
|
|
151
|
+
let h = this.shape.hash() | 0;
|
|
152
|
+
h = (h * 31 + computeHashFloat(this.friction)) | 0;
|
|
153
|
+
h = (h * 31 + computeHashFloat(this.restitution)) | 0;
|
|
154
|
+
h = (h * 31 + computeHashFloat(this.density)) | 0;
|
|
155
|
+
h = (h * 31 + (this.flags | 0)) | 0;
|
|
156
|
+
return h;
|
|
157
|
+
}
|
|
124
158
|
}
|
|
125
159
|
|
|
126
160
|
/**
|
|
@@ -280,6 +280,24 @@ export class Joint {
|
|
|
280
280
|
* @returns {Joint} this
|
|
281
281
|
*/
|
|
282
282
|
setAngularSpring(axis: number, stiffness: number, damping: number): Joint;
|
|
283
|
+
/**
|
|
284
|
+
* Value equality over the configurable / persistent state — the same field
|
|
285
|
+
* set that {@link JointSerializationAdapter} writes: the two anchor
|
|
286
|
+
* entities, the local anchor points and basis frames, the swing-twist flag,
|
|
287
|
+
* and the six per-DOF mode + limit / spring / motor arrays. Transient
|
|
288
|
+
* solver state (`dofImpulse`) and the system-resolved handles (`_bodyIdA`,
|
|
289
|
+
* `_bodyIdB`, `_jointId`) are excluded.
|
|
290
|
+
*
|
|
291
|
+
* @param {Joint} other
|
|
292
|
+
* @returns {boolean}
|
|
293
|
+
*/
|
|
294
|
+
equals(other: Joint): boolean;
|
|
295
|
+
/**
|
|
296
|
+
* Hash over the same persistent state as {@link equals}. Equal joints hash
|
|
297
|
+
* equal.
|
|
298
|
+
* @returns {number}
|
|
299
|
+
*/
|
|
300
|
+
hash(): number;
|
|
283
301
|
/**
|
|
284
302
|
* @readonly
|
|
285
303
|
* @type {boolean}
|