@woosh/meep-engine 2.144.0 → 2.146.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/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/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 +365 -302
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +1 -3
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +12 -2
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +7 -2
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +13 -0
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +16 -2
- package/src/engine/control/first-person/TODO.md +13 -11
- 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 +30 -35
- package/src/engine/control/first-person/collision/KinematicMover.d.ts +35 -5
- package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -1
- package/src/engine/control/first-person/collision/KinematicMover.js +634 -424
- package/src/engine/control/first-person/prototype_first_person_controller.js +1003 -901
- package/src/engine/physics/PLAN.md +943 -767
- 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 +18 -3
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +59 -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 +811 -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 +70 -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
|
@@ -8,10 +8,25 @@
|
|
|
8
8
|
* [+6..+8] vC.xyz
|
|
9
9
|
* [+9] feature_id (stable across frames; warm-start key)
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* ## Tessellation
|
|
12
|
+
*
|
|
13
|
+
* Each sampler cell is split into `N × N` sub-cells, where `N =
|
|
14
|
+
* shape.tessellation` (a non-negative integer, validated at construction).
|
|
15
|
+
* The sub-cell corners are sampled with the same Catmull-Rom filter the
|
|
16
|
+
* surface uses everywhere else, so finer `N` makes the faceted collision
|
|
17
|
+
* surface converge toward the smooth surface the renderer draws (which
|
|
18
|
+
* samples the identical cubic, just at its own `size × resolution` density).
|
|
19
|
+
* `N = 1` emits exactly one quad per sampler cell — the legacy behaviour;
|
|
20
|
+
* `N = 0` emits nothing. Cost is O(N²) per cell, so the caller owns the
|
|
21
|
+
* fidelity/cost trade-off.
|
|
22
|
+
*
|
|
23
|
+
* The collision grid therefore has `seg_u = (W − 1)·N` segments along u and
|
|
24
|
+
* `seg_v = (H − 1)·N` along v. The feature_id encodes
|
|
25
|
+
* `(sub_y · seg_u + sub_x) · 2 + tri_idx`, where (sub_x, sub_y) is the
|
|
26
|
+
* sub-cell and tri_idx ∈ {0, 1}: a unique, frame-stable key per sub-cell
|
|
27
|
+
* triangle (`seg_u · seg_v · 2` stays well under 2^53 for any realistic
|
|
28
|
+
* sampler × N). Two triangles per sub-cell, lower-left-diagonal split
|
|
29
|
+
* (matches the existing {@link build_height_field_geometry} winding).
|
|
15
30
|
*
|
|
16
31
|
* The query AABB is filtered against the footprint *and* against the
|
|
17
32
|
* cell grid; triangles outside the query AABB along the height axis are
|
|
@@ -21,11 +36,15 @@
|
|
|
21
36
|
* Bullet's btHeightfieldTerrainShape does.
|
|
22
37
|
*
|
|
23
38
|
* The caller is responsible for ensuring
|
|
24
|
-
* `output.length - offset >=
|
|
39
|
+
* `output.length - offset >= sub_cells_in_query * 2 * TRIANGLE_FLOAT_STRIDE`
|
|
25
40
|
* — sizing the scratch buffer for the *worst-case* query AABB is the
|
|
26
|
-
* right pattern
|
|
27
|
-
* world envelope, so the cell count is bounded by the footprint
|
|
28
|
-
* resolution)
|
|
41
|
+
* right pattern. The broadphase already bounded the AABB to one body's
|
|
42
|
+
* world envelope, so the sampler-cell count is bounded by the footprint
|
|
43
|
+
* resolution; note the sub-cell count (and thus the buffer requirement)
|
|
44
|
+
* scales by N² with {@link HeightMapShape3D#tessellation}. Writes past the
|
|
45
|
+
* end of `output` are silently dropped by the typed array, so an
|
|
46
|
+
* undersized buffer degrades to a missed contact on a far cell (recovered
|
|
47
|
+
* next broadphase), never a crash.
|
|
29
48
|
*
|
|
30
49
|
* @param {Float64Array} output
|
|
31
50
|
* @param {number} offset float-index into output
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heightmap_enumerate_triangles.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"heightmap_enumerate_triangles.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,sDAXW,YAAY,UACZ,MAAM,uCAEN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,cACN,MAAM,GACJ,MAAM,CA+KlB"}
|
|
@@ -1,204 +1,235 @@
|
|
|
1
|
-
import { TRIANGLE_FEATURE_ID_OFFSET, TRIANGLE_FLOAT_STRIDE } from "./triangle_buffer_layout.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Write the heightmap's triangles that may overlap the given body-local
|
|
5
|
-
* query AABB into `output`, starting at float-index `offset`.
|
|
6
|
-
*
|
|
7
|
-
* Each triangle occupies {@link TRIANGLE_FLOAT_STRIDE} consecutive floats:
|
|
8
|
-
* [+0..+2] vA.xyz (body-local frame)
|
|
9
|
-
* [+3..+5] vB.xyz
|
|
10
|
-
* [+6..+8] vC.xyz
|
|
11
|
-
* [+9] feature_id (stable across frames; warm-start key)
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* The
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// ──
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
let
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
1
|
+
import { TRIANGLE_FEATURE_ID_OFFSET, TRIANGLE_FLOAT_STRIDE } from "./triangle_buffer_layout.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Write the heightmap's triangles that may overlap the given body-local
|
|
5
|
+
* query AABB into `output`, starting at float-index `offset`.
|
|
6
|
+
*
|
|
7
|
+
* Each triangle occupies {@link TRIANGLE_FLOAT_STRIDE} consecutive floats:
|
|
8
|
+
* [+0..+2] vA.xyz (body-local frame)
|
|
9
|
+
* [+3..+5] vB.xyz
|
|
10
|
+
* [+6..+8] vC.xyz
|
|
11
|
+
* [+9] feature_id (stable across frames; warm-start key)
|
|
12
|
+
*
|
|
13
|
+
* ## Tessellation
|
|
14
|
+
*
|
|
15
|
+
* Each sampler cell is split into `N × N` sub-cells, where `N =
|
|
16
|
+
* shape.tessellation` (a non-negative integer, validated at construction).
|
|
17
|
+
* The sub-cell corners are sampled with the same Catmull-Rom filter the
|
|
18
|
+
* surface uses everywhere else, so finer `N` makes the faceted collision
|
|
19
|
+
* surface converge toward the smooth surface the renderer draws (which
|
|
20
|
+
* samples the identical cubic, just at its own `size × resolution` density).
|
|
21
|
+
* `N = 1` emits exactly one quad per sampler cell — the legacy behaviour;
|
|
22
|
+
* `N = 0` emits nothing. Cost is O(N²) per cell, so the caller owns the
|
|
23
|
+
* fidelity/cost trade-off.
|
|
24
|
+
*
|
|
25
|
+
* The collision grid therefore has `seg_u = (W − 1)·N` segments along u and
|
|
26
|
+
* `seg_v = (H − 1)·N` along v. The feature_id encodes
|
|
27
|
+
* `(sub_y · seg_u + sub_x) · 2 + tri_idx`, where (sub_x, sub_y) is the
|
|
28
|
+
* sub-cell and tri_idx ∈ {0, 1}: a unique, frame-stable key per sub-cell
|
|
29
|
+
* triangle (`seg_u · seg_v · 2` stays well under 2^53 for any realistic
|
|
30
|
+
* sampler × N). Two triangles per sub-cell, lower-left-diagonal split
|
|
31
|
+
* (matches the existing {@link build_height_field_geometry} winding).
|
|
32
|
+
*
|
|
33
|
+
* The query AABB is filtered against the footprint *and* against the
|
|
34
|
+
* cell grid; triangles outside the query AABB along the height axis are
|
|
35
|
+
* NOT filtered out — that test is left to the per-triangle narrowphase
|
|
36
|
+
* (where the precise GJK distance is computed anyway). This keeps the
|
|
37
|
+
* enumerator branch-free over the height field and aligned with what
|
|
38
|
+
* Bullet's btHeightfieldTerrainShape does.
|
|
39
|
+
*
|
|
40
|
+
* The caller is responsible for ensuring
|
|
41
|
+
* `output.length - offset >= sub_cells_in_query * 2 * TRIANGLE_FLOAT_STRIDE`
|
|
42
|
+
* — sizing the scratch buffer for the *worst-case* query AABB is the
|
|
43
|
+
* right pattern. The broadphase already bounded the AABB to one body's
|
|
44
|
+
* world envelope, so the sampler-cell count is bounded by the footprint
|
|
45
|
+
* resolution; note the sub-cell count (and thus the buffer requirement)
|
|
46
|
+
* scales by N² with {@link HeightMapShape3D#tessellation}. Writes past the
|
|
47
|
+
* end of `output` are silently dropped by the typed array, so an
|
|
48
|
+
* undersized buffer degrades to a missed contact on a far cell (recovered
|
|
49
|
+
* next broadphase), never a crash.
|
|
50
|
+
*
|
|
51
|
+
* @param {Float64Array} output
|
|
52
|
+
* @param {number} offset float-index into output
|
|
53
|
+
* @param {HeightMapShape3D} shape
|
|
54
|
+
* @param {number} aabb_min_x query AABB in shape's body-local frame
|
|
55
|
+
* @param {number} aabb_min_y
|
|
56
|
+
* @param {number} aabb_min_z
|
|
57
|
+
* @param {number} aabb_max_x
|
|
58
|
+
* @param {number} aabb_max_y
|
|
59
|
+
* @param {number} aabb_max_z
|
|
60
|
+
* @returns {number} number of triangles written
|
|
61
|
+
*/
|
|
62
|
+
export function heightmap_enumerate_triangles(
|
|
63
|
+
output, offset, shape,
|
|
64
|
+
aabb_min_x, aabb_min_y, aabb_min_z,
|
|
65
|
+
aabb_max_x, aabb_max_y, aabb_max_z
|
|
66
|
+
) {
|
|
67
|
+
const sampler = shape.sampler;
|
|
68
|
+
|
|
69
|
+
if (sampler === null) {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const W = sampler.width;
|
|
74
|
+
const H = sampler.height;
|
|
75
|
+
|
|
76
|
+
if (W < 2 || H < 2) {
|
|
77
|
+
// need at least one cell (one cell = 2 vertices per axis)
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const size_x = shape.size[0];
|
|
82
|
+
const size_z = shape.size[2];
|
|
83
|
+
|
|
84
|
+
if (size_x <= 0 || size_z <= 0) {
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Collision tessellation: split each sampler cell into N×N sub-cells so a
|
|
89
|
+
// coarse sampler's contact surface can approach render density. N is a
|
|
90
|
+
// non-negative integer (validated by HeightMapShape3D.from); N=1
|
|
91
|
+
// reproduces the legacy one-quad-per-sampler-cell grid exactly (seg_u =
|
|
92
|
+
// W-1, seg_v = H-1), and N=0 collapses the grid so the cell-range check
|
|
93
|
+
// below returns 0 — the degenerate empty case, handled without a branch.
|
|
94
|
+
const N = shape.tessellation;
|
|
95
|
+
|
|
96
|
+
const seg_u = (W - 1) * N;
|
|
97
|
+
const seg_v = (H - 1) * N;
|
|
98
|
+
|
|
99
|
+
// ── Project body-local AABB into heightmap-local (u, v, h) frame ─────
|
|
100
|
+
// Basis rows = (u_axis, v_axis, n_axis) in body-local frame. The
|
|
101
|
+
// body→heightmap rotation is therefore B (rows of basis), and the
|
|
102
|
+
// Arvo trick gives us the projected AABB extents.
|
|
103
|
+
|
|
104
|
+
shape._ensure_basis();
|
|
105
|
+
const b = shape._basis;
|
|
106
|
+
|
|
107
|
+
const c_x = 0.5 * (aabb_min_x + aabb_max_x);
|
|
108
|
+
const c_y = 0.5 * (aabb_min_y + aabb_max_y);
|
|
109
|
+
const c_z = 0.5 * (aabb_min_z + aabb_max_z);
|
|
110
|
+
|
|
111
|
+
const h_x = 0.5 * (aabb_max_x - aabb_min_x);
|
|
112
|
+
const h_y = 0.5 * (aabb_max_y - aabb_min_y);
|
|
113
|
+
const h_z = 0.5 * (aabb_max_z - aabb_min_z);
|
|
114
|
+
|
|
115
|
+
const u_c = b[0] * c_x + b[1] * c_y + b[2] * c_z;
|
|
116
|
+
const v_c = b[3] * c_x + b[4] * c_y + b[5] * c_z;
|
|
117
|
+
// height axis (b[6..8]) is intentionally not projected — see fn docstring
|
|
118
|
+
|
|
119
|
+
const u_e = Math.abs(b[0]) * h_x + Math.abs(b[1]) * h_y + Math.abs(b[2]) * h_z;
|
|
120
|
+
const v_e = Math.abs(b[3]) * h_x + Math.abs(b[4]) * h_y + Math.abs(b[5]) * h_z;
|
|
121
|
+
|
|
122
|
+
const half_u = size_x * 0.5;
|
|
123
|
+
const half_v = size_z * 0.5;
|
|
124
|
+
|
|
125
|
+
// ── Intersect with footprint, derive cell range ──────────────────────
|
|
126
|
+
|
|
127
|
+
const u_lo_q = u_c - u_e;
|
|
128
|
+
const u_hi_q = u_c + u_e;
|
|
129
|
+
const v_lo_q = v_c - v_e;
|
|
130
|
+
const v_hi_q = v_c + v_e;
|
|
131
|
+
|
|
132
|
+
if (u_lo_q > half_u || u_hi_q < -half_u) return 0;
|
|
133
|
+
if (v_lo_q > half_v || v_hi_q < -half_v) return 0;
|
|
134
|
+
|
|
135
|
+
const u_lo = u_lo_q > -half_u ? u_lo_q : -half_u;
|
|
136
|
+
const u_hi = u_hi_q < +half_u ? u_hi_q : +half_u;
|
|
137
|
+
const v_lo = v_lo_q > -half_v ? v_lo_q : -half_v;
|
|
138
|
+
const v_hi = v_hi_q < +half_v ? v_hi_q : +half_v;
|
|
139
|
+
|
|
140
|
+
const inv_cell_u = seg_u / size_x;
|
|
141
|
+
const inv_cell_v = seg_v / size_z;
|
|
142
|
+
|
|
143
|
+
let i_first = Math.floor((u_lo + half_u) * inv_cell_u);
|
|
144
|
+
let i_last = Math.ceil ((u_hi + half_u) * inv_cell_u) - 1;
|
|
145
|
+
let j_first = Math.floor((v_lo + half_v) * inv_cell_v);
|
|
146
|
+
let j_last = Math.ceil ((v_hi + half_v) * inv_cell_v) - 1;
|
|
147
|
+
|
|
148
|
+
if (i_first < 0) i_first = 0;
|
|
149
|
+
if (j_first < 0) j_first = 0;
|
|
150
|
+
if (i_last > seg_u - 1) i_last = seg_u - 1;
|
|
151
|
+
if (j_last > seg_v - 1) j_last = seg_v - 1;
|
|
152
|
+
|
|
153
|
+
if (i_first > i_last || j_first > j_last) return 0;
|
|
154
|
+
|
|
155
|
+
// ── Emit triangles ───────────────────────────────────────────────────
|
|
156
|
+
//
|
|
157
|
+
// For each sub-cell (i, j) on the seg_u × seg_v grid we sample 4 corner
|
|
158
|
+
// heights (Catmull-Rom, at the sub-cell UVs) and emit 2 triangles
|
|
159
|
+
// matching the build_height_field_geometry winding:
|
|
160
|
+
// A = (i, j ), B = (i+1, j )
|
|
161
|
+
// D = (i, j+1), C = (i+1, j+1)
|
|
162
|
+
// tri 0 = (A, D, B)
|
|
163
|
+
// tri 1 = (D, C, B)
|
|
164
|
+
//
|
|
165
|
+
// body_point = u_axis * u_coord + v_axis * v_coord + n_axis * h_coord
|
|
166
|
+
// where columns of the body-frame projection matrix are the basis rows
|
|
167
|
+
// of B (since B is orthonormal, body→heightmap and heightmap→body
|
|
168
|
+
// matrices are mutual transposes).
|
|
169
|
+
|
|
170
|
+
const ux = b[0]; const uy = b[1]; const uz = b[2];
|
|
171
|
+
const vx = b[3]; const vy = b[4]; const vz = b[5];
|
|
172
|
+
const nx = b[6]; const ny = b[7]; const nz = b[8];
|
|
173
|
+
|
|
174
|
+
const inv_segU = 1 / seg_u;
|
|
175
|
+
const inv_segV = 1 / seg_v;
|
|
176
|
+
|
|
177
|
+
let count = 0;
|
|
178
|
+
let cursor = offset;
|
|
179
|
+
|
|
180
|
+
for (let j = j_first; j <= j_last; j++) {
|
|
181
|
+
const v01_lo = j * inv_segV;
|
|
182
|
+
const v01_hi = (j + 1) * inv_segV;
|
|
183
|
+
const v_lo_coord = -half_v + size_z * v01_lo;
|
|
184
|
+
const v_hi_coord = -half_v + size_z * v01_hi;
|
|
185
|
+
|
|
186
|
+
for (let i = i_first; i <= i_last; i++) {
|
|
187
|
+
const u01_lo = i * inv_segU;
|
|
188
|
+
const u01_hi = (i + 1) * inv_segU;
|
|
189
|
+
const u_lo_coord = -half_u + size_x * u01_lo;
|
|
190
|
+
const u_hi_coord = -half_u + size_x * u01_hi;
|
|
191
|
+
|
|
192
|
+
const hA = sampler.sampleChannelCatmullRomUV(u01_lo, v01_lo, 0);
|
|
193
|
+
const hB = sampler.sampleChannelCatmullRomUV(u01_hi, v01_lo, 0);
|
|
194
|
+
const hC = sampler.sampleChannelCatmullRomUV(u01_hi, v01_hi, 0);
|
|
195
|
+
const hD = sampler.sampleChannelCatmullRomUV(u01_lo, v01_hi, 0);
|
|
196
|
+
|
|
197
|
+
const Ax = ux * u_lo_coord + vx * v_lo_coord + nx * hA;
|
|
198
|
+
const Ay = uy * u_lo_coord + vy * v_lo_coord + ny * hA;
|
|
199
|
+
const Az = uz * u_lo_coord + vz * v_lo_coord + nz * hA;
|
|
200
|
+
|
|
201
|
+
const Bx = ux * u_hi_coord + vx * v_lo_coord + nx * hB;
|
|
202
|
+
const By = uy * u_hi_coord + vy * v_lo_coord + ny * hB;
|
|
203
|
+
const Bz = uz * u_hi_coord + vz * v_lo_coord + nz * hB;
|
|
204
|
+
|
|
205
|
+
const Cx = ux * u_hi_coord + vx * v_hi_coord + nx * hC;
|
|
206
|
+
const Cy = uy * u_hi_coord + vy * v_hi_coord + ny * hC;
|
|
207
|
+
const Cz = uz * u_hi_coord + vz * v_hi_coord + nz * hC;
|
|
208
|
+
|
|
209
|
+
const Dx = ux * u_lo_coord + vx * v_hi_coord + nx * hD;
|
|
210
|
+
const Dy = uy * u_lo_coord + vy * v_hi_coord + ny * hD;
|
|
211
|
+
const Dz = uz * u_lo_coord + vz * v_hi_coord + nz * hD;
|
|
212
|
+
|
|
213
|
+
const cell_idx = j * seg_u + i;
|
|
214
|
+
const fid_base = cell_idx * 2;
|
|
215
|
+
|
|
216
|
+
// triangle 0: (A, D, B)
|
|
217
|
+
output[cursor ] = Ax; output[cursor + 1] = Ay; output[cursor + 2] = Az;
|
|
218
|
+
output[cursor + 3] = Dx; output[cursor + 4] = Dy; output[cursor + 5] = Dz;
|
|
219
|
+
output[cursor + 6] = Bx; output[cursor + 7] = By; output[cursor + 8] = Bz;
|
|
220
|
+
output[cursor + TRIANGLE_FEATURE_ID_OFFSET] = fid_base;
|
|
221
|
+
cursor += TRIANGLE_FLOAT_STRIDE;
|
|
222
|
+
count++;
|
|
223
|
+
|
|
224
|
+
// triangle 1: (D, C, B)
|
|
225
|
+
output[cursor ] = Dx; output[cursor + 1] = Dy; output[cursor + 2] = Dz;
|
|
226
|
+
output[cursor + 3] = Cx; output[cursor + 4] = Cy; output[cursor + 5] = Cz;
|
|
227
|
+
output[cursor + 6] = Bx; output[cursor + 7] = By; output[cursor + 8] = Bz;
|
|
228
|
+
output[cursor + TRIANGLE_FEATURE_ID_OFFSET] = fid_base + 1;
|
|
229
|
+
cursor += TRIANGLE_FLOAT_STRIDE;
|
|
230
|
+
count++;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return count;
|
|
235
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"narrowphase_step.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/narrowphase_step.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"narrowphase_step.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/narrowphase_step.js"],"names":[],"mappings":"AA2zCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qDAVW,YAAY,GAAC,MAAM,EAAE,iCAGrB;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,QAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,iCAErC;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,QAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,GACnC,MAAM,CAyClB;AAED;;;;;;;;;;;GAWG;AACH,uFALW,MAAM,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,CAAC,QA0JlE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,uEAJW,MAAM,UACN,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,UACjD,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,QAuF3D"}
|
|
@@ -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,64 @@ 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
|
+
if (best_k === -1) continue; // no unclaimed same-fid candidate this substep — keep frozen geometry
|
|
1671
|
+
|
|
1672
|
+
redetect_claimed[best_k] = 1;
|
|
1673
|
+
const co = best_k * CANDIDATE_STRIDE;
|
|
1674
|
+
// Overwrite geometry only: witnesses, normal, depth. (Count, feature
|
|
1675
|
+
// ids and accumulated impulses are intentionally left untouched — see
|
|
1676
|
+
// the function contract.)
|
|
1677
|
+
data[off] = candidates[co];
|
|
1678
|
+
data[off + 1] = candidates[co + 1];
|
|
1679
|
+
data[off + 2] = candidates[co + 2];
|
|
1680
|
+
data[off + 3] = candidates[co + 3];
|
|
1681
|
+
data[off + 4] = candidates[co + 4];
|
|
1682
|
+
data[off + 5] = candidates[co + 5];
|
|
1683
|
+
data[off + 6] = candidates[co + 6];
|
|
1684
|
+
data[off + 7] = candidates[co + 7];
|
|
1685
|
+
data[off + 8] = candidates[co + 8];
|
|
1686
|
+
data[off + 9] = candidates[co + 9];
|
|
1630
1687
|
}
|
|
1631
1688
|
}
|