@woosh/meep-engine 2.145.0 → 2.147.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 +33 -3
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.js +486 -451
- package/src/engine/control/first-person/DESIGN_COLLISION.md +365 -352
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +1 -14
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +20 -8
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +552 -546
- package/src/engine/control/first-person/TODO.md +13 -11
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +8 -3
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/LedgeGrab.js +213 -199
- package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/Mantle.js +195 -188
- package/src/engine/control/first-person/abilities/WallJump.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/WallJump.js +11 -3
- package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/WallRun.js +183 -163
- package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -1
- package/src/engine/control/first-person/collision/KinematicMover.js +634 -592
- package/src/engine/control/first-person/prototype_first_person_controller.js +1003 -901
- package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts +9 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -1
- package/src/engine/control/first-person/sensors/FirstPersonSensors.js +87 -77
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +8 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +229 -196
- package/src/engine/ecs/EntityManager.d.ts +34 -11
- package/src/engine/ecs/EntityManager.d.ts.map +1 -1
- package/src/engine/ecs/EntityManager.js +71 -42
- package/src/engine/interpolation/BinaryInterpolationAdapter.d.ts.map +1 -0
- package/src/engine/interpolation/Interpoland.d.ts +48 -0
- package/src/engine/interpolation/Interpoland.d.ts.map +1 -0
- package/src/engine/interpolation/Interpoland.js +49 -0
- package/src/engine/interpolation/Interpolated.d.ts +101 -0
- package/src/engine/interpolation/Interpolated.d.ts.map +1 -0
- package/src/engine/interpolation/Interpolated.js +149 -0
- package/src/engine/{network/sim → interpolation}/InterpolationLog.d.ts +1 -1
- package/src/engine/interpolation/InterpolationLog.d.ts.map +1 -0
- package/src/engine/{network/sim → interpolation}/InterpolationLog.js +2 -2
- package/src/engine/interpolation/InterpolationSystem.d.ts +116 -0
- package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -0
- package/src/engine/interpolation/InterpolationSystem.js +233 -0
- package/src/engine/interpolation/PoseInterpolationAdapter.d.ts +17 -0
- package/src/engine/interpolation/PoseInterpolationAdapter.d.ts.map +1 -0
- package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +35 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -0
- package/src/engine/interpolation/pose_interpoland.d.ts +18 -0
- package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -0
- package/src/engine/interpolation/pose_interpoland.js +27 -0
- package/src/engine/network/NetworkSession.d.ts +2 -2
- package/src/engine/network/NetworkSession.d.ts.map +1 -1
- package/src/engine/network/NetworkSession.js +2 -2
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts +1 -1
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +1 -1
- package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts +1 -1
- package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/TransformInterpolationAdapter.js +1 -1
- package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts +1 -1
- package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/Vector3InterpolationAdapter.js +1 -1
- package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -0
- package/src/engine/physics/PLAN.md +944 -809
- package/src/engine/physics/body/BodyStorage.d.ts +9 -0
- package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
- package/src/engine/physics/body/BodyStorage.js +23 -0
- package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
- package/src/engine/physics/broadphase/generate_pairs.js +7 -0
- package/src/engine/physics/ccd/linear_sweep.d.ts +97 -0
- package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -0
- package/src/engine/physics/ccd/linear_sweep.js +238 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +82 -3
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +227 -8
- package/src/engine/physics/ecs/RigidBodyFlags.d.ts +6 -0
- package/src/engine/physics/ecs/RigidBodyFlags.d.ts.map +1 -1
- package/src/engine/physics/ecs/RigidBodyFlags.js +6 -0
- package/src/engine/physics/narrowphase/box_triangle_contact.js +814 -811
- package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/compute_penetration.js +325 -323
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +27 -8
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +235 -204
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +97 -13
- package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
- package/src/engine/physics/queries/overlap_shape.js +185 -183
- package/src/engine/simulation/Ticker.d.ts +14 -0
- package/src/engine/simulation/Ticker.d.ts.map +1 -1
- package/src/engine/simulation/Ticker.js +136 -1
- package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts.map +0 -1
- package/src/engine/network/sim/InterpolationLog.d.ts.map +0 -1
- /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.d.ts +0 -0
- /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.js +0 -0
|
@@ -133,6 +133,18 @@ const prev_claimed = new Uint8Array(MAX_CONTACTS_PER_MANIFOLD);
|
|
|
133
133
|
*/
|
|
134
134
|
const cand_to_prev = new Int32Array(MAX_KEPT);
|
|
135
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Per-candidate "already claimed" flags for {@link redetect_pair_geometry}'s
|
|
138
|
+
* 1:1 existing-contact → fresh-candidate matching. Sized to the candidate
|
|
139
|
+
* buffer capacity (64) so it covers any per-pair candidate count. Without it,
|
|
140
|
+
* several existing contacts that share one triangle's `feature_id` (the
|
|
141
|
+
* box/capsule-triangle paths emit multiple contacts per triangle) would all
|
|
142
|
+
* match the same first candidate, collapsing the manifold to duplicate witness
|
|
143
|
+
* points.
|
|
144
|
+
* @type {Uint8Array}
|
|
145
|
+
*/
|
|
146
|
+
const redetect_claimed = new Uint8Array(64);
|
|
147
|
+
|
|
136
148
|
/**
|
|
137
149
|
* Per body-pair scratch buffer for candidate contacts produced by the
|
|
138
150
|
* cross-product of A's colliders × B's colliders. Sized generously for
|
|
@@ -148,6 +160,13 @@ const candidates = new Float64Array(64 * CANDIDATE_STRIDE);
|
|
|
148
160
|
* the convex-side body, so a single concave-vs-convex pair typically
|
|
149
161
|
* yields tens of triangles, not thousands. 1024 is the safety cap.
|
|
150
162
|
*
|
|
163
|
+
* For a heightmap the per-cell triangle count scales O(N²) with the
|
|
164
|
+
* shape's {@link HeightMapShape3D#tessellation} (a sub-cell quad is 2
|
|
165
|
+
* triangles, and there are N×N sub-cells per sampler cell). The bounded
|
|
166
|
+
* query AABB keeps the cell count small, so a typical pair stays well
|
|
167
|
+
* inside 1024 at moderate tessellation; the silent-drop backstop below
|
|
168
|
+
* covers any overflow at extreme values.
|
|
169
|
+
*
|
|
151
170
|
* If an enumerator's output would exceed this, the extra triangles are
|
|
152
171
|
* silently dropped by the enumerator's bounds-check on the output
|
|
153
172
|
* array — the worst case is a missed contact on a far edge of the
|
|
@@ -1606,26 +1625,91 @@ export function redetect_pair_geometry(manifolds, slot, list_a, list_b) {
|
|
|
1606
1625
|
|
|
1607
1626
|
const data = manifolds.data_buffer;
|
|
1608
1627
|
const slot_off = manifolds.slot_data_offset(slot);
|
|
1628
|
+
|
|
1629
|
+
// Match each existing contact to a DISTINCT fresh candidate. feature_id
|
|
1630
|
+
// identifies the TRIANGLE, not the contact point — the box/capsule-triangle
|
|
1631
|
+
// paths emit several contacts for one triangle, all sharing that triangle's
|
|
1632
|
+
// single fid (a flat box-on-heightmap cell yields fids like [6,6,6,7]). A
|
|
1633
|
+
// plain first-match-by-fid therefore maps every same-fid existing contact
|
|
1634
|
+
// onto the SAME candidate, collapsing the manifold to duplicate witness
|
|
1635
|
+
// points → a degenerate support polygon the solver can't damp (the
|
|
1636
|
+
// box-on-heightmap rattle). So: gate by fid, disambiguate same-fid
|
|
1637
|
+
// candidates by NEAREST previous witness (world-A) position, and claim each
|
|
1638
|
+
// candidate so no two existing contacts take the same one. For the common
|
|
1639
|
+
// unique-fid case (sphere/mesh: one contact per triangle) this picks that
|
|
1640
|
+
// single candidate exactly once — identical to the old behaviour.
|
|
1641
|
+
for (let k = 0; k < cc; k++) redetect_claimed[k] = 0;
|
|
1642
|
+
|
|
1609
1643
|
for (let j = 0; j < count; j++) {
|
|
1610
1644
|
const off = slot_off + j * CONTACT_STRIDE;
|
|
1611
1645
|
const fid = data[off + 13];
|
|
1612
1646
|
if (fid === 0) continue; // no feature info to match on
|
|
1647
|
+
|
|
1648
|
+
// Previous witness (world-A) of this existing contact — the anchor we
|
|
1649
|
+
// disambiguate same-fid candidates against.
|
|
1650
|
+
const pax = data[off];
|
|
1651
|
+
const pay = data[off + 1];
|
|
1652
|
+
const paz = data[off + 2];
|
|
1653
|
+
|
|
1654
|
+
let best_k = -1;
|
|
1655
|
+
let best_d2 = Infinity;
|
|
1613
1656
|
for (let k = 0; k < cc; k++) {
|
|
1657
|
+
if (redetect_claimed[k] === 1) continue;
|
|
1614
1658
|
const co = k * CANDIDATE_STRIDE;
|
|
1615
|
-
if (candidates[co + 10]
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
data[off + 6] = candidates[co + 6];
|
|
1624
|
-
data[off + 7] = candidates[co + 7];
|
|
1625
|
-
data[off + 8] = candidates[co + 8];
|
|
1626
|
-
data[off + 9] = candidates[co + 9];
|
|
1627
|
-
break;
|
|
1659
|
+
if (candidates[co + 10] !== fid) continue;
|
|
1660
|
+
const dx = candidates[co] - pax;
|
|
1661
|
+
const dy = candidates[co + 1] - pay;
|
|
1662
|
+
const dz = candidates[co + 2] - paz;
|
|
1663
|
+
const d2 = dx * dx + dy * dy + dz * dz;
|
|
1664
|
+
if (d2 < best_d2) {
|
|
1665
|
+
best_d2 = d2;
|
|
1666
|
+
best_k = k;
|
|
1628
1667
|
}
|
|
1629
1668
|
}
|
|
1669
|
+
|
|
1670
|
+
// Position fallback (mirrors narrowphase_step's match-and-merge). A
|
|
1671
|
+
// single triangle's clipped contact count is NOT stable across sub-mm
|
|
1672
|
+
// pose changes — a box straddling a cell seam can have one triangle
|
|
1673
|
+
// yield 4 points one substep and 3 the next — so an existing contact can
|
|
1674
|
+
// outnumber this substep's same-fid candidates. Freezing its stale
|
|
1675
|
+
// witness then duplicates a sibling contact's point, leaving a
|
|
1676
|
+
// degenerate (sub-dimensional) manifold. Instead, claim the nearest
|
|
1677
|
+
// unclaimed candidate of ANY fid: every contact keeps a DISTINCT live
|
|
1678
|
+
// witness. The fid label is left intact and re-resolved by the next
|
|
1679
|
+
// once-per-step narrowphase. (At a fixed pose the same-fid match always
|
|
1680
|
+
// succeeds, so this never fires for the depth-equality guards or the
|
|
1681
|
+
// one-contact-per-triangle sphere/mesh paths.)
|
|
1682
|
+
if (best_k === -1) {
|
|
1683
|
+
for (let k = 0; k < cc; k++) {
|
|
1684
|
+
if (redetect_claimed[k] === 1) continue;
|
|
1685
|
+
const co = k * CANDIDATE_STRIDE;
|
|
1686
|
+
const dx = candidates[co] - pax;
|
|
1687
|
+
const dy = candidates[co + 1] - pay;
|
|
1688
|
+
const dz = candidates[co + 2] - paz;
|
|
1689
|
+
const d2 = dx * dx + dy * dy + dz * dz;
|
|
1690
|
+
if (d2 < best_d2) {
|
|
1691
|
+
best_d2 = d2;
|
|
1692
|
+
best_k = k;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
if (best_k === -1) continue; // no unclaimed candidate at all this substep — keep frozen geometry
|
|
1698
|
+
|
|
1699
|
+
redetect_claimed[best_k] = 1;
|
|
1700
|
+
const co = best_k * CANDIDATE_STRIDE;
|
|
1701
|
+
// Overwrite geometry only: witnesses, normal, depth. (Count, feature
|
|
1702
|
+
// ids and accumulated impulses are intentionally left untouched — see
|
|
1703
|
+
// the function contract.)
|
|
1704
|
+
data[off] = candidates[co];
|
|
1705
|
+
data[off + 1] = candidates[co + 1];
|
|
1706
|
+
data[off + 2] = candidates[co + 2];
|
|
1707
|
+
data[off + 3] = candidates[co + 3];
|
|
1708
|
+
data[off + 4] = candidates[co + 4];
|
|
1709
|
+
data[off + 5] = candidates[co + 5];
|
|
1710
|
+
data[off + 6] = candidates[co + 6];
|
|
1711
|
+
data[off + 7] = candidates[co + 7];
|
|
1712
|
+
data[off + 8] = candidates[co + 8];
|
|
1713
|
+
data[off + 9] = candidates[co + 9];
|
|
1630
1714
|
}
|
|
1631
1715
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlap_shape.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/queries/overlap_shape.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"overlap_shape.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/queries/overlap_shape.js"],"names":[],"mappings":"AA8CA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,uFAZW;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,YAE5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,UAErC,WAAW,GAAC,MAAM,EAAE,iBAEpB,MAAM,oBACE,MAAM,yBAAsB,OAAO,GAEzC,MAAM,CAqGlB"}
|
|
@@ -1,183 +1,185 @@
|
|
|
1
|
-
import { bvh_query_user_data_overlaps_aabb } from "../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js";
|
|
2
|
-
import { returnTrue } from "../../../core/function/returnTrue.js";
|
|
3
|
-
import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
|
|
4
|
-
import { Triangle3D } from "../../../core/geom/3d/shape/Triangle3D.js";
|
|
5
|
-
import { body_id_index } from "../body/BodyStorage.js";
|
|
6
|
-
import { gjk } from "../gjk/gjk.js";
|
|
7
|
-
import { aabb_world_to_local } from "../narrowphase/decomposition/aabb_world_to_local.js";
|
|
8
|
-
import { decompose_to_triangles } from "../narrowphase/decomposition/decompose_to_triangles.js";
|
|
9
|
-
import { TRIANGLE_FLOAT_STRIDE } from "../narrowphase/decomposition/triangle_buffer_layout.js";
|
|
10
|
-
import { PosedShape } from "../narrowphase/PosedShape.js";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Scratch state — module-scoped to avoid per-query allocation. Safe
|
|
14
|
-
* because PhysicsSystem queries run on the main thread, sequentially.
|
|
15
|
-
*/
|
|
16
|
-
const local_aabb = new Float64Array(6);
|
|
17
|
-
const world_aabb = new Float64Array(6);
|
|
18
|
-
const concave_query_aabb = new Float64Array(6);
|
|
19
|
-
const simplex_buf = new Float64Array(12);
|
|
20
|
-
|
|
21
|
-
const query_posed = new PosedShape();
|
|
22
|
-
const candidate_posed = new PosedShape();
|
|
23
|
-
const triangle_shape = new Triangle3D();
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Maximum triangles a concave candidate can emit per overlap pair.
|
|
27
|
-
* Same rationale as the narrowphase's `MAX_TRIANGLES_PER_PAIR`: the
|
|
28
|
-
* broadphase has already bounded the query AABB to the query shape's
|
|
29
|
-
* envelope, so a single candidate typically yields tens of triangles
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* @param {
|
|
74
|
-
*
|
|
75
|
-
* @param {{x:number,y:number,z:number
|
|
76
|
-
*
|
|
77
|
-
* @param {
|
|
78
|
-
*
|
|
79
|
-
* @param {number}
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* @
|
|
83
|
-
* @
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
candidate_posed.
|
|
160
|
-
candidate_posed.
|
|
161
|
-
candidate_posed.
|
|
162
|
-
candidate_posed.
|
|
163
|
-
candidate_posed.
|
|
164
|
-
candidate_posed.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
1
|
+
import { bvh_query_user_data_overlaps_aabb } from "../../../core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js";
|
|
2
|
+
import { returnTrue } from "../../../core/function/returnTrue.js";
|
|
3
|
+
import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
|
|
4
|
+
import { Triangle3D } from "../../../core/geom/3d/shape/Triangle3D.js";
|
|
5
|
+
import { body_id_index } from "../body/BodyStorage.js";
|
|
6
|
+
import { gjk } from "../gjk/gjk.js";
|
|
7
|
+
import { aabb_world_to_local } from "../narrowphase/decomposition/aabb_world_to_local.js";
|
|
8
|
+
import { decompose_to_triangles } from "../narrowphase/decomposition/decompose_to_triangles.js";
|
|
9
|
+
import { TRIANGLE_FLOAT_STRIDE } from "../narrowphase/decomposition/triangle_buffer_layout.js";
|
|
10
|
+
import { PosedShape } from "../narrowphase/PosedShape.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Scratch state — module-scoped to avoid per-query allocation. Safe
|
|
14
|
+
* because PhysicsSystem queries run on the main thread, sequentially.
|
|
15
|
+
*/
|
|
16
|
+
const local_aabb = new Float64Array(6);
|
|
17
|
+
const world_aabb = new Float64Array(6);
|
|
18
|
+
const concave_query_aabb = new Float64Array(6);
|
|
19
|
+
const simplex_buf = new Float64Array(12);
|
|
20
|
+
|
|
21
|
+
const query_posed = new PosedShape();
|
|
22
|
+
const candidate_posed = new PosedShape();
|
|
23
|
+
const triangle_shape = new Triangle3D();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maximum triangles a concave candidate can emit per overlap pair.
|
|
27
|
+
* Same rationale as the narrowphase's `MAX_TRIANGLES_PER_PAIR`: the
|
|
28
|
+
* broadphase has already bounded the query AABB to the query shape's
|
|
29
|
+
* envelope, so a single candidate typically yields tens of triangles
|
|
30
|
+
* (a heightmap's count scales O(N²) with its `tessellation`, still
|
|
31
|
+
* inside the buffer for a bounded query at moderate tessellation).
|
|
32
|
+
* Excess triangles are dropped by the enumerator's bounds check —
|
|
33
|
+
* worst case is a missed overlap on a far edge of the candidate's
|
|
34
|
+
* geometry, recovered next query.
|
|
35
|
+
* @type {number}
|
|
36
|
+
*/
|
|
37
|
+
const MAX_TRIANGLES_PER_PAIR = 1024;
|
|
38
|
+
|
|
39
|
+
const triangle_buffer = new Float64Array(MAX_TRIANGLES_PER_PAIR * TRIANGLE_FLOAT_STRIDE);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Broadphase candidate buffer. Grows by doubling on overflow.
|
|
43
|
+
* @type {Uint32Array}
|
|
44
|
+
*/
|
|
45
|
+
let scratch_candidates = new Uint32Array(64);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Test what bodies overlap a convex shape placed at a given pose. Each
|
|
49
|
+
* overlapping body's `body_id` is written to `output` starting at
|
|
50
|
+
* `output_offset`; the function returns the number of body ids written.
|
|
51
|
+
*
|
|
52
|
+
* Use case: speculative physics queries for kinematic / character
|
|
53
|
+
* controllers. An external system can ask "would my body collide with
|
|
54
|
+
* anything if I moved it here?" without committing a tick of
|
|
55
|
+
* simulation. The output is a flat list of body ids so the caller can
|
|
56
|
+
* decide what to do per hit (skip, push, slide, etc.).
|
|
57
|
+
*
|
|
58
|
+
* The pipeline mirrors the narrowphase pair test:
|
|
59
|
+
* 1. Build the query shape's world AABB.
|
|
60
|
+
* 2. Pull candidates from both broadphase trees that overlap that AABB.
|
|
61
|
+
* 3. For each candidate, run GJK in world frame. Convex candidates
|
|
62
|
+
* go through one GJK call; concave candidates (heightmap / mesh)
|
|
63
|
+
* go through the per-triangle decomposition path.
|
|
64
|
+
* 4. Apply the optional `filter` callback (same signature as in
|
|
65
|
+
* raycast / shapeCast) before the GJK test — early-out on bodies
|
|
66
|
+
* the caller already wants to skip (themselves, allies, etc.).
|
|
67
|
+
*
|
|
68
|
+
* The query shape must be convex (`is_convex === true`). Concave shapes
|
|
69
|
+
* are typically static terrain and not used as kinematic query
|
|
70
|
+
* probes; rejecting them avoids the M×N triangle-pair cost.
|
|
71
|
+
*
|
|
72
|
+
* @param {PhysicsSystem} system
|
|
73
|
+
* @param {AbstractShape3D} shape query shape, convex; expressed in
|
|
74
|
+
* its own local frame
|
|
75
|
+
* @param {{x:number,y:number,z:number}} position world position of the
|
|
76
|
+
* query shape
|
|
77
|
+
* @param {{x:number,y:number,z:number,w:number}} rotation world rotation
|
|
78
|
+
* of the query shape (unit quaternion)
|
|
79
|
+
* @param {Uint32Array|number[]} output buffer to write body_ids into.
|
|
80
|
+
* Caller is responsible for sizing it; ids past its end are dropped.
|
|
81
|
+
* @param {number} output_offset float-index in output to start writing at
|
|
82
|
+
* @param {(entity:number, collider:Collider)=>boolean} [filter]
|
|
83
|
+
* defaults to {@link returnTrue} (accept every candidate)
|
|
84
|
+
* @returns {number} number of overlapping bodies written
|
|
85
|
+
* @throws {Error} if `shape.is_convex === false`
|
|
86
|
+
*/
|
|
87
|
+
export function overlap_shape(system, shape, position, rotation, output, output_offset, filter = returnTrue) {
|
|
88
|
+
if (shape.is_convex === false) {
|
|
89
|
+
throw new Error(`overlap_shape: query shape must be convex; received \`${shape.constructor.name}\` (is_convex=false)`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── 1. Query shape's world AABB ─────────────────────────────────
|
|
93
|
+
shape.compute_bounding_box(local_aabb);
|
|
94
|
+
aabb3_transform_oriented(
|
|
95
|
+
world_aabb, 0,
|
|
96
|
+
local_aabb[0], local_aabb[1], local_aabb[2],
|
|
97
|
+
local_aabb[3], local_aabb[4], local_aabb[5],
|
|
98
|
+
position.x, position.y, position.z,
|
|
99
|
+
rotation.x, rotation.y, rotation.z, rotation.w
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// ── 2. Gather broadphase candidates ─────────────────────────────
|
|
103
|
+
const n_static = bvh_query_user_data_overlaps_aabb(
|
|
104
|
+
scratch_candidates, 0, system.staticBvh, world_aabb
|
|
105
|
+
);
|
|
106
|
+
const n_dynamic = bvh_query_user_data_overlaps_aabb(
|
|
107
|
+
scratch_candidates, n_static, system.dynamicBvh, world_aabb
|
|
108
|
+
);
|
|
109
|
+
const n_total = n_static + n_dynamic;
|
|
110
|
+
if (n_total === 0) return 0;
|
|
111
|
+
|
|
112
|
+
// ── 3. Set up query PosedShape (constant across candidates) ─────
|
|
113
|
+
query_posed.setup(shape, position, rotation);
|
|
114
|
+
|
|
115
|
+
// ── 4. Per-candidate narrowphase ────────────────────────────────
|
|
116
|
+
const output_capacity = output.length - output_offset;
|
|
117
|
+
let count = 0;
|
|
118
|
+
let cursor = output_offset;
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < n_total; i++) {
|
|
121
|
+
if (count >= output_capacity) break;
|
|
122
|
+
|
|
123
|
+
const body_id = scratch_candidates[i];
|
|
124
|
+
const body_idx = body_id_index(body_id);
|
|
125
|
+
|
|
126
|
+
const entity = system.entityOf(body_id);
|
|
127
|
+
if (entity < 0) continue;
|
|
128
|
+
|
|
129
|
+
const collider = system.__primary_collider(body_idx);
|
|
130
|
+
if (collider === null) continue;
|
|
131
|
+
if (!filter(entity, collider)) continue;
|
|
132
|
+
|
|
133
|
+
const candidate_tr = system.__transforms[body_idx];
|
|
134
|
+
|
|
135
|
+
let overlaps = false;
|
|
136
|
+
|
|
137
|
+
if (collider.shape.is_convex !== false) {
|
|
138
|
+
candidate_posed.setup(collider.shape, candidate_tr.position, candidate_tr.rotation);
|
|
139
|
+
overlaps = gjk(simplex_buf, query_posed, candidate_posed);
|
|
140
|
+
} else {
|
|
141
|
+
// Concave candidate: project the query's world AABB into
|
|
142
|
+
// the candidate's body-local frame, decompose to triangles,
|
|
143
|
+
// run per-triangle GJK until one overlap is found.
|
|
144
|
+
aabb_world_to_local(
|
|
145
|
+
concave_query_aabb, 0,
|
|
146
|
+
world_aabb,
|
|
147
|
+
candidate_tr.position.x, candidate_tr.position.y, candidate_tr.position.z,
|
|
148
|
+
candidate_tr.rotation.x, candidate_tr.rotation.y, candidate_tr.rotation.z, candidate_tr.rotation.w
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const tri_count = decompose_to_triangles(
|
|
152
|
+
triangle_buffer, 0, collider.shape,
|
|
153
|
+
concave_query_aabb[0], concave_query_aabb[1], concave_query_aabb[2],
|
|
154
|
+
concave_query_aabb[3], concave_query_aabb[4], concave_query_aabb[5]
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Re-pose candidate as the concave body, rebinding the
|
|
158
|
+
// flyweight triangle per iteration.
|
|
159
|
+
candidate_posed.shape = triangle_shape;
|
|
160
|
+
candidate_posed.px = candidate_tr.position.x;
|
|
161
|
+
candidate_posed.py = candidate_tr.position.y;
|
|
162
|
+
candidate_posed.pz = candidate_tr.position.z;
|
|
163
|
+
candidate_posed.qx = candidate_tr.rotation.x;
|
|
164
|
+
candidate_posed.qy = candidate_tr.rotation.y;
|
|
165
|
+
candidate_posed.qz = candidate_tr.rotation.z;
|
|
166
|
+
candidate_posed.qw = candidate_tr.rotation.w;
|
|
167
|
+
|
|
168
|
+
for (let t = 0; t < tri_count; t++) {
|
|
169
|
+
triangle_shape.bind(triangle_buffer, t * TRIANGLE_FLOAT_STRIDE);
|
|
170
|
+
if (gjk(simplex_buf, query_posed, candidate_posed)) {
|
|
171
|
+
overlaps = true;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (overlaps) {
|
|
178
|
+
output[cursor] = body_id;
|
|
179
|
+
cursor++;
|
|
180
|
+
count++;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return count;
|
|
185
|
+
}
|
|
@@ -20,6 +20,20 @@ declare class Ticker {
|
|
|
20
20
|
* @type {Clock}
|
|
21
21
|
*/
|
|
22
22
|
readonly clock: Clock;
|
|
23
|
+
/**
|
|
24
|
+
* When `true`, ticking is suspended while the host document/tab is in the
|
|
25
|
+
* background (hidden, or stored in the back/forward cache) and resumes from
|
|
26
|
+
* the current moment once it returns to the foreground.
|
|
27
|
+
*
|
|
28
|
+
* This avoids dispatching a flood of catch-up ticks (or one huge delta) for
|
|
29
|
+
* the time spent suspended, which would otherwise destabilise the simulation.
|
|
30
|
+
*
|
|
31
|
+
* Has no effect in environments without document/visibility support (e.g.
|
|
32
|
+
* Node.js), where the relevant lifecycle events simply never fire.
|
|
33
|
+
*
|
|
34
|
+
* @type {boolean}
|
|
35
|
+
*/
|
|
36
|
+
suspend_in_background: boolean;
|
|
23
37
|
/**
|
|
24
38
|
* Dispatches time delta in seconds since last tick
|
|
25
39
|
* @readonly
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Ticker.d.ts","sourceRoot":"","sources":["../../../../src/engine/simulation/Ticker.js"],"names":[],"mappings":";AAIA;;;;;;;;;;;;;;GAcG;AACH;IACI;;;OAGG;IACH,gBAFU,KAAK,CAEK;
|
|
1
|
+
{"version":3,"file":"Ticker.d.ts","sourceRoot":"","sources":["../../../../src/engine/simulation/Ticker.js"],"names":[],"mappings":";AAIA;;;;;;;;;;;;;;GAcG;AACH;IACI;;;OAGG;IACH,gBAFU,KAAK,CAEK;IAEpB;;;;;;;;;;;;OAYG;IACH,uBAFU,OAAO,CAEY;IAwB7B;;;;OAIG;IACH,iBAFU,OAAO,MAAM,CAAC,CAEF;IA+GtB;;;OAGG;IACH,uBAFW,MAAM,QAyDhB;IAED,cAOC;IAED,eAQC;IAED,aAUC;;CACJ;kBA5QiB,aAAa;mBADZ,oCAAoC"}
|