@woosh/meep-engine 2.153.0 → 2.154.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/ConvexHullShape3D.d.ts +112 -0
- package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/ConvexHullShape3D.js +325 -0
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts +4 -0
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2D.js +21 -0
- package/src/engine/physics/PLAN.md +4 -4
- package/src/engine/physics/body/BodyStorage.d.ts +3 -1
- package/src/engine/physics/body/BodyStorage.d.ts.map +1 -1
- package/src/engine/physics/body/BodyStorage.js +452 -450
- package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -1
- package/src/engine/physics/body/SolverBodyState.js +6 -5
- package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
- package/src/engine/physics/broadphase/generate_pairs.js +9 -1
- package/src/engine/physics/ccd/linear_sweep.d.ts.map +1 -1
- package/src/engine/physics/ccd/linear_sweep.js +237 -238
- package/src/engine/physics/computeInterceptPoint.d.ts.map +1 -1
- package/src/engine/physics/computeInterceptPoint.js +8 -3
- package/src/engine/physics/contact/ManifoldStore.d.ts +0 -16
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +1 -38
- package/src/engine/physics/ecs/BodyKind.d.ts +3 -2
- package/src/engine/physics/ecs/BodyKind.d.ts.map +1 -1
- package/src/engine/physics/ecs/BodyKind.js +25 -24
- package/src/engine/physics/ecs/PhysicsEvents.d.ts +4 -5
- package/src/engine/physics/ecs/PhysicsEvents.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsEvents.js +15 -16
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +5 -30
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +13 -45
- package/src/engine/physics/ecs/RigidBodySerializationAdapter.d.ts.map +1 -1
- package/src/engine/physics/ecs/RigidBodySerializationAdapter.js +85 -81
- package/src/engine/physics/ecs/is_sensor.d.ts +18 -0
- package/src/engine/physics/ecs/is_sensor.d.ts.map +1 -0
- package/src/engine/physics/ecs/is_sensor.js +27 -0
- package/src/engine/physics/events/ContactEventBuffer.d.ts +2 -1
- package/src/engine/physics/events/ContactEventBuffer.d.ts.map +1 -1
- package/src/engine/physics/events/ContactEventBuffer.js +84 -83
- package/src/engine/physics/gjk/gjk.d.ts +0 -26
- package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
- package/src/engine/physics/gjk/gjk.js +3 -52
- package/src/engine/physics/gjk/gjk_epa_penetration.d.ts +16 -0
- package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +1 -0
- package/src/engine/physics/gjk/gjk_epa_penetration.js +255 -0
- package/src/engine/physics/gjk/minkowski_support.d.ts +4 -9
- package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -1
- package/src/engine/physics/gjk/minkowski_support.js +70 -75
- package/src/engine/physics/gjk/mpr.d.ts +1 -1
- package/src/engine/physics/gjk/mpr.d.ts.map +1 -1
- package/src/engine/physics/gjk/mpr.js +362 -344
- package/src/engine/physics/island/IslandBuilder.d.ts.map +1 -1
- package/src/engine/physics/island/IslandBuilder.js +431 -428
- package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_box_manifold.js +4 -81
- package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_triangle_contact.js +4 -39
- package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/capsule_contacts.js +459 -462
- package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/clip_against_axis_uv.js +4 -1
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts +83 -0
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/convex_convex_manifold.js +425 -0
- package/src/engine/physics/narrowphase/convex_decomposition.d.ts +32 -0
- package/src/engine/physics/narrowphase/convex_decomposition.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/convex_decomposition.js +293 -0
- package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts +41 -0
- package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/mesh_convex_hull.js +106 -0
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.d.ts +8 -0
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +105 -102
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts +29 -0
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +69 -0
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_concave.js +152 -145
- package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/sphere_box_contact.js +132 -123
- package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -1
- package/src/engine/physics/queries/overlap_shape.js +16 -17
- package/src/engine/physics/queries/raycast.d.ts +5 -0
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +16 -8
- package/src/engine/physics/queries/shape_cast.d.ts.map +1 -1
- package/src/engine/physics/queries/shape_cast.js +13 -7
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +8 -11
- package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -1
- package/src/engine/physics/vehicle/RaycastVehicle.js +339 -333
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +0 -13
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +0 -1
- package/src/engine/physics/gjk/expanding_polytope_algorithm.js +0 -399
|
@@ -1,344 +1,362 @@
|
|
|
1
|
-
import { v3_length } from "../../../core/geom/vec3/v3_length.js";
|
|
2
|
-
import { minkowski_support } from "./minkowski_support.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Maximum portal-refinement iterations. The portal must converge to
|
|
6
|
-
* within {@link MPR_TOLERANCE} of the closest face before this many
|
|
7
|
-
* iterations or we accept whatever it has and return it as an
|
|
8
|
-
* approximation — same graceful-degradation strategy as EPA, but
|
|
9
|
-
* generally MPR converges in 5–15 iterations on the shapes it does
|
|
10
|
-
* well on (smooth curves, mixed convex bodies).
|
|
11
|
-
* @type {number}
|
|
12
|
-
*/
|
|
13
|
-
const MPR_MAX_ITERATIONS = 64;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Convergence threshold on the portal-normal direction. When the new
|
|
17
|
-
* support point is within this distance of the current portal plane
|
|
18
|
-
* (measured along the portal normal), we declare convergence and use
|
|
19
|
-
* the portal as the closest-face approximation of the MTV.
|
|
20
|
-
* @type {number}
|
|
21
|
-
*/
|
|
22
|
-
const MPR_TOLERANCE = 1e-4;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Tolerance for collinearity / coincidence degeneracies during portal
|
|
26
|
-
* discovery. Picked to be safely below practical world-distance noise
|
|
27
|
-
* but large enough that real coincidences (shapes sharing a centre)
|
|
28
|
-
* trip it.
|
|
29
|
-
* @type {number}
|
|
30
|
-
*/
|
|
31
|
-
const MPR_EPSILON = 1e-10;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
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
|
-
*
|
|
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
|
-
//
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
//
|
|
135
|
-
// the
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (-V0[0] *
|
|
215
|
-
// origin outside V0-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
pn_x
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
1
|
+
import { v3_length } from "../../../core/geom/vec3/v3_length.js";
|
|
2
|
+
import { minkowski_support } from "./minkowski_support.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Maximum portal-refinement iterations. The portal must converge to
|
|
6
|
+
* within {@link MPR_TOLERANCE} of the closest face before this many
|
|
7
|
+
* iterations or we accept whatever it has and return it as an
|
|
8
|
+
* approximation — same graceful-degradation strategy as EPA, but
|
|
9
|
+
* generally MPR converges in 5–15 iterations on the shapes it does
|
|
10
|
+
* well on (smooth curves, mixed convex bodies).
|
|
11
|
+
* @type {number}
|
|
12
|
+
*/
|
|
13
|
+
const MPR_MAX_ITERATIONS = 64;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Convergence threshold on the portal-normal direction. When the new
|
|
17
|
+
* support point is within this distance of the current portal plane
|
|
18
|
+
* (measured along the portal normal), we declare convergence and use
|
|
19
|
+
* the portal as the closest-face approximation of the MTV.
|
|
20
|
+
* @type {number}
|
|
21
|
+
*/
|
|
22
|
+
const MPR_TOLERANCE = 1e-4;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Tolerance for collinearity / coincidence degeneracies during portal
|
|
26
|
+
* discovery. Picked to be safely below practical world-distance noise
|
|
27
|
+
* but large enough that real coincidences (shapes sharing a centre)
|
|
28
|
+
* trip it.
|
|
29
|
+
* @type {number}
|
|
30
|
+
*/
|
|
31
|
+
const MPR_EPSILON = 1e-10;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Scale-relative collinearity threshold (sin²θ). A cross product |a×b|² equals
|
|
35
|
+
* |a|²·|b|²·sin²θ, so comparing |a×b|² against this fraction of |a|²·|b|² tests
|
|
36
|
+
* the ANGLE, independent of shape scale — unlike an absolute epsilon on the
|
|
37
|
+
* (length⁴ / length²) cross magnitude, which fired spuriously for sub-millimetre
|
|
38
|
+
* shapes and returned a bogus, inflated MTV. ~ (1e-6 rad)².
|
|
39
|
+
* @type {number}
|
|
40
|
+
*/
|
|
41
|
+
const MPR_COLLINEAR_REL_SQR = 1e-12;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Portal vertices in the Minkowski-difference space.
|
|
45
|
+
* V0 is an interior point of the Mink-diff (the difference of the two
|
|
46
|
+
* bodies' centres); V1, V2, V3 are the portal triangle's vertices; V4
|
|
47
|
+
* is the candidate new vertex during refinement.
|
|
48
|
+
*/
|
|
49
|
+
const V0 = new Float64Array(3);
|
|
50
|
+
const V1 = new Float64Array(3);
|
|
51
|
+
const V2 = new Float64Array(3);
|
|
52
|
+
const V3 = new Float64Array(3);
|
|
53
|
+
const V4 = new Float64Array(3);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Minkowski Portal Refinement (XenoCollide, Gary Snethen, GDC 2009).
|
|
57
|
+
*
|
|
58
|
+
* Determines whether two convex shapes overlap and, if so, returns the
|
|
59
|
+
* Minimum Translation Vector (MTV) that would separate them. The
|
|
60
|
+
* algorithm runs in two phases:
|
|
61
|
+
*
|
|
62
|
+
* 1. **Portal discovery.** Starting from an interior point V0 of the
|
|
63
|
+
* Minkowski difference (the difference of the two body centres),
|
|
64
|
+
* find three vertices V1, V2, V3 on the Mink-diff boundary that
|
|
65
|
+
* form a triangle the ray V0→origin pierces. If at any point a
|
|
66
|
+
* support hyperplane separates V0 from the origin, the shapes
|
|
67
|
+
* can't overlap and we return false.
|
|
68
|
+
*
|
|
69
|
+
* 2. **Portal refinement.** Iteratively replace one of V1, V2, V3
|
|
70
|
+
* with a new support V4 found in the direction of the portal
|
|
71
|
+
* face's outward normal. When V4 doesn't bring the portal any
|
|
72
|
+
* closer to the origin (within `MPR_TOLERANCE`), the portal face
|
|
73
|
+
* *is* the closest face on the Mink-diff to the origin; its
|
|
74
|
+
* outward normal is the MTV direction and the perpendicular
|
|
75
|
+
* distance from the origin to the face is the penetration depth.
|
|
76
|
+
*
|
|
77
|
+
* Compared to GJK+EPA on the same overlap:
|
|
78
|
+
* - One algorithm produces both the overlap test and the MTV (EPA is
|
|
79
|
+
* a separate pass after GJK).
|
|
80
|
+
* - Better behaviour on smooth or high-vertex-count shapes — EPA's
|
|
81
|
+
* polytope-expansion can stall on curved surfaces because each new
|
|
82
|
+
* support produces a face with no flat region to converge on; MPR's
|
|
83
|
+
* portal stays a single triangle and just slides toward the origin.
|
|
84
|
+
* - Tends to converge in 5–15 iterations on typical inputs vs. EPA's
|
|
85
|
+
* 32+ on smooth pairs.
|
|
86
|
+
*
|
|
87
|
+
* **Output convention matches the EPA penetration query** ({@link gjk_epa_penetration}'s MTV form):
|
|
88
|
+
* `result[result_offset .. result_offset+2]` is the MTV vector —
|
|
89
|
+
* direction is "from A's surface into B" (i.e. the direction you'd
|
|
90
|
+
* translate B by to separate the shapes), magnitude is the depth.
|
|
91
|
+
* The caller reads depth as `√(x² + y² + z²)` and normalises to get
|
|
92
|
+
* the unit normal. This makes MPR a drop-in replacement for EPA at
|
|
93
|
+
* any narrowphase call site.
|
|
94
|
+
*
|
|
95
|
+
* On failure modes:
|
|
96
|
+
* - Returns `false` if the shapes don't overlap.
|
|
97
|
+
* - On a degenerate Mink-diff (shapes touching at a point, perfectly
|
|
98
|
+
* coincident centres with collinear support points), returns
|
|
99
|
+
* `true` with a small fallback MTV — same approach as EPA's
|
|
100
|
+
* non-convergent fallback. The narrowphase consumer already
|
|
101
|
+
* filters non-positive / non-finite depths.
|
|
102
|
+
*
|
|
103
|
+
* @param {Float64Array} result destination buffer for the MTV vector
|
|
104
|
+
* @param {number} result_offset
|
|
105
|
+
* @param {PosedShape} shape_a
|
|
106
|
+
* @param {PosedShape} shape_b
|
|
107
|
+
* @returns {boolean} true on overlap
|
|
108
|
+
*/
|
|
109
|
+
export function mpr(result, result_offset, shape_a, shape_b) {
|
|
110
|
+
// ── Step 1: interior point V0 = centerA − centerB ───────────────────
|
|
111
|
+
//
|
|
112
|
+
// PosedShape carries the body's world-space position; the centre of
|
|
113
|
+
// its underlying shape (in body-local space) is the origin, so the
|
|
114
|
+
// world centre is just (px, py, pz). The difference of two body
|
|
115
|
+
// centres lies inside the Mink-diff for any convex pair whose shape
|
|
116
|
+
// origins are interior to their geometry — true for spheres,
|
|
117
|
+
// boxes, capsules, and any closed mesh whose bounding box contains
|
|
118
|
+
// its centroid.
|
|
119
|
+
V0[0] = shape_a.px - shape_b.px;
|
|
120
|
+
V0[1] = shape_a.py - shape_b.py;
|
|
121
|
+
V0[2] = shape_a.pz - shape_b.pz;
|
|
122
|
+
|
|
123
|
+
// If the two centres coincide exactly, V0 is at the origin and we
|
|
124
|
+
// can't direction-find from it. Perturb to break the symmetry —
|
|
125
|
+
// any direction works; the algorithm will converge to the same
|
|
126
|
+
// answer regardless of which we pick.
|
|
127
|
+
if (V0[0] === 0 && V0[1] === 0 && V0[2] === 0) {
|
|
128
|
+
V0[0] = MPR_EPSILON;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Step 2: first support V1 in direction −V0 (toward the origin) ──
|
|
132
|
+
minkowski_support(V1, 0, shape_a, shape_b, -V0[0], -V0[1], -V0[2]);
|
|
133
|
+
|
|
134
|
+
// If V1 isn't past the origin along −V0, the origin lies outside
|
|
135
|
+
// the Mink-diff and the shapes are separated.
|
|
136
|
+
if (V1[0] * -V0[0] + V1[1] * -V0[1] + V1[2] * -V0[2] < 0) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── Step 3: portal discovery ────────────────────────────────────────
|
|
141
|
+
//
|
|
142
|
+
// Find V2 in a direction perpendicular to the V0–V1 axis. n = V1 × V0
|
|
143
|
+
// is perpendicular to both; if it degenerates to zero, V0 and V1
|
|
144
|
+
// are collinear with the origin, which means the origin lies on
|
|
145
|
+
// the segment from V0 to V1 (since V0 is interior and V1 is past
|
|
146
|
+
// the origin) — definite overlap, emit a fallback MTV along V1.
|
|
147
|
+
let nx = V1[1] * V0[2] - V1[2] * V0[1];
|
|
148
|
+
let ny = V1[2] * V0[0] - V1[0] * V0[2];
|
|
149
|
+
let nz = V1[0] * V0[1] - V1[1] * V0[0];
|
|
150
|
+
|
|
151
|
+
// Scale-relative collinearity test: |V1×V0|² vs sin²θ·|V1|²·|V0|².
|
|
152
|
+
const v0_len_sqr = V0[0] * V0[0] + V0[1] * V0[1] + V0[2] * V0[2];
|
|
153
|
+
const v1_len_sqr = V1[0] * V1[0] + V1[1] * V1[1] + V1[2] * V1[2];
|
|
154
|
+
if (nx * nx + ny * ny + nz * nz < MPR_COLLINEAR_REL_SQR * v0_len_sqr * v1_len_sqr) {
|
|
155
|
+
// Collinear degeneracy: project the answer along V1.
|
|
156
|
+
const v1_len = v3_length(V1[0], V1[1], V1[2]);
|
|
157
|
+
if (v1_len > MPR_EPSILON) {
|
|
158
|
+
result[result_offset] = V1[0];
|
|
159
|
+
result[result_offset + 1] = V1[1];
|
|
160
|
+
result[result_offset + 2] = V1[2];
|
|
161
|
+
} else {
|
|
162
|
+
// V1 at origin — shapes touching exactly. Emit a tiny MTV.
|
|
163
|
+
result[result_offset] = MPR_EPSILON;
|
|
164
|
+
result[result_offset + 1] = 0;
|
|
165
|
+
result[result_offset + 2] = 0;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
minkowski_support(V2, 0, shape_a, shape_b, nx, ny, nz);
|
|
171
|
+
|
|
172
|
+
if (V2[0] * nx + V2[1] * ny + V2[2] * nz < 0) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Compute the portal-triangle normal (V1 − V0) × (V2 − V0) and
|
|
177
|
+
// orient it away from V0 (so it points roughly toward the origin).
|
|
178
|
+
let v1mv0_x = V1[0] - V0[0], v1mv0_y = V1[1] - V0[1], v1mv0_z = V1[2] - V0[2];
|
|
179
|
+
let v2mv0_x = V2[0] - V0[0], v2mv0_y = V2[1] - V0[1], v2mv0_z = V2[2] - V0[2];
|
|
180
|
+
nx = v1mv0_y * v2mv0_z - v1mv0_z * v2mv0_y;
|
|
181
|
+
ny = v1mv0_z * v2mv0_x - v1mv0_x * v2mv0_z;
|
|
182
|
+
nz = v1mv0_x * v2mv0_y - v1mv0_y * v2mv0_x;
|
|
183
|
+
|
|
184
|
+
// If the normal points along +V0 (back toward V0's side), swap V1/V2
|
|
185
|
+
// and flip — we want the portal facing away from V0 toward the
|
|
186
|
+
// origin.
|
|
187
|
+
if (nx * V0[0] + ny * V0[1] + nz * V0[2] > 0) {
|
|
188
|
+
const tx = V1[0], ty = V1[1], tz = V1[2];
|
|
189
|
+
V1[0] = V2[0]; V1[1] = V2[1]; V1[2] = V2[2];
|
|
190
|
+
V2[0] = tx; V2[1] = ty; V2[2] = tz;
|
|
191
|
+
nx = -nx; ny = -ny; nz = -nz;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Portal-discovery iteration. We keep refining V3 until the
|
|
195
|
+
// tetrahedron V0–V1–V2–V3 actually contains the origin.
|
|
196
|
+
let discovered = false;
|
|
197
|
+
for (let i = 0; i < MPR_MAX_ITERATIONS; i++) {
|
|
198
|
+
minkowski_support(V3, 0, shape_a, shape_b, nx, ny, nz);
|
|
199
|
+
|
|
200
|
+
if (V3[0] * nx + V3[1] * ny + V3[2] * nz < 0) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Test if origin lies outside plane V0–V1–V3 (V2's side).
|
|
205
|
+
// n_013 = (V1 − V0) × (V3 − V0) — points away from V2 if portal
|
|
206
|
+
// is consistently oriented.
|
|
207
|
+
const v3mv0_x = V3[0] - V0[0], v3mv0_y = V3[1] - V0[1], v3mv0_z = V3[2] - V0[2];
|
|
208
|
+
const n013_x = v1mv0_y * v3mv0_z - v1mv0_z * v3mv0_y;
|
|
209
|
+
const n013_y = v1mv0_z * v3mv0_x - v1mv0_x * v3mv0_z;
|
|
210
|
+
const n013_z = v1mv0_x * v3mv0_y - v1mv0_y * v3mv0_x;
|
|
211
|
+
|
|
212
|
+
// origin's signed distance from plane V0-V1-V3 along n013 is
|
|
213
|
+
// dot(−V0, n013), since the plane passes through V0.
|
|
214
|
+
if (-V0[0] * n013_x - V0[1] * n013_y - V0[2] * n013_z > 0) {
|
|
215
|
+
// origin outside V0-V1-V3 plane on the V2 side — replace V2
|
|
216
|
+
V2[0] = V3[0]; V2[1] = V3[1]; V2[2] = V3[2];
|
|
217
|
+
v2mv0_x = v3mv0_x; v2mv0_y = v3mv0_y; v2mv0_z = v3mv0_z;
|
|
218
|
+
nx = n013_x; ny = n013_y; nz = n013_z;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Test if origin lies outside plane V0-V2-V3 (V1's side).
|
|
223
|
+
const n023_x = v2mv0_y * v3mv0_z - v2mv0_z * v3mv0_y;
|
|
224
|
+
const n023_y = v2mv0_z * v3mv0_x - v2mv0_x * v3mv0_z;
|
|
225
|
+
const n023_z = v2mv0_x * v3mv0_y - v2mv0_y * v3mv0_x;
|
|
226
|
+
|
|
227
|
+
if (-V0[0] * n023_x - V0[1] * n023_y - V0[2] * n023_z < 0) {
|
|
228
|
+
// origin outside V0-V2-V3 plane on V1's side — replace V1
|
|
229
|
+
V1[0] = V3[0]; V1[1] = V3[1]; V1[2] = V3[2];
|
|
230
|
+
v1mv0_x = v3mv0_x; v1mv0_y = v3mv0_y; v1mv0_z = v3mv0_z;
|
|
231
|
+
// New portal normal: (V1' - V0) × (V2 - V0) where V1' is the new V1 (= old V3)
|
|
232
|
+
nx = v3mv0_y * v2mv0_z - v3mv0_z * v2mv0_y;
|
|
233
|
+
ny = v3mv0_z * v2mv0_x - v3mv0_x * v2mv0_z;
|
|
234
|
+
nz = v3mv0_x * v2mv0_y - v3mv0_y * v2mv0_x;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Origin is inside the tetrahedron V0-V1-V2-V3 — portal found.
|
|
239
|
+
discovered = true;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!discovered) {
|
|
244
|
+
// Ran out of portal-discovery iterations — treat as a near-miss.
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── Step 4: portal refinement ───────────────────────────────────────
|
|
249
|
+
//
|
|
250
|
+
// Now we know V0–V1–V2–V3 is a valid tetrahedron containing the
|
|
251
|
+
// origin. Iteratively replace one of V1/V2/V3 with a new support
|
|
252
|
+
// V4 (found in the direction of the portal triangle's outward
|
|
253
|
+
// normal). When V4 doesn't extend the portal further, the portal
|
|
254
|
+
// face is the closest Mink-diff face to the origin and its normal
|
|
255
|
+
// is the MTV.
|
|
256
|
+
for (let i = 0; i < MPR_MAX_ITERATIONS; i++) {
|
|
257
|
+
// Portal face normal = (V2 - V1) × (V3 - V1)
|
|
258
|
+
const e1_x = V2[0] - V1[0], e1_y = V2[1] - V1[1], e1_z = V2[2] - V1[2];
|
|
259
|
+
const e2_x = V3[0] - V1[0], e2_y = V3[1] - V1[1], e2_z = V3[2] - V1[2];
|
|
260
|
+
let pn_x = e1_y * e2_z - e1_z * e2_y;
|
|
261
|
+
let pn_y = e1_z * e2_x - e1_x * e2_z;
|
|
262
|
+
let pn_z = e1_x * e2_y - e1_y * e2_x;
|
|
263
|
+
// Scale-relative degeneracy: |e1×e2|² vs sin²θ·|e1|²·|e2|².
|
|
264
|
+
const pn_len_sqr = pn_x * pn_x + pn_y * pn_y + pn_z * pn_z;
|
|
265
|
+
const e1_len_sqr = e1_x * e1_x + e1_y * e1_y + e1_z * e1_z;
|
|
266
|
+
const e2_len_sqr = e2_x * e2_x + e2_y * e2_y + e2_z * e2_z;
|
|
267
|
+
if (pn_len_sqr < MPR_COLLINEAR_REL_SQR * e1_len_sqr * e2_len_sqr) {
|
|
268
|
+
// Degenerate portal (collinear vertices) — return the
|
|
269
|
+
// current state as a fallback approximation.
|
|
270
|
+
result[result_offset] = pn_x;
|
|
271
|
+
result[result_offset + 1] = pn_y;
|
|
272
|
+
result[result_offset + 2] = pn_z;
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
const inv_pn = 1 / Math.sqrt(pn_len_sqr);
|
|
276
|
+
pn_x *= inv_pn; pn_y *= inv_pn; pn_z *= inv_pn;
|
|
277
|
+
|
|
278
|
+
// Perpendicular distance from the origin to the portal plane,
|
|
279
|
+
// = dot(V1, portal_normal). This is the candidate depth on
|
|
280
|
+
// every iteration; we accept it once V4 fails to push the
|
|
281
|
+
// portal further.
|
|
282
|
+
const portal_dist = V1[0] * pn_x + V1[1] * pn_y + V1[2] * pn_z;
|
|
283
|
+
|
|
284
|
+
// New support V4 in the portal normal direction.
|
|
285
|
+
minkowski_support(V4, 0, shape_a, shape_b, pn_x, pn_y, pn_z);
|
|
286
|
+
|
|
287
|
+
const v4_dist = V4[0] * pn_x + V4[1] * pn_y + V4[2] * pn_z;
|
|
288
|
+
|
|
289
|
+
// Convergence: V4 doesn't extend the portal beyond the current
|
|
290
|
+
// face (within tolerance). Emit MTV = normal × depth.
|
|
291
|
+
if (v4_dist - portal_dist < MPR_TOLERANCE) {
|
|
292
|
+
result[result_offset] = pn_x * portal_dist;
|
|
293
|
+
result[result_offset + 1] = pn_y * portal_dist;
|
|
294
|
+
result[result_offset + 2] = pn_z * portal_dist;
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// V4 didn't pass the origin along the portal normal — the
|
|
299
|
+
// shapes aren't actually overlapping (shouldn't happen after
|
|
300
|
+
// successful portal discovery, but defensive).
|
|
301
|
+
if (v4_dist < 0) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Replace one of V1, V2, V3 with V4 such that the new portal
|
|
306
|
+
// still contains the V0→origin ray. The split is determined by
|
|
307
|
+
// three cross-product / dot tests against V4 × V0: signs of
|
|
308
|
+
// (Vi · (V4 × V0)) place V0 in one of the three sub-portals.
|
|
309
|
+
//
|
|
310
|
+
// The branch structure here mirrors libccd / bullet's MPR
|
|
311
|
+
// (`btMprExpandPortal`).
|
|
312
|
+
const c_x = V4[1] * V0[2] - V4[2] * V0[1];
|
|
313
|
+
const c_y = V4[2] * V0[0] - V4[0] * V0[2];
|
|
314
|
+
const c_z = V4[0] * V0[1] - V4[1] * V0[0];
|
|
315
|
+
|
|
316
|
+
const d1 = V1[0] * c_x + V1[1] * c_y + V1[2] * c_z;
|
|
317
|
+
|
|
318
|
+
if (d1 >= 0) {
|
|
319
|
+
const d2 = V2[0] * c_x + V2[1] * c_y + V2[2] * c_z;
|
|
320
|
+
if (d2 >= 0) {
|
|
321
|
+
// origin sits in sub-portal between V4 and V1 — drop V1
|
|
322
|
+
V1[0] = V4[0]; V1[1] = V4[1]; V1[2] = V4[2];
|
|
323
|
+
} else {
|
|
324
|
+
// sub-portal between V4 and V3 — drop V3
|
|
325
|
+
V3[0] = V4[0]; V3[1] = V4[1]; V3[2] = V4[2];
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
const d3 = V3[0] * c_x + V3[1] * c_y + V3[2] * c_z;
|
|
329
|
+
if (d3 >= 0) {
|
|
330
|
+
// sub-portal between V4 and V2 — drop V2
|
|
331
|
+
V2[0] = V4[0]; V2[1] = V4[1]; V2[2] = V4[2];
|
|
332
|
+
} else {
|
|
333
|
+
// sub-portal between V4 and V1 (other side) — drop V1
|
|
334
|
+
V1[0] = V4[0]; V1[1] = V4[1]; V1[2] = V4[2];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Refinement ran out of iterations — emit the current portal face
|
|
340
|
+
// as the best-known MTV, same graceful-degradation as EPA.
|
|
341
|
+
const e1_x = V2[0] - V1[0], e1_y = V2[1] - V1[1], e1_z = V2[2] - V1[2];
|
|
342
|
+
const e2_x = V3[0] - V1[0], e2_y = V3[1] - V1[1], e2_z = V3[2] - V1[2];
|
|
343
|
+
let pn_x = e1_y * e2_z - e1_z * e2_y;
|
|
344
|
+
let pn_y = e1_z * e2_x - e1_x * e2_z;
|
|
345
|
+
let pn_z = e1_x * e2_y - e1_y * e2_x;
|
|
346
|
+
const pn_len_sqr = pn_x * pn_x + pn_y * pn_y + pn_z * pn_z;
|
|
347
|
+
const e1_len_sqr = e1_x * e1_x + e1_y * e1_y + e1_z * e1_z;
|
|
348
|
+
const e2_len_sqr = e2_x * e2_x + e2_y * e2_y + e2_z * e2_z;
|
|
349
|
+
if (pn_len_sqr < MPR_COLLINEAR_REL_SQR * e1_len_sqr * e2_len_sqr) {
|
|
350
|
+
result[result_offset] = 0;
|
|
351
|
+
result[result_offset + 1] = 0;
|
|
352
|
+
result[result_offset + 2] = 0;
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
const inv_pn = 1 / Math.sqrt(pn_len_sqr);
|
|
356
|
+
pn_x *= inv_pn; pn_y *= inv_pn; pn_z *= inv_pn;
|
|
357
|
+
const portal_dist = V1[0] * pn_x + V1[1] * pn_y + V1[2] * pn_z;
|
|
358
|
+
result[result_offset] = pn_x * portal_dist;
|
|
359
|
+
result[result_offset + 1] = pn_y * portal_dist;
|
|
360
|
+
result[result_offset + 2] = pn_z * portal_dist;
|
|
361
|
+
return true;
|
|
362
|
+
}
|