@woosh/meep-engine 3.5.0 → 3.7.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/gjk/gjk_epa_penetration.d.ts +30 -3
- package/src/core/geom/3d/gjk/gjk_epa_penetration.d.ts.map +1 -1
- package/src/core/geom/3d/gjk/gjk_epa_penetration.js +83 -10
- package/src/core/geom/3d/gjk/mpr.d.ts +7 -1
- package/src/core/geom/3d/gjk/mpr.d.ts.map +1 -1
- package/src/core/geom/3d/gjk/mpr.js +76 -13
- package/src/engine/graphics3/GraphicsEngine3.d.ts +10 -5
- package/src/engine/graphics3/GraphicsEngine3.d.ts.map +1 -1
- package/src/engine/graphics3/GraphicsEngine3.js +10 -5
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +20 -2
- package/src/engine/physics/queries/shape_cast.d.ts +15 -10
- package/src/engine/physics/queries/shape_cast.d.ts.map +1 -1
- package/src/engine/physics/queries/shape_cast.js +620 -417
- package/src/shade/renderer/global_illumination/brick4/gpu/bake/brick4_bake_basic.js +2 -2
- package/src/shade/renderer/scene/bvh/StaticSceneBVH.js +4 -4
- package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +1 -1
- package/src/engine/graphics/sh3/build_injected_shader.d.ts +0 -51
- package/src/engine/graphics/sh3/build_injected_shader.d.ts.map +0 -1
- package/src/engine/graphics/sh3/build_injected_shader.js +0 -56
- package/src/engine/graphics/sh3/fromCubeRenderTarget.d.ts +0 -9
- package/src/engine/graphics/sh3/fromCubeRenderTarget.d.ts.map +0 -1
- package/src/engine/graphics/sh3/fromCubeRenderTarget.js +0 -142
- package/src/engine/graphics/sh3/shader/visualize.frag.d.ts +0 -3
- package/src/engine/graphics/sh3/shader/visualize.frag.d.ts.map +0 -1
- package/src/engine/graphics/sh3/shader/visualize.frag.js +0 -51
- package/src/engine/graphics/sh3/shader/visualize.vert.d.ts +0 -3
- package/src/engine/graphics/sh3/shader/visualize.vert.d.ts.map +0 -1
- package/src/engine/graphics/sh3/shader/visualize.vert.js +0 -12
package/package.json
CHANGED
|
@@ -3,15 +3,42 @@
|
|
|
3
3
|
* Writes the unit separation normal (A→B: the direction A must move along
|
|
4
4
|
* NEGATED to separate — equivalently, negate `out` for the B→A push axis;
|
|
5
5
|
* the narrowphase sign-check sites canonicalise it against the body centres)
|
|
6
|
-
* into `out` and returns the depth
|
|
7
|
-
*
|
|
6
|
+
* into `out` and returns the depth. `out` is untouched unless the return is
|
|
7
|
+
* positive.
|
|
8
8
|
*
|
|
9
9
|
* @param {Float64Array|number[]} out destination for the unit normal, length ≥ 3
|
|
10
10
|
* @param {SupportProvider} A world-space support provider (e.g. PosedShape3D)
|
|
11
11
|
* @param {SupportProvider} B world-space support provider (e.g. PosedShape3D)
|
|
12
|
-
* @returns {number} penetration depth
|
|
12
|
+
* @returns {number} the penetration depth (> 0) on overlap;
|
|
13
|
+
* {@link GJK_SEPARATED} when GJK PROVED the shapes apart; `0` for no usable
|
|
14
|
+
* axis — EPA degenerated on an established overlap, or GJK reached no
|
|
15
|
+
* verdict at all. `0` is not a separation and callers with a fallback
|
|
16
|
+
* should still run it
|
|
13
17
|
*/
|
|
14
18
|
export function gjk_epa_penetration(out: Float64Array | number[], A: SupportProvider, B: SupportProvider): number;
|
|
19
|
+
/**
|
|
20
|
+
* Sentinel returned by {@link gjk_epa_penetration} when GJK PROVED the shapes
|
|
21
|
+
* apart — {@link GJK_APART}, a support plane with the whole Minkowski
|
|
22
|
+
* difference on the far side of the origin — as distinct from `0`, "no usable
|
|
23
|
+
* axis": a degenerate EPA, or a GJK that reached no verdict at all.
|
|
24
|
+
*
|
|
25
|
+
* The two need separate values because callers act on them differently. A
|
|
26
|
+
* proof of separation is the final answer and the pair is done; the rest is a
|
|
27
|
+
* gap in this query that a fallback (MPR) may still be able to fill. Note the
|
|
28
|
+
* asymmetry — this is claimed ONLY from the proof, never from GJK's other two
|
|
29
|
+
* non-overlap exits, which are inconclusive rather than negative.
|
|
30
|
+
* Collapsing both to `0`, as this function used to, sent every non-touching
|
|
31
|
+
* pair the broadphase produced into that fallback — where MPR, whose portal
|
|
32
|
+
* argument assumes an overlap it was never told to doubt, answered a question
|
|
33
|
+
* GJK had already settled and reported the gap as a penetration of its own
|
|
34
|
+
* size (see the separating-plane certificate in {@link mpr}).
|
|
35
|
+
*
|
|
36
|
+
* Negative so that the existing `depth > 0` guard at every call site keeps
|
|
37
|
+
* rejecting it unchanged.
|
|
38
|
+
*
|
|
39
|
+
* @type {number}
|
|
40
|
+
*/
|
|
41
|
+
export const GJK_SEPARATED: number;
|
|
15
42
|
/**
|
|
16
43
|
* A convex shape exposing a world-space support mapping: `support(out, off, dx,
|
|
17
44
|
* dy, dz)` writes the farthest point of the shape in direction `(dx, dy, dz)`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gjk_epa_penetration.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/gjk/gjk_epa_penetration.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gjk_epa_penetration.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/gjk/gjk_epa_penetration.js"],"names":[],"mappings":"AA2iBA;;;;;;;;;;;;;;;;GAgBG;AACH,yCATW,YAAY,GAAC,MAAM,EAAE,KACrB,eAAe,KACf,eAAe,GACb,MAAM,CA0BlB;AA7DD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,4BAFU,MAAM,CAEgB;;;;;;8BAzdnB;IAAE,OAAO,SAAW,YAAY,QAAE,MAAM,QAAE,MAAM,QAAE,MAAM,QAAE,MAAM,KAAG,IAAI,CAAA;CAAE"}
|
|
@@ -81,14 +81,45 @@ const CEN = new Float64Array(3); // polytope centroid (for outward orientation)
|
|
|
81
81
|
* @typedef {{ support: function(Float64Array, number, number, number, number): void }} SupportProvider
|
|
82
82
|
*/
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* {@link gjk} enclosed the origin: the shapes overlap and {@link SIMPLEX} holds
|
|
86
|
+
* a rank-4 simplex EPA can expand from.
|
|
87
|
+
* @type {number}
|
|
88
|
+
*/
|
|
89
|
+
const GJK_ENCLOSED = 4;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* {@link gjk} found a support plane with the whole Minkowski difference on the
|
|
93
|
+
* far side of the origin. That is a proof of separation, and the only one of
|
|
94
|
+
* this function's non-overlap exits that is.
|
|
95
|
+
* @type {number}
|
|
96
|
+
*/
|
|
97
|
+
const GJK_APART = -1;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* {@link gjk} ran out of road: the search direction collapsed, or the iteration
|
|
101
|
+
* cap hit. Neither says the shapes are apart — the direction collapses when the
|
|
102
|
+
* origin lands exactly ON a simplex feature, which is a TOUCH (two spheres at
|
|
103
|
+
* exactly 2r do this), and the cap is just non-convergence on a smooth support.
|
|
104
|
+
* Callers must treat it as "no answer" and keep whatever fallback they have.
|
|
105
|
+
* @type {number}
|
|
106
|
+
*/
|
|
107
|
+
const GJK_INCONCLUSIVE = 0;
|
|
108
|
+
|
|
84
109
|
/**
|
|
85
110
|
* GJK intersection. Fills {@link SIMPLEX} with a rank-4 simplex enclosing the
|
|
86
|
-
* origin and returns
|
|
111
|
+
* origin and returns {@link GJK_ENCLOSED} on overlap.
|
|
87
112
|
* `SIMPLEX` slot 0 is the most-recent point throughout (Muratori convention).
|
|
88
113
|
*
|
|
114
|
+
* The two non-overlap verdicts are deliberately distinct: only
|
|
115
|
+
* {@link GJK_APART} is a proof, and collapsing it together with
|
|
116
|
+
* {@link GJK_INCONCLUSIVE} silently converts "I could not tell" into "they are
|
|
117
|
+
* not touching" — which drops touching contacts.
|
|
118
|
+
*
|
|
89
119
|
* @param {SupportProvider} A first support provider
|
|
90
120
|
* @param {SupportProvider} B second support provider
|
|
91
|
-
* @returns {number}
|
|
121
|
+
* @returns {number} {@link GJK_ENCLOSED}, {@link GJK_APART}, or
|
|
122
|
+
* {@link GJK_INCONCLUSIVE}
|
|
92
123
|
*/
|
|
93
124
|
function gjk(A, B) {
|
|
94
125
|
minkowski_support(SIMPLEX, 0, A, B, 1, 0, 0);
|
|
@@ -102,11 +133,11 @@ function gjk(A, B) {
|
|
|
102
133
|
|
|
103
134
|
for (let iter = 0; iter < GJK_MAX_ITER; iter++) {
|
|
104
135
|
if (v3_length_sqr(DIR[0], DIR[1], DIR[2]) < DIR_EPS) {
|
|
105
|
-
return
|
|
136
|
+
return GJK_INCONCLUSIVE; // direction collapsed → touching, or apart; unknown
|
|
106
137
|
}
|
|
107
138
|
minkowski_support(_w, 0, A, B, DIR[0], DIR[1], DIR[2]);
|
|
108
139
|
if (v3_dot(_w[0], _w[1], _w[2], DIR[0], DIR[1], DIR[2]) < 0) {
|
|
109
|
-
return
|
|
140
|
+
return GJK_APART; // farthest point short of the origin → proven separated
|
|
110
141
|
}
|
|
111
142
|
// push the new point to slot 0, shifting the existing points down one slot
|
|
112
143
|
// (copyWithin is memmove-style: the overlapping forward copy is well-defined)
|
|
@@ -118,10 +149,10 @@ function gjk(A, B) {
|
|
|
118
149
|
|
|
119
150
|
count = do_simplex(count);
|
|
120
151
|
if (count === 0) {
|
|
121
|
-
return
|
|
152
|
+
return GJK_ENCLOSED; // origin enclosed by the tetrahedron
|
|
122
153
|
}
|
|
123
154
|
}
|
|
124
|
-
return
|
|
155
|
+
return GJK_INCONCLUSIVE; // iteration cap → non-convergent, not a separation
|
|
125
156
|
}
|
|
126
157
|
|
|
127
158
|
/**
|
|
@@ -498,23 +529,65 @@ function epa(A, B, out) {
|
|
|
498
529
|
return FACE_DIST[best];
|
|
499
530
|
}
|
|
500
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Sentinel returned by {@link gjk_epa_penetration} when GJK PROVED the shapes
|
|
534
|
+
* apart — {@link GJK_APART}, a support plane with the whole Minkowski
|
|
535
|
+
* difference on the far side of the origin — as distinct from `0`, "no usable
|
|
536
|
+
* axis": a degenerate EPA, or a GJK that reached no verdict at all.
|
|
537
|
+
*
|
|
538
|
+
* The two need separate values because callers act on them differently. A
|
|
539
|
+
* proof of separation is the final answer and the pair is done; the rest is a
|
|
540
|
+
* gap in this query that a fallback (MPR) may still be able to fill. Note the
|
|
541
|
+
* asymmetry — this is claimed ONLY from the proof, never from GJK's other two
|
|
542
|
+
* non-overlap exits, which are inconclusive rather than negative.
|
|
543
|
+
* Collapsing both to `0`, as this function used to, sent every non-touching
|
|
544
|
+
* pair the broadphase produced into that fallback — where MPR, whose portal
|
|
545
|
+
* argument assumes an overlap it was never told to doubt, answered a question
|
|
546
|
+
* GJK had already settled and reported the gap as a penetration of its own
|
|
547
|
+
* size (see the separating-plane certificate in {@link mpr}).
|
|
548
|
+
*
|
|
549
|
+
* Negative so that the existing `depth > 0` guard at every call site keeps
|
|
550
|
+
* rejecting it unchanged.
|
|
551
|
+
*
|
|
552
|
+
* @type {number}
|
|
553
|
+
*/
|
|
554
|
+
export const GJK_SEPARATED = -1;
|
|
555
|
+
|
|
501
556
|
/**
|
|
502
557
|
* Penetration depth + normal between two convex support providers at world pose.
|
|
503
558
|
* Writes the unit separation normal (A→B: the direction A must move along
|
|
504
559
|
* NEGATED to separate — equivalently, negate `out` for the B→A push axis;
|
|
505
560
|
* the narrowphase sign-check sites canonicalise it against the body centres)
|
|
506
|
-
* into `out` and returns the depth
|
|
507
|
-
*
|
|
561
|
+
* into `out` and returns the depth. `out` is untouched unless the return is
|
|
562
|
+
* positive.
|
|
508
563
|
*
|
|
509
564
|
* @param {Float64Array|number[]} out destination for the unit normal, length ≥ 3
|
|
510
565
|
* @param {SupportProvider} A world-space support provider (e.g. PosedShape3D)
|
|
511
566
|
* @param {SupportProvider} B world-space support provider (e.g. PosedShape3D)
|
|
512
|
-
* @returns {number} penetration depth
|
|
567
|
+
* @returns {number} the penetration depth (> 0) on overlap;
|
|
568
|
+
* {@link GJK_SEPARATED} when GJK PROVED the shapes apart; `0` for no usable
|
|
569
|
+
* axis — EPA degenerated on an established overlap, or GJK reached no
|
|
570
|
+
* verdict at all. `0` is not a separation and callers with a fallback
|
|
571
|
+
* should still run it
|
|
513
572
|
*/
|
|
514
573
|
export function gjk_epa_penetration(out, A, B) {
|
|
515
|
-
|
|
574
|
+
const verdict = gjk(A, B);
|
|
575
|
+
|
|
576
|
+
if (verdict === GJK_APART) {
|
|
577
|
+
return GJK_SEPARATED;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (verdict !== GJK_ENCLOSED) {
|
|
581
|
+
// GJK_INCONCLUSIVE — no simplex to expand and no proof of separation
|
|
582
|
+
// either, so this reports the same "no usable axis" as a degenerate
|
|
583
|
+
// EPA and leaves the caller's fallback in play. Reporting it as
|
|
584
|
+
// GJK_SEPARATED would be an overclaim, and an expensive one: the
|
|
585
|
+
// collapse exit fires on EXACTLY touching pairs (two spheres at 2r),
|
|
586
|
+
// whose resting contact would then be dropped without anything ever
|
|
587
|
+
// looking at the geometry.
|
|
516
588
|
return 0;
|
|
517
589
|
}
|
|
590
|
+
|
|
518
591
|
const depth = epa(A, B, out);
|
|
519
592
|
return depth > 0 && Number.isFinite(depth) ? depth : 0;
|
|
520
593
|
}
|
|
@@ -39,7 +39,13 @@
|
|
|
39
39
|
* any narrowphase call site.
|
|
40
40
|
*
|
|
41
41
|
* On failure modes:
|
|
42
|
-
* - Returns `false` if the shapes don't overlap
|
|
42
|
+
* - Returns `false` if the shapes don't overlap, decided by a
|
|
43
|
+
* separating-plane certificate — the support of the Mink-diff along
|
|
44
|
+
* the portal normal fails to reach the origin — which is one exact
|
|
45
|
+
* evaluation carrying none of the refinement's error. A miss leaves
|
|
46
|
+
* `result` untouched: the separation distance is deliberately NOT
|
|
47
|
+
* written, because every caller reads the depth as |MTV| and cannot
|
|
48
|
+
* tell a gap from a penetration once the sign is gone.
|
|
43
49
|
* - On a degenerate Mink-diff (shapes touching at a point, perfectly
|
|
44
50
|
* coincident centres with collinear support points), returns
|
|
45
51
|
* `true` with a small fallback MTV — same approach as EPA's
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mpr.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/gjk/mpr.js"],"names":[],"mappings":"AAsDA
|
|
1
|
+
{"version":3,"file":"mpr.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/gjk/mpr.js"],"names":[],"mappings":"AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,4BANW,YAAY,iBACZ,MAAM,iDAGJ,OAAO,CA8TnB"}
|
|
@@ -93,7 +93,13 @@ const V4 = new Float64Array(3);
|
|
|
93
93
|
* any narrowphase call site.
|
|
94
94
|
*
|
|
95
95
|
* On failure modes:
|
|
96
|
-
* - Returns `false` if the shapes don't overlap
|
|
96
|
+
* - Returns `false` if the shapes don't overlap, decided by a
|
|
97
|
+
* separating-plane certificate — the support of the Mink-diff along
|
|
98
|
+
* the portal normal fails to reach the origin — which is one exact
|
|
99
|
+
* evaluation carrying none of the refinement's error. A miss leaves
|
|
100
|
+
* `result` untouched: the separation distance is deliberately NOT
|
|
101
|
+
* written, because every caller reads the depth as |MTV| and cannot
|
|
102
|
+
* tell a gap from a penetration once the sign is gone.
|
|
97
103
|
* - On a degenerate Mink-diff (shapes touching at a point, perfectly
|
|
98
104
|
* coincident centres with collinear support points), returns
|
|
99
105
|
* `true` with a small fallback MTV — same approach as EPA's
|
|
@@ -116,6 +122,17 @@ export function mpr(result, result_offset, shape_a, shape_b) {
|
|
|
116
122
|
// origins are interior to their geometry — true for spheres,
|
|
117
123
|
// boxes, capsules, and any closed mesh whose bounding box contains
|
|
118
124
|
// its centroid.
|
|
125
|
+
//
|
|
126
|
+
// This is a PRECONDITION, not a preference, and nothing here can
|
|
127
|
+
// check it: every argument below assumes the ray V0→origin starts
|
|
128
|
+
// inside. An authored ConvexHullShape3D whose vertices sit far from
|
|
129
|
+
// its own local origin (a level brush keeping map coordinates under
|
|
130
|
+
// an identity collider transform, say) violates it, and MPR then
|
|
131
|
+
// returns a confidently wrong MTV — measured at 17.8 m, the slab's
|
|
132
|
+
// half-width, for a 0.05 m overlap. Callers that can answer overlap
|
|
133
|
+
// some other way should do so and reach for MPR only when that
|
|
134
|
+
// answer is genuinely unavailable; {@link narrowphase_step} keeps it
|
|
135
|
+
// behind a GJK verdict for exactly this reason.
|
|
119
136
|
V0[0] = shape_a.px - shape_b.px;
|
|
120
137
|
V0[1] = shape_a.py - shape_b.py;
|
|
121
138
|
V0[2] = shape_a.pz - shape_b.pz;
|
|
@@ -281,10 +298,22 @@ export function mpr(result, result_offset, shape_a, shape_b) {
|
|
|
281
298
|
const inv_pn = 1 / Math.sqrt(pn_len_sqr);
|
|
282
299
|
pn_x *= inv_pn; pn_y *= inv_pn; pn_z *= inv_pn;
|
|
283
300
|
|
|
284
|
-
//
|
|
285
|
-
// = dot(V1, portal_normal)
|
|
286
|
-
//
|
|
287
|
-
//
|
|
301
|
+
// Signed offset of the portal plane from the origin along the
|
|
302
|
+
// outward normal, = dot(V1, portal_normal), and the depth this
|
|
303
|
+
// returns once the portal settles. In exact arithmetic its sign
|
|
304
|
+
// would also BE the overlap test — V0 is interior and the ray
|
|
305
|
+
// V0→origin pierces the portal, so the origin is inside the
|
|
306
|
+
// Mink-diff exactly when it lies on V0's side of that plane —
|
|
307
|
+
// and a negative value would mean the ray left the Mink-diff
|
|
308
|
+
// before reaching the origin, making |portal_dist| a SEPARATION
|
|
309
|
+
// rather than a depth.
|
|
310
|
+
//
|
|
311
|
+
// It is not the test used, because this is an iterate: near zero
|
|
312
|
+
// its sign is refinement noise, not geometry. The certificate
|
|
313
|
+
// below decides instead, and getting a magnitude out of a
|
|
314
|
+
// separated pair — which every caller then reads as a depth,
|
|
315
|
+
// |MTV| having thrown the sign away — is the failure that made
|
|
316
|
+
// this loop worth re-deriving.
|
|
288
317
|
const portal_dist = V1[0] * pn_x + V1[1] * pn_y + V1[2] * pn_z;
|
|
289
318
|
|
|
290
319
|
// New support V4 in the portal normal direction.
|
|
@@ -292,8 +321,41 @@ export function mpr(result, result_offset, shape_a, shape_b) {
|
|
|
292
321
|
|
|
293
322
|
const v4_dist = V4[0] * pn_x + V4[1] * pn_y + V4[2] * pn_z;
|
|
294
323
|
|
|
324
|
+
// Separating-plane certificate — and it has to be tested BEFORE
|
|
325
|
+
// the convergence branch below. If the farthest point of the
|
|
326
|
+
// Mink-diff along pn does not reach the origin, the plane
|
|
327
|
+
// through the origin with normal pn separates the two shapes,
|
|
328
|
+
// which is a proof of a miss. It can never reject a true
|
|
329
|
+
// overlap: the origin is then inside the Mink-diff, whose
|
|
330
|
+
// support is ≥ 0 in EVERY direction.
|
|
331
|
+
//
|
|
332
|
+
// The order is the whole point. A small shape near a large flat
|
|
333
|
+
// face seeds a portal already within MPR_TOLERANCE of the
|
|
334
|
+
// support plane, so the convergence test succeeds on the first
|
|
335
|
+
// iteration and this proof was never reached — the old order
|
|
336
|
+
// returned `true` with a negative portal_dist, and callers
|
|
337
|
+
// (which read the depth as |MTV|) saw the gap as a penetration
|
|
338
|
+
// of exactly its own size.
|
|
339
|
+
if (v4_dist < 0) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
|
|
295
343
|
// Convergence: V4 doesn't extend the portal beyond the current
|
|
296
|
-
// face (within tolerance)
|
|
344
|
+
// face (within tolerance), so the portal face is the closest
|
|
345
|
+
// face of the Mink-diff and its offset is the depth.
|
|
346
|
+
//
|
|
347
|
+
// Note what does NOT appear here: a `portal_dist <= 0` gate. It
|
|
348
|
+
// is tempting — a negative offset is exactly what a miss looks
|
|
349
|
+
// like — but portal_dist is the one quantity in this loop that
|
|
350
|
+
// carries the refinement's error, and near zero that error is
|
|
351
|
+
// the whole signal. A capsule resting exactly on a floor
|
|
352
|
+
// converges at portal_dist ≈ −3e-6 with v4_dist ≈ +2.5e-6: a
|
|
353
|
+
// real touching contact whose sign is pure noise, which such a
|
|
354
|
+
// gate drops, taking the resting contact with it. The certificate
|
|
355
|
+
// above is error-free (one exact support evaluation) and is the
|
|
356
|
+
// right arbiter; by the time it passes, convergence bounds
|
|
357
|
+
// portal_dist below at −MPR_TOLERANCE, which is the same
|
|
358
|
+
// accuracy the depth itself is quoted to.
|
|
297
359
|
if (v4_dist - portal_dist < MPR_TOLERANCE) {
|
|
298
360
|
result[result_offset] = pn_x * portal_dist;
|
|
299
361
|
result[result_offset + 1] = pn_y * portal_dist;
|
|
@@ -301,13 +363,6 @@ export function mpr(result, result_offset, shape_a, shape_b) {
|
|
|
301
363
|
return true;
|
|
302
364
|
}
|
|
303
365
|
|
|
304
|
-
// V4 didn't pass the origin along the portal normal — the
|
|
305
|
-
// shapes aren't actually overlapping (shouldn't happen after
|
|
306
|
-
// successful portal discovery, but defensive).
|
|
307
|
-
if (v4_dist < 0) {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
366
|
// Replace one of V1, V2, V3 with V4 such that the new portal
|
|
312
367
|
// still contains the V0→origin ray. The split is determined by
|
|
313
368
|
// three cross-product / dot tests against V4 × V0: signs of
|
|
@@ -360,6 +415,14 @@ export function mpr(result, result_offset, shape_a, shape_b) {
|
|
|
360
415
|
}
|
|
361
416
|
const inv_pn = 1 / Math.sqrt(pn_len_sqr);
|
|
362
417
|
pn_x *= inv_pn; pn_y *= inv_pn; pn_z *= inv_pn;
|
|
418
|
+
// The portal never settled, so this face is a guess — but the
|
|
419
|
+
// separating-plane certificate costs one support call and holds
|
|
420
|
+
// regardless of how the refinement terminated. Apply the same rule
|
|
421
|
+
// both exits use rather than emitting an unexamined MTV.
|
|
422
|
+
minkowski_support(V4, 0, shape_a, shape_b, pn_x, pn_y, pn_z);
|
|
423
|
+
if (V4[0] * pn_x + V4[1] * pn_y + V4[2] * pn_z < 0) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
363
426
|
const portal_dist = V1[0] * pn_x + V1[1] * pn_y + V1[2] * pn_z;
|
|
364
427
|
result[result_offset] = pn_x * portal_dist;
|
|
365
428
|
result[result_offset + 1] = pn_y * portal_dist;
|
|
@@ -6,17 +6,22 @@
|
|
|
6
6
|
* start(), stop(), render(), updateSize()}` — because that is what `Engine` needs in order to boot,
|
|
7
7
|
* and because the facade is deliberately not allowed to grow past §4.2 (D14).
|
|
8
8
|
*
|
|
9
|
-
* **
|
|
9
|
+
* **Three things have been let through since, each by name.** {@link add_extension} /
|
|
10
10
|
* {@link remove_extension} (E9), because an extension has to survive the restart that replaces the
|
|
11
11
|
* renderer holding it, and
|
|
12
12
|
* {@link scene_context} (E13, D29), because the managers a system needs — animation above all —
|
|
13
13
|
* hang off Shade's per-scene GPU state and there is no other door to them. Both take or return
|
|
14
14
|
* something the caller already owns; neither hands out the renderer.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* {@link renderer} is the third, and much the widest: it hands the renderer straight out, so
|
|
17
|
+
* everything the other two are shaped to avoid is reachable through it. It is an escape hatch for
|
|
18
|
+
* callers that have no other route, not a migration target — the old engine's 44
|
|
19
|
+
* `.renderer`/`getRenderer()` call sites are each still a migration item.
|
|
20
|
+
*
|
|
21
|
+
* What is deliberately absent, and stays absent: `getRenderer()` (the old engine's accessor, and
|
|
22
|
+
* the shape those call sites are being ported off), `layers` (CPU visibility dies with GPU
|
|
23
|
+
* culling), `scene` (entities reach the scene through the mesh system, not through the facade),
|
|
24
|
+
* and the three-shaped material manager.
|
|
20
25
|
*
|
|
21
26
|
* **Lifecycle differs from the old engine on purpose.** `GraphicsEngine` starts inside the `Engine`
|
|
22
27
|
* constructor, which is why stopping an engine used to leave a restarted one with no renderer at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphicsEngine3.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/GraphicsEngine3.js"],"names":[],"mappings":"AAkDA
|
|
1
|
+
{"version":3,"file":"GraphicsEngine3.d.ts","sourceRoot":"","sources":["../../../../src/engine/graphics3/GraphicsEngine3.js"],"names":[],"mappings":"AAkDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH;IACI;;;;;OAKG;IACH,gCAEC;IAUD;;;;OAIG;IACH,yBAEC;IA0DG;;;;;;OAMG;IACH,QAFU,kBAAkB,CAEsB;IAElD;QACI;;;;;;;;WAQG;mBADO,MAAM;QAIhB;;;;;WAKG;oBADO,MAAM;QAIhB;;;;WAIG;qBADO,MAAM;QAIhB;;;;;;;WAOG;;QAGH;;;;;WAKG;;MAEN;IAED;;OAEG;IACH,YAFU,OAAO,CAEe;IAEhC;;;;;OAKG;IACH,4BAFU,OAAO,CAEyB;IAE1C;;;;;;OAMG;IACH,UAFU,SAAS,CAEY;IAG/B;;OAEG;IACH,YAFU,iBAAiB,GAAC,IAAI,CAEV;IAEtB;;;;;OAKG;IACH,UAFU,OAAO,CAEG;IAEpB;;;;;OAKG;IACH,UAFU,OAAO,CAEG;IAEpB;;OAEG;IACH,YAFU,MAAM,CAEG;IAoBvB;;;;;;;;;;;;OAYG;IACH,mDAEC;IAED;;;;;;;;OAQG;IACH,iBAFW,KAAK,QAoBf;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,iDAOC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,2DAWC;IAED;;;;;;;;;;OAUG;IACH,oCAFa,MAAM,CAclB;IAED;;;;;;OAMG;IACH,8CAFa,OAAO,CAgBnB;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,qBALW,KAAK,GACH,kBAAgB,IAAI,CAehC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,yBARW,KAAK,yBAEL,MAAM,UACN,MAAM,GACJ,QAAQ,SAAS,CAAC,CAwD9B;IAED;;;;OAIG;IACH,0BAEC;IAED;;;;;;;;;;OAUG;IACH,SAFa,QAAQ,IAAI,CAAC,CA4DzB;IAED;;;;;OAKG;IACH,aAiBC;IASD;;;;;;;;OAQG;IACH,8BAHW,OAAO,UAAQ,UACf,OAAO,UAAQ,QAQzB;IAED;;;;;;;;;;;;;;;OAeG;IACH,yBALW,MAAM,KACN,MAAM,6CAMhB;IAED;;;;;;;OAOG;IACH,mBA0BC;IAED;;;;;OAKG;IACH,4BAHW,MAAM,GACJ,OAAO,CAoDnB;;CACJ;yBA7wBwB,kCAAkC;mCAMxB,yBAAyB;mBAbzC,oCAAoC;oBACnC,4BAA4B;wBACxB,4BAA4B;sBAQ9B,kCAAkC;yCAJf,kDAAkD;sBAErE,qCAAqC;0BACjC,0CAA0C"}
|
|
@@ -56,17 +56,22 @@ const TARGET_FRAME_RATE = 30;
|
|
|
56
56
|
* start(), stop(), render(), updateSize()}` — because that is what `Engine` needs in order to boot,
|
|
57
57
|
* and because the facade is deliberately not allowed to grow past §4.2 (D14).
|
|
58
58
|
*
|
|
59
|
-
* **
|
|
59
|
+
* **Three things have been let through since, each by name.** {@link add_extension} /
|
|
60
60
|
* {@link remove_extension} (E9), because an extension has to survive the restart that replaces the
|
|
61
61
|
* renderer holding it, and
|
|
62
62
|
* {@link scene_context} (E13, D29), because the managers a system needs — animation above all —
|
|
63
63
|
* hang off Shade's per-scene GPU state and there is no other door to them. Both take or return
|
|
64
64
|
* something the caller already owns; neither hands out the renderer.
|
|
65
65
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
66
|
+
* {@link renderer} is the third, and much the widest: it hands the renderer straight out, so
|
|
67
|
+
* everything the other two are shaped to avoid is reachable through it. It is an escape hatch for
|
|
68
|
+
* callers that have no other route, not a migration target — the old engine's 44
|
|
69
|
+
* `.renderer`/`getRenderer()` call sites are each still a migration item.
|
|
70
|
+
*
|
|
71
|
+
* What is deliberately absent, and stays absent: `getRenderer()` (the old engine's accessor, and
|
|
72
|
+
* the shape those call sites are being ported off), `layers` (CPU visibility dies with GPU
|
|
73
|
+
* culling), `scene` (entities reach the scene through the mesh system, not through the facade),
|
|
74
|
+
* and the three-shaped material manager.
|
|
70
75
|
*
|
|
71
76
|
* **Lifecycle differs from the old engine on purpose.** `GraphicsEngine` starts inside the `Engine`
|
|
72
77
|
* constructor, which is why stopping an engine used to leave a restarted one with no renderer at
|
|
@@ -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,CAuClB;AAED;;;;;;;;;;;GAWG;AACH,uFALW,MAAM,MAAM;IAAC,QAAQ,WAAW;IAAC,SAAS,YAAW;CAAC,CAAC,CAAC,QA4LlE;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,QA2H3D"}
|
|
@@ -3,7 +3,7 @@ import { Triangle3D } from "../../../core/geom/3d/shape/Triangle3D.js";
|
|
|
3
3
|
import { body_id_index } from "../body/BodyStorage.js";
|
|
4
4
|
import { combine_friction, combine_restitution } from "../contact/combine_material.js";
|
|
5
5
|
import { CONTACT_STRIDE, MAX_CONTACTS_PER_MANIFOLD } from "../contact/ManifoldStore.js";
|
|
6
|
-
import { gjk_epa_penetration } from "../../../core/geom/3d/gjk/gjk_epa_penetration.js";
|
|
6
|
+
import { GJK_SEPARATED, gjk_epa_penetration } from "../../../core/geom/3d/gjk/gjk_epa_penetration.js";
|
|
7
7
|
import { reduce_manifold_contacts } from "./reduce_manifold_contacts.js";
|
|
8
8
|
import { mpr } from "../../../core/geom/3d/gjk/mpr.js";
|
|
9
9
|
import { box_box_manifold, BOX_BOX_OUT_LENGTH } from "./box_box_manifold.js";
|
|
@@ -1124,12 +1124,15 @@ function dispatch_pair(count, colA, trA, colB, trB) {
|
|
|
1124
1124
|
// expanding_polytope_algorithm pair was replaced because it returned
|
|
1125
1125
|
// a non-minimal axis on degenerate simplices (see memory:
|
|
1126
1126
|
// feedback_epa_unreliable_polytopes); MPR is kept as a secondary
|
|
1127
|
-
// safety net for the
|
|
1127
|
+
// safety net for the case EPA degenerates on a real overlap, or
|
|
1128
|
+
// GJK reaches no verdict — never for a triangle GJK proved clear.
|
|
1128
1129
|
let ex, ey, ez, depth;
|
|
1129
1130
|
const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
|
|
1130
1131
|
if (pen_depth > 0 && Number.isFinite(pen_depth)) {
|
|
1131
1132
|
ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
|
|
1132
1133
|
depth = pen_depth;
|
|
1134
|
+
} else if (pen_depth === GJK_SEPARATED) {
|
|
1135
|
+
continue;
|
|
1133
1136
|
} else {
|
|
1134
1137
|
if (!mpr(epa_result, 0, posed_a, posed_b)) continue;
|
|
1135
1138
|
ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
|
|
@@ -1247,7 +1250,22 @@ function dispatch_pair(count, colA, trA, colB, trB) {
|
|
|
1247
1250
|
if (pen_depth > 0 && Number.isFinite(pen_depth)) {
|
|
1248
1251
|
ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
|
|
1249
1252
|
depth = pen_depth;
|
|
1253
|
+
} else if (pen_depth === GJK_SEPARATED) {
|
|
1254
|
+
// GJK proved the pair apart — the answer, not a gap in the query, so
|
|
1255
|
+
// the safety net below has nothing to add and must not be consulted.
|
|
1256
|
+
// This branch is the common case on this path, not an edge: the
|
|
1257
|
+
// broadphase hands over every pair whose fat AABBs touch, and most of
|
|
1258
|
+
// them are near misses. Asking MPR about them is what turned a clear
|
|
1259
|
+
// gap into a ContactBegin carrying a positive depth equal to the gap
|
|
1260
|
+
// — for a sphere near a large ConvexHullShape3D face, which is every
|
|
1261
|
+
// projectile in a brush-built level (mpr.js's separating-plane
|
|
1262
|
+
// certificate now refuses that answer at the source too).
|
|
1263
|
+
return count;
|
|
1250
1264
|
} else {
|
|
1265
|
+
// No usable axis: EPA degenerated on a real overlap, or GJK itself
|
|
1266
|
+
// reached no verdict (its search direction collapses on an EXACTLY
|
|
1267
|
+
// touching pair). Neither is a separation, so the net still gets to
|
|
1268
|
+
// look. THIS is what it is for.
|
|
1251
1269
|
if (!mpr(epa_result, 0, posed_a, posed_b)) return count;
|
|
1252
1270
|
ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
|
|
1253
1271
|
depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
|
|
@@ -30,10 +30,12 @@
|
|
|
30
30
|
* refinement which the broadphase-only architecture doesn't have
|
|
31
31
|
* wired up yet.
|
|
32
32
|
* - `result.normal` — target surface's outward normal at the kiss
|
|
33
|
-
* point (unit length),
|
|
34
|
-
* just-overlapping configuration at the TOI.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* point (unit length), recovered by running MPR against the
|
|
34
|
+
* just-overlapping configuration at the TOI. A pair that is exactly
|
|
35
|
+
* tangent there carries no MTV to read, so a second probe nudged
|
|
36
|
+
* toward the target's centre supplies one; `-ray.direction` remains
|
|
37
|
+
* the last-resort fallback if both degenerate. See the
|
|
38
|
+
* contact-normal recovery block near the end of the function.
|
|
37
39
|
* - `result.entity` / `result.body_id` — the hit body.
|
|
38
40
|
*
|
|
39
41
|
* Output on miss: untouched; same convention as `raycast`.
|
|
@@ -52,12 +54,15 @@
|
|
|
52
54
|
* distinguishes bodies when colliders live on child entities)
|
|
53
55
|
* @param {boolean} [skip_initial_overlaps=false] how a candidate ALREADY
|
|
54
56
|
* overlapping the swept shape at `t = 0` is treated. `false` (kinematic
|
|
55
|
-
* "can I move?" semantics): the cast is blocked where
|
|
56
|
-
* hit at `t = 0`.
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
57
|
+
* "can I move?" semantics): targets are solid, so the cast is blocked where
|
|
58
|
+
* it stands — report a hit at `t = 0`. The one exception is a convex target
|
|
59
|
+
* the shape merely TOUCHES (no depth beyond {@link CONTACT_EPSILON}): a
|
|
60
|
+
* tangency blocks only a sweep driving into it, and otherwise steps aside so
|
|
61
|
+
* the scan can reach real blockers. `true` (CCD semantics): such a candidate
|
|
62
|
+
* is a resting / sliding contact the discrete solver already owns, NOT a
|
|
63
|
+
* tunnelling threat — skip it and keep scanning the remaining candidates for
|
|
64
|
+
* real blockers along the sweep. Either way, one contact at the start of the
|
|
65
|
+
* sweep must never blind the cast to every other obstacle.
|
|
61
66
|
* @returns {boolean}
|
|
62
67
|
*/
|
|
63
68
|
export function shape_cast(system: PhysicsSystem, ray: any, shape: AbstractShape3D, rotation: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape_cast.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/queries/shape_cast.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shape_cast.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/queries/shape_cast.js"],"names":[],"mappings":"AAoPA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,8FAnBW;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,iDAE7B,MAAM,+BAA6B,MAAM,KAAG,OAAO,0BAI3D,OAAO,GAWL,OAAO,CAsTnB"}
|