@codexo/exojs-physics 0.15.3 → 0.16.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/README.md +51 -23
- package/dist/esm/Aabb.d.ts +6 -10
- package/dist/esm/Aabb.d.ts.map +1 -0
- package/dist/esm/Aabb.js +9 -5
- package/dist/esm/Aabb.js.map +1 -1
- package/dist/esm/Collider.d.ts +62 -10
- package/dist/esm/Collider.d.ts.map +1 -0
- package/dist/esm/Collider.js +263 -157
- package/dist/esm/Collider.js.map +1 -1
- package/dist/esm/ContactGraph.d.ts +51 -5
- package/dist/esm/ContactGraph.d.ts.map +1 -0
- package/dist/esm/ContactGraph.js +282 -223
- package/dist/esm/ContactGraph.js.map +1 -1
- package/dist/esm/ContactModifier.d.ts +85 -0
- package/dist/esm/ContactModifier.d.ts.map +1 -0
- package/dist/esm/ContactModifier.js +49 -0
- package/dist/esm/ContactModifier.js.map +1 -0
- package/dist/esm/PhysicsBody.d.ts +56 -22
- package/dist/esm/PhysicsBody.d.ts.map +1 -0
- package/dist/esm/PhysicsBody.js +461 -443
- package/dist/esm/PhysicsBody.js.map +1 -1
- package/dist/esm/PhysicsWorld.d.ts +276 -43
- package/dist/esm/PhysicsWorld.d.ts.map +1 -0
- package/dist/esm/PhysicsWorld.js +890 -626
- package/dist/esm/PhysicsWorld.js.map +1 -1
- package/dist/esm/TimeStepper.d.ts +3 -2
- package/dist/esm/TimeStepper.d.ts.map +1 -0
- package/dist/esm/TimeStepper.js +56 -67
- package/dist/esm/TimeStepper.js.map +1 -1
- package/dist/esm/backend/NativePhysicsBackend.d.ts +8 -4
- package/dist/esm/backend/NativePhysicsBackend.d.ts.map +1 -0
- package/dist/esm/backend/NativePhysicsBackend.js +48 -41
- package/dist/esm/backend/NativePhysicsBackend.js.map +1 -1
- package/dist/esm/backend/PhysicsBackend.d.ts +9 -2
- package/dist/esm/backend/PhysicsBackend.d.ts.map +1 -0
- package/dist/esm/binding/BindingRegistry.d.ts +25 -4
- package/dist/esm/binding/BindingRegistry.d.ts.map +1 -0
- package/dist/esm/binding/BindingRegistry.js +70 -36
- package/dist/esm/binding/BindingRegistry.js.map +1 -1
- package/dist/esm/binding/PhysicsBinding.d.ts +22 -5
- package/dist/esm/binding/PhysicsBinding.d.ts.map +1 -0
- package/dist/esm/binding/PhysicsBinding.js +46 -21
- package/dist/esm/binding/PhysicsBinding.js.map +1 -1
- package/dist/esm/broadphase/AabbTreeBroadPhase.d.ts +68 -0
- package/dist/esm/broadphase/AabbTreeBroadPhase.d.ts.map +1 -0
- package/dist/esm/broadphase/AabbTreeBroadPhase.js +149 -0
- package/dist/esm/broadphase/AabbTreeBroadPhase.js.map +1 -0
- package/dist/esm/broadphase/BroadPhase.d.ts +7 -4
- package/dist/esm/broadphase/BroadPhase.d.ts.map +1 -0
- package/dist/esm/broadphase/index.d.ts +2 -1
- package/dist/esm/broadphase/index.d.ts.map +1 -0
- package/dist/esm/collision/CollisionProxy.d.ts +3 -2
- package/dist/esm/collision/CollisionProxy.d.ts.map +1 -0
- package/dist/esm/collision/Manifold.d.ts +5 -4
- package/dist/esm/collision/Manifold.d.ts.map +1 -0
- package/dist/esm/collision/Manifold.js +34 -25
- package/dist/esm/collision/Manifold.js.map +1 -1
- package/dist/esm/collision/chainAdjacency.d.ts +29 -0
- package/dist/esm/collision/chainAdjacency.d.ts.map +1 -0
- package/dist/esm/collision/chainAdjacency.js +44 -0
- package/dist/esm/collision/chainAdjacency.js.map +1 -0
- package/dist/esm/collision/dispatch.d.ts +35 -0
- package/dist/esm/collision/dispatch.d.ts.map +1 -0
- package/dist/esm/collision/dispatch.js +58 -0
- package/dist/esm/collision/dispatch.js.map +1 -0
- package/dist/esm/collision/index.d.ts +2 -0
- package/dist/esm/collision/index.d.ts.map +1 -0
- package/dist/esm/collision/narrowphase.d.ts +5 -2
- package/dist/esm/collision/narrowphase.d.ts.map +1 -0
- package/dist/esm/collision/narrowphase.js +524 -399
- package/dist/esm/collision/narrowphase.js.map +1 -1
- package/dist/esm/collision/segments.d.ts +19 -0
- package/dist/esm/collision/segments.d.ts.map +1 -0
- package/dist/esm/collision/segments.js +36 -0
- package/dist/esm/collision/segments.js.map +1 -0
- package/dist/esm/collision/sweep.d.ts +35 -0
- package/dist/esm/collision/sweep.d.ts.map +1 -0
- package/dist/esm/collision/sweep.js +437 -0
- package/dist/esm/collision/sweep.js.map +1 -0
- package/dist/esm/debug/PhysicsDebugDraw.d.ts +23 -4
- package/dist/esm/debug/PhysicsDebugDraw.d.ts.map +1 -0
- package/dist/esm/debug/PhysicsDebugDraw.js +230 -188
- package/dist/esm/debug/PhysicsDebugDraw.js.map +1 -1
- package/dist/esm/debug/index.d.ts +1 -0
- package/dist/esm/debug/index.d.ts.map +1 -0
- package/dist/esm/debug/index.js +3 -2
- package/dist/esm/events.d.ts +1 -0
- package/dist/esm/events.d.ts.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +24 -19
- package/dist/esm/joints/DistanceJoint.d.ts +4 -3
- package/dist/esm/joints/DistanceJoint.d.ts.map +1 -0
- package/dist/esm/joints/DistanceJoint.js +160 -172
- package/dist/esm/joints/DistanceJoint.js.map +1 -1
- package/dist/esm/joints/Joint.d.ts +5 -4
- package/dist/esm/joints/Joint.d.ts.map +1 -0
- package/dist/esm/joints/Joint.js +23 -21
- package/dist/esm/joints/Joint.js.map +1 -1
- package/dist/esm/joints/MouseJoint.d.ts +8 -7
- package/dist/esm/joints/MouseJoint.d.ts.map +1 -0
- package/dist/esm/joints/MouseJoint.js +132 -133
- package/dist/esm/joints/MouseJoint.js.map +1 -1
- package/dist/esm/joints/PrismaticJoint.d.ts +5 -4
- package/dist/esm/joints/PrismaticJoint.d.ts.map +1 -0
- package/dist/esm/joints/PrismaticJoint.js +211 -240
- package/dist/esm/joints/PrismaticJoint.js.map +1 -1
- package/dist/esm/joints/RevoluteJoint.d.ts +4 -3
- package/dist/esm/joints/RevoluteJoint.d.ts.map +1 -0
- package/dist/esm/joints/RevoluteJoint.js +191 -213
- package/dist/esm/joints/RevoluteJoint.js.map +1 -1
- package/dist/esm/joints/WeldJoint.d.ts +3 -2
- package/dist/esm/joints/WeldJoint.d.ts.map +1 -0
- package/dist/esm/joints/WeldJoint.js +156 -152
- package/dist/esm/joints/WeldJoint.js.map +1 -1
- package/dist/esm/joints/WheelJoint.d.ts +4 -3
- package/dist/esm/joints/WheelJoint.d.ts.map +1 -0
- package/dist/esm/joints/WheelJoint.js +228 -255
- package/dist/esm/joints/WheelJoint.js.map +1 -1
- package/dist/esm/math.d.ts +6 -9
- package/dist/esm/math.d.ts.map +1 -0
- package/dist/esm/math.js +37 -39
- package/dist/esm/math.js.map +1 -1
- package/dist/esm/physicsBuildInfo.d.ts +1 -0
- package/dist/esm/physicsBuildInfo.d.ts.map +1 -0
- package/dist/esm/physicsBuildInfo.js +6 -4
- package/dist/esm/physicsBuildInfo.js.map +1 -1
- package/dist/esm/public.d.ts +8 -3
- package/dist/esm/public.d.ts.map +1 -0
- package/dist/esm/query/QueryEngine.d.ts +56 -12
- package/dist/esm/query/QueryEngine.d.ts.map +1 -0
- package/dist/esm/query/QueryEngine.js +494 -237
- package/dist/esm/query/QueryEngine.js.map +1 -1
- package/dist/esm/query/SpatialIndex.d.ts +34 -0
- package/dist/esm/query/SpatialIndex.d.ts.map +1 -0
- package/dist/esm/shapes/AnyShape.d.ts +9 -4
- package/dist/esm/shapes/AnyShape.d.ts.map +1 -0
- package/dist/esm/shapes/BoxShape.d.ts +2 -1
- package/dist/esm/shapes/BoxShape.d.ts.map +1 -0
- package/dist/esm/shapes/BoxShape.js +37 -25
- package/dist/esm/shapes/BoxShape.js.map +1 -1
- package/dist/esm/shapes/CapsuleShape.d.ts +30 -0
- package/dist/esm/shapes/CapsuleShape.d.ts.map +1 -0
- package/dist/esm/shapes/CapsuleShape.js +88 -0
- package/dist/esm/shapes/CapsuleShape.js.map +1 -0
- package/dist/esm/shapes/ChainEdgeShape.d.ts +30 -0
- package/dist/esm/shapes/ChainEdgeShape.d.ts.map +1 -0
- package/dist/esm/shapes/ChainEdgeShape.js +66 -0
- package/dist/esm/shapes/ChainEdgeShape.js.map +1 -0
- package/dist/esm/shapes/ChainShape.d.ts +52 -0
- package/dist/esm/shapes/ChainShape.d.ts.map +1 -0
- package/dist/esm/shapes/ChainShape.js +85 -0
- package/dist/esm/shapes/ChainShape.js.map +1 -0
- package/dist/esm/shapes/CircleShape.d.ts +3 -4
- package/dist/esm/shapes/CircleShape.d.ts.map +1 -0
- package/dist/esm/shapes/CircleShape.js +27 -26
- package/dist/esm/shapes/CircleShape.js.map +1 -1
- package/dist/esm/shapes/PolygonShape.d.ts +7 -8
- package/dist/esm/shapes/PolygonShape.d.ts.map +1 -0
- package/dist/esm/shapes/PolygonShape.js +131 -162
- package/dist/esm/shapes/PolygonShape.js.map +1 -1
- package/dist/esm/shapes/SegmentShape.d.ts +31 -0
- package/dist/esm/shapes/SegmentShape.d.ts.map +1 -0
- package/dist/esm/shapes/SegmentShape.js +62 -0
- package/dist/esm/shapes/SegmentShape.js.map +1 -0
- package/dist/esm/shapes/Shape.d.ts +28 -17
- package/dist/esm/shapes/Shape.d.ts.map +1 -0
- package/dist/esm/shapes/Shape.js +12 -13
- package/dist/esm/shapes/Shape.js.map +1 -1
- package/dist/esm/shapes/convexParts.d.ts +21 -0
- package/dist/esm/shapes/convexParts.d.ts.map +1 -0
- package/dist/esm/shapes/convexParts.js +26 -0
- package/dist/esm/shapes/convexParts.js.map +1 -0
- package/dist/esm/shapes/decompose.d.ts +24 -0
- package/dist/esm/shapes/decompose.d.ts.map +1 -0
- package/dist/esm/shapes/decompose.js +272 -0
- package/dist/esm/shapes/decompose.js.map +1 -0
- package/dist/esm/shapes/index.d.ts +4 -0
- package/dist/esm/shapes/index.d.ts.map +1 -0
- package/dist/esm/solver/ContactSolver.d.ts +12 -5
- package/dist/esm/solver/ContactSolver.d.ts.map +1 -0
- package/dist/esm/solver/ContactSolver.js +425 -471
- package/dist/esm/solver/ContactSolver.js.map +1 -1
- package/dist/esm/solver/tolerances.d.ts +34 -0
- package/dist/esm/solver/tolerances.d.ts.map +1 -0
- package/dist/esm/solver/tolerances.js +38 -0
- package/dist/esm/solver/tolerances.js.map +1 -0
- package/dist/esm/sort.d.ts +5 -4
- package/dist/esm/sort.d.ts.map +1 -0
- package/dist/esm/sort.js +41 -51
- package/dist/esm/sort.js.map +1 -1
- package/dist/esm/types.d.ts +4 -8
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +15 -17
- package/dist/esm/types.js.map +1 -1
- package/package.json +6 -6
- package/dist/esm/broadphase/SweepAndPrune.d.ts +0 -15
- package/dist/esm/broadphase/SweepAndPrune.js +0 -77
- package/dist/esm/broadphase/SweepAndPrune.js.map +0 -1
- package/dist/esm/debug/index.js.map +0 -1
- package/dist/esm/index.js.map +0 -1
|
@@ -1,255 +1,512 @@
|
|
|
1
|
-
import { aabbOverlap,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { aabbContainsPoint, aabbOverlap, createAabb } from "../Aabb.js";
|
|
2
|
+
import { applyRotation, applyTransform, createTransform } from "../math.js";
|
|
3
|
+
import { resolveFilter, shouldCollide } from "../types.js";
|
|
4
|
+
import { authoredCollider } from "../Collider.js";
|
|
5
|
+
import { closestPointOnSegment, pointSegmentDistanceSquared } from "../collision/segments.js";
|
|
6
|
+
import { testOverlap } from "../collision/narrowphase.js";
|
|
5
7
|
|
|
8
|
+
//#region src/query/QueryEngine.ts
|
|
9
|
+
/** Reused sink for the closest-point primitives; queries run sequentially. */
|
|
10
|
+
const _pointScratch = {
|
|
11
|
+
x: 0,
|
|
12
|
+
y: 0
|
|
13
|
+
};
|
|
6
14
|
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
+
* Spatial queries over the world's live collider set. The engine holds a
|
|
16
|
+
* reference to the world's collider array (kept world-synchronised on every
|
|
17
|
+
* body move), so queries always see current placements. Array-returning queries
|
|
18
|
+
* follow the three explicit allocation forms: fresh array, caller-owned `out`,
|
|
19
|
+
* or an allocation-free callback - never a hidden shared buffer.
|
|
20
|
+
*
|
|
21
|
+
* The engine scans the geometry the broad phase holds, which for a chain
|
|
22
|
+
* collider is its per-edge proxies. Every result is reported as the authored
|
|
23
|
+
* collider, and an overlap query reports a chain once however many of its edges
|
|
24
|
+
* it touches. A ray still reports one hit per edge it crosses, because those are
|
|
25
|
+
* distinct intersections.
|
|
26
|
+
*/
|
|
27
|
+
var QueryEngine = class {
|
|
28
|
+
_colliders;
|
|
29
|
+
_spatialIndex;
|
|
30
|
+
_scratchHits = [];
|
|
31
|
+
_scratchHitsForEach = [];
|
|
32
|
+
/** Chains already reported by the running `forEachAabbHit`, so one chain is one callback. */
|
|
33
|
+
_scratchSeenForEach = [];
|
|
34
|
+
_scratchAabb = createAabb();
|
|
35
|
+
constructor(colliders, spatialIndex) {
|
|
36
|
+
this._colliders = colliders;
|
|
37
|
+
this._spatialIndex = spatialIndex;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Colliders to scan for a query over `bounds` - the spatial index's narrowed
|
|
41
|
+
* result if one is wired, else every live collider. `sync` is called first so
|
|
42
|
+
* colliders added/moved since the index's last detection pass (including
|
|
43
|
+
* before the world's very first `step()`) are still found.
|
|
44
|
+
*
|
|
45
|
+
* `buffer` is the scratch array the spatial index writes candidates into
|
|
46
|
+
* (cleared and refilled in place). Callers whose per-candidate loop can
|
|
47
|
+
* re-enter `_candidatesFor` - directly or via caller-supplied callback code -
|
|
48
|
+
* must pass a buffer dedicated to them, so a nested call refilling its own
|
|
49
|
+
* buffer can't truncate/corrupt an outer call's still-live iteration.
|
|
50
|
+
*/
|
|
51
|
+
_candidatesFor(bounds, buffer) {
|
|
52
|
+
if (this._spatialIndex === void 0) return this._colliders;
|
|
53
|
+
this._spatialIndex.sync(this._colliders);
|
|
54
|
+
return this._spatialIndex.queryAabb(bounds, buffer);
|
|
55
|
+
}
|
|
56
|
+
/** Colliders containing `point`. Allocates a fresh array. */
|
|
57
|
+
queryPoint(point, filter) {
|
|
58
|
+
const out = [];
|
|
59
|
+
const resolved = filter ? resolveFilter(filter) : null;
|
|
60
|
+
const bounds = this._scratchAabb;
|
|
61
|
+
bounds.minX = point.x;
|
|
62
|
+
bounds.minY = point.y;
|
|
63
|
+
bounds.maxX = point.x;
|
|
64
|
+
bounds.maxY = point.y;
|
|
65
|
+
for (const collider of this._candidatesFor(bounds, this._scratchHits)) {
|
|
66
|
+
if (resolved && !shouldCollide(resolved, collider.filter)) continue;
|
|
67
|
+
if (pointInCollider(collider, point.x, point.y)) out.push(collider);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** Colliders whose AABB overlaps `bounds`. Writes into `out` (cleared first) when given, else a fresh array. */
|
|
72
|
+
queryAabb(bounds, filter, out) {
|
|
73
|
+
const result = out ?? [];
|
|
74
|
+
result.length = 0;
|
|
75
|
+
const resolved = filter ? resolveFilter(filter) : null;
|
|
76
|
+
for (const collider of this._candidatesFor(bounds, this._scratchHits)) {
|
|
77
|
+
const authored = authoredCollider(collider);
|
|
78
|
+
if (resolved && !shouldCollide(resolved, authored.filter)) continue;
|
|
79
|
+
if (aabbOverlap(collider.aabb, bounds) && !alreadyReported(collider, authored, result)) result.push(authored);
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Invoke `callback` for each collider whose AABB overlaps `bounds`. Allocation-free.
|
|
85
|
+
*
|
|
86
|
+
* NOT re-entrant on itself: `callback` runs mid-traversal over a scratch
|
|
87
|
+
* buffer dedicated to this method, so it may safely call `queryPoint`,
|
|
88
|
+
* `queryAabb`, or `overlapShape` on this same `PhysicsWorld`/`QueryEngine`
|
|
89
|
+
* (they use a separate buffer and never invoke caller code mid-loop). It
|
|
90
|
+
* must NOT call `forEachAabbHit` again - directly or indirectly - on the
|
|
91
|
+
* same instance: the nested call would refill the same shared buffer this
|
|
92
|
+
* traversal is still iterating, silently truncating/corrupting it. This
|
|
93
|
+
* mirrors `DynamicAabbTree.query()`'s own non-reentrancy contract.
|
|
94
|
+
*/
|
|
95
|
+
forEachAabbHit(bounds, filter, callback) {
|
|
96
|
+
const resolved = filter ? resolveFilter(filter) : null;
|
|
97
|
+
const reported = this._scratchSeenForEach;
|
|
98
|
+
reported.length = 0;
|
|
99
|
+
for (const collider of this._candidatesFor(bounds, this._scratchHitsForEach)) {
|
|
100
|
+
const authored = authoredCollider(collider);
|
|
101
|
+
if (resolved && !shouldCollide(resolved, authored.filter)) continue;
|
|
102
|
+
if (!aabbOverlap(collider.aabb, bounds) || alreadyReported(collider, authored, reported)) continue;
|
|
103
|
+
if (collider !== authored) reported.push(authored);
|
|
104
|
+
callback(authored);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Nearest collider hit by the ray from `origin` along `direction`, or `null`. */
|
|
108
|
+
rayCast(origin, direction, filter, maxDistance = Infinity) {
|
|
109
|
+
const length = Math.hypot(direction.x, direction.y);
|
|
110
|
+
if (length < 1e-9) throw new RangeError("QueryEngine.rayCast: direction must be non-zero.");
|
|
111
|
+
const dx = direction.x / length;
|
|
112
|
+
const dy = direction.y / length;
|
|
113
|
+
const resolved = filter ? resolveFilter(filter) : null;
|
|
114
|
+
let best = null;
|
|
115
|
+
const consider = (collider) => {
|
|
116
|
+
const authored = authoredCollider(collider);
|
|
117
|
+
if (resolved && !shouldCollide(resolved, authored.filter)) return;
|
|
118
|
+
const hit = rayCastCollider(collider, origin.x, origin.y, dx, dy, best ? best.distance : maxDistance);
|
|
119
|
+
if (hit && (best === null || hit.distance < best.distance)) {
|
|
120
|
+
hit.collider = authored;
|
|
121
|
+
best = hit;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
if (this._spatialIndex === void 0) for (const collider of this._colliders) consider(collider);
|
|
125
|
+
else {
|
|
126
|
+
this._spatialIndex.sync(this._colliders);
|
|
127
|
+
this._spatialIndex.rayCast(origin.x, origin.y, dx, dy, maxDistance, consider);
|
|
128
|
+
}
|
|
129
|
+
return best;
|
|
130
|
+
}
|
|
131
|
+
/** All collider hits along the ray, sorted by distance. Writes into `out` (cleared first) when given. */
|
|
132
|
+
rayCastAll(origin, direction, filter, out, maxDistance = Infinity) {
|
|
133
|
+
const length = Math.hypot(direction.x, direction.y);
|
|
134
|
+
if (length < 1e-9) throw new RangeError("QueryEngine.rayCastAll: direction must be non-zero.");
|
|
135
|
+
const dx = direction.x / length;
|
|
136
|
+
const dy = direction.y / length;
|
|
137
|
+
const resolved = filter ? resolveFilter(filter) : null;
|
|
138
|
+
const result = out ?? [];
|
|
139
|
+
result.length = 0;
|
|
140
|
+
const consider = (collider) => {
|
|
141
|
+
const authored = authoredCollider(collider);
|
|
142
|
+
if (resolved && !shouldCollide(resolved, authored.filter)) return;
|
|
143
|
+
const hit = rayCastCollider(collider, origin.x, origin.y, dx, dy, maxDistance);
|
|
144
|
+
if (hit) {
|
|
145
|
+
hit.collider = authored;
|
|
146
|
+
result.push(hit);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
if (this._spatialIndex === void 0) for (const collider of this._colliders) consider(collider);
|
|
150
|
+
else {
|
|
151
|
+
this._spatialIndex.sync(this._colliders);
|
|
152
|
+
this._spatialIndex.rayCast(origin.x, origin.y, dx, dy, maxDistance, consider);
|
|
153
|
+
}
|
|
154
|
+
result.sort((a, b) => a.distance - b.distance);
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Colliders overlapping `shape` placed at `position`/`angle`. Allocates a
|
|
159
|
+
* fresh array.
|
|
160
|
+
*
|
|
161
|
+
* A chain query shape is tested edge by edge, exactly as a chain collider is
|
|
162
|
+
* solved. Two boundaries have no overlap volume, so a chain - like a segment -
|
|
163
|
+
* never reports another chain or segment.
|
|
164
|
+
*/
|
|
165
|
+
overlapShape(shape, position, filter, angle = 0) {
|
|
166
|
+
const proxies = queryProxies(shape, position.x, position.y, angle);
|
|
167
|
+
const out = [];
|
|
168
|
+
const resolved = filter ? resolveFilter(filter) : null;
|
|
169
|
+
const bounds = proxiesAabb(shape, proxies, this._scratchAabb);
|
|
170
|
+
for (const collider of this._candidatesFor(bounds, this._scratchHits)) {
|
|
171
|
+
const authored = authoredCollider(collider);
|
|
172
|
+
if (resolved && !shouldCollide(resolved, authored.filter)) continue;
|
|
173
|
+
if (alreadyReported(collider, authored, out)) continue;
|
|
174
|
+
for (const proxy of proxies) if (testOverlap(proxy, collider)) {
|
|
175
|
+
out.push(authored);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
119
182
|
/** Read a flat vertex/normal buffer in-bounds: callers index within 0..count-1. */
|
|
120
183
|
/** `true` when world point `(px, py)` lies inside `collider`'s shape. */
|
|
121
184
|
const pointInCollider = (collider, px, py) => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
185
|
+
if (!aabbContainsPoint(collider.aabb, px, py)) return false;
|
|
186
|
+
if (collider.shape.type === "circle") {
|
|
187
|
+
const c = collider.worldCenter;
|
|
188
|
+
const r = collider.shape.radius;
|
|
189
|
+
const dx = px - c.x;
|
|
190
|
+
const dy = py - c.y;
|
|
191
|
+
return dx * dx + dy * dy <= r * r;
|
|
192
|
+
}
|
|
193
|
+
if (collider.shape.type === "chain") return false;
|
|
194
|
+
if (collider.shape.type === "segment") return false;
|
|
195
|
+
if (collider.shape.type === "capsule") {
|
|
196
|
+
const spine = collider.worldVertices;
|
|
197
|
+
return pointSegmentDistanceSquared(px, py, spine[0], spine[1], spine[2], spine[3], _pointScratch) <= collider.shape.radius * collider.shape.radius;
|
|
198
|
+
}
|
|
199
|
+
const verts = collider.worldVertices;
|
|
200
|
+
const normals = collider.worldNormals;
|
|
201
|
+
const count = collider.shape.count;
|
|
202
|
+
for (let i = 0; i < count; i++) {
|
|
203
|
+
const nx = normals[i * 2];
|
|
204
|
+
const ny = normals[i * 2 + 1];
|
|
205
|
+
const vx = verts[i * 2];
|
|
206
|
+
const vy = verts[i * 2 + 1];
|
|
207
|
+
if (nx * (px - vx) + ny * (py - vy) > 0) return false;
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
145
210
|
};
|
|
146
211
|
/** Cast the (normalised) ray against one collider, returning the entry hit or `null`. */
|
|
147
212
|
const rayCastCollider = (collider, ox, oy, dx, dy, maxDistance) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
213
|
+
switch (collider.shape.type) {
|
|
214
|
+
case "circle": return rayCastCircle(collider, ox, oy, dx, dy, maxDistance);
|
|
215
|
+
case "capsule": return rayCastCapsule(collider, collider.shape.radius, ox, oy, dx, dy, maxDistance);
|
|
216
|
+
case "segment": return rayCastSegment(collider, ox, oy, dx, dy, maxDistance);
|
|
217
|
+
case "polygon": return rayCastPolygon(collider, ox, oy, dx, dy, maxDistance);
|
|
218
|
+
case "chain": return null;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Ray against a segment: a plain segment/segment crossing. The reported normal is
|
|
223
|
+
* the segment's own side normal, flipped to face the incoming ray - a boundary
|
|
224
|
+
* blocks from both sides, so which of its two normals applies is decided by where
|
|
225
|
+
* the ray comes from.
|
|
226
|
+
*/
|
|
227
|
+
const rayCastSegment = (collider, ox, oy, dx, dy, maxDistance) => {
|
|
228
|
+
const ends = collider.worldVertices;
|
|
229
|
+
const ax = ends[0];
|
|
230
|
+
const ay = ends[1];
|
|
231
|
+
const ex = ends[2] - ax;
|
|
232
|
+
const ey = ends[3] - ay;
|
|
233
|
+
const denominator = dx * ey - dy * ex;
|
|
234
|
+
if (Math.abs(denominator) < 1e-12) return null;
|
|
235
|
+
const rx = ax - ox;
|
|
236
|
+
const ry = ay - oy;
|
|
237
|
+
const distance = (rx * ey - ry * ex) / denominator;
|
|
238
|
+
const along = (rx * dy - ry * dx) / denominator;
|
|
239
|
+
if (distance < 0 || distance > maxDistance || along < 0 || along > 1) return null;
|
|
240
|
+
const nx = collider.worldNormals[0];
|
|
241
|
+
const ny = collider.worldNormals[1];
|
|
242
|
+
const facing = nx * dx + ny * dy > 0 ? -1 : 1;
|
|
243
|
+
return {
|
|
244
|
+
collider,
|
|
245
|
+
body: collider.body,
|
|
246
|
+
point: {
|
|
247
|
+
x: ox + dx * distance,
|
|
248
|
+
y: oy + dy * distance
|
|
249
|
+
},
|
|
250
|
+
normal: {
|
|
251
|
+
x: nx * facing,
|
|
252
|
+
y: ny * facing
|
|
253
|
+
},
|
|
254
|
+
distance
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Ray against a capsule: the first point along the ray whose distance to the
|
|
259
|
+
* spine equals the radius.
|
|
260
|
+
*
|
|
261
|
+
* Found by scanning for the first sample inside and bisecting the bracket that
|
|
262
|
+
* produced it, rather than by three closed-form feature tests (two caps and the
|
|
263
|
+
* side slab). The distance-to-spine function is continuous and, approaching the
|
|
264
|
+
* capsule from outside, monotone up to the first touch, so the bracket is valid;
|
|
265
|
+
* a fixed 40 bisections put the result far below the engine's contact slop. The
|
|
266
|
+
* cost is a query-path concern only - the solver never runs this.
|
|
267
|
+
*/
|
|
268
|
+
const rayCastCapsule = (collider, radius, ox, oy, dx, dy, maxDistance) => {
|
|
269
|
+
const spine = collider.worldVertices;
|
|
270
|
+
const ax = spine[0];
|
|
271
|
+
const ay = spine[1];
|
|
272
|
+
const bx = spine[2];
|
|
273
|
+
const by = spine[3];
|
|
274
|
+
const radiusSquared = radius * radius;
|
|
275
|
+
if (pointSegmentDistanceSquared(ox, oy, ax, ay, bx, by, _pointScratch) <= radiusSquared) return null;
|
|
276
|
+
const box = collider.aabb;
|
|
277
|
+
let near = 0;
|
|
278
|
+
let far = maxDistance;
|
|
279
|
+
for (let axis = 0; axis < 2; axis++) {
|
|
280
|
+
const origin = axis === 0 ? ox : oy;
|
|
281
|
+
const direction = axis === 0 ? dx : dy;
|
|
282
|
+
const lo = axis === 0 ? box.minX : box.minY;
|
|
283
|
+
const hi = axis === 0 ? box.maxX : box.maxY;
|
|
284
|
+
if (Math.abs(direction) < 1e-12) {
|
|
285
|
+
if (origin < lo || origin > hi) return null;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const t0 = (lo - origin) / direction;
|
|
289
|
+
const t1 = (hi - origin) / direction;
|
|
290
|
+
near = Math.max(near, Math.min(t0, t1));
|
|
291
|
+
far = Math.min(far, Math.max(t0, t1));
|
|
292
|
+
}
|
|
293
|
+
if (near > far) return null;
|
|
294
|
+
const steps = 32;
|
|
295
|
+
const stepLength = (far - near) / steps;
|
|
296
|
+
let entered = -1;
|
|
297
|
+
for (let i = 1; i <= steps; i++) {
|
|
298
|
+
const t = near + i * stepLength;
|
|
299
|
+
if (pointSegmentDistanceSquared(ox + dx * t, oy + dy * t, ax, ay, bx, by, _pointScratch) <= radiusSquared) {
|
|
300
|
+
entered = t;
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (entered < 0) return null;
|
|
305
|
+
let lo = entered - stepLength;
|
|
306
|
+
let hi = entered;
|
|
307
|
+
for (let i = 0; i < 40; i++) {
|
|
308
|
+
const mid = (lo + hi) / 2;
|
|
309
|
+
if (pointSegmentDistanceSquared(ox + dx * mid, oy + dy * mid, ax, ay, bx, by, _pointScratch) <= radiusSquared) hi = mid;
|
|
310
|
+
else lo = mid;
|
|
311
|
+
}
|
|
312
|
+
const hitX = ox + dx * hi;
|
|
313
|
+
const hitY = oy + dy * hi;
|
|
314
|
+
closestPointOnSegment(hitX, hitY, ax, ay, bx, by, _pointScratch);
|
|
315
|
+
const nx = hitX - _pointScratch.x;
|
|
316
|
+
const ny = hitY - _pointScratch.y;
|
|
317
|
+
const length = Math.hypot(nx, ny) || 1;
|
|
318
|
+
return {
|
|
319
|
+
collider,
|
|
320
|
+
body: collider.body,
|
|
321
|
+
point: {
|
|
322
|
+
x: hitX,
|
|
323
|
+
y: hitY
|
|
324
|
+
},
|
|
325
|
+
normal: {
|
|
326
|
+
x: nx / length,
|
|
327
|
+
y: ny / length
|
|
328
|
+
},
|
|
329
|
+
distance: hi
|
|
330
|
+
};
|
|
152
331
|
};
|
|
153
332
|
const rayCastCircle = (collider, ox, oy, dx, dy, maxDistance) => {
|
|
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
|
-
|
|
333
|
+
const c = collider.worldCenter;
|
|
334
|
+
const r = collider.shape.type === "circle" ? collider.shape.radius : 0;
|
|
335
|
+
const mx = ox - c.x;
|
|
336
|
+
const my = oy - c.y;
|
|
337
|
+
const b = mx * dx + my * dy;
|
|
338
|
+
const cc = mx * mx + my * my - r * r;
|
|
339
|
+
if (cc > 0 && b > 0) return null;
|
|
340
|
+
const disc = b * b - cc;
|
|
341
|
+
if (disc < 0) return null;
|
|
342
|
+
let t = -b - Math.sqrt(disc);
|
|
343
|
+
if (t < 0) t = 0;
|
|
344
|
+
if (t > maxDistance) return null;
|
|
345
|
+
const px = ox + dx * t;
|
|
346
|
+
const py = oy + dy * t;
|
|
347
|
+
const nx = (px - c.x) / r;
|
|
348
|
+
const ny = (py - c.y) / r;
|
|
349
|
+
return {
|
|
350
|
+
collider,
|
|
351
|
+
body: collider.body,
|
|
352
|
+
point: {
|
|
353
|
+
x: px,
|
|
354
|
+
y: py
|
|
355
|
+
},
|
|
356
|
+
normal: {
|
|
357
|
+
x: nx,
|
|
358
|
+
y: ny
|
|
359
|
+
},
|
|
360
|
+
distance: t
|
|
361
|
+
};
|
|
181
362
|
};
|
|
182
363
|
const rayCastPolygon = (collider, ox, oy, dx, dy, maxDistance) => {
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
collider,
|
|
229
|
-
body: collider.body,
|
|
230
|
-
point: { x: ox + dx * tmin, y: oy + dy * tmin },
|
|
231
|
-
normal: { x: enterNx, y: enterNy },
|
|
232
|
-
distance: tmin,
|
|
233
|
-
};
|
|
364
|
+
const verts = collider.worldVertices;
|
|
365
|
+
const normals = collider.worldNormals;
|
|
366
|
+
const count = collider.shape.type === "polygon" ? collider.shape.count : 0;
|
|
367
|
+
let tmin = 0;
|
|
368
|
+
let tmax = maxDistance;
|
|
369
|
+
let enterNx = 0;
|
|
370
|
+
let enterNy = 0;
|
|
371
|
+
let entered = false;
|
|
372
|
+
for (let i = 0; i < count; i++) {
|
|
373
|
+
const nx = normals[i * 2];
|
|
374
|
+
const ny = normals[i * 2 + 1];
|
|
375
|
+
const vx = verts[i * 2];
|
|
376
|
+
const vy = verts[i * 2 + 1];
|
|
377
|
+
const numerator = nx * (vx - ox) + ny * (vy - oy);
|
|
378
|
+
const denominator = nx * dx + ny * dy;
|
|
379
|
+
if (denominator === 0) {
|
|
380
|
+
if (numerator < 0) return null;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const t = numerator / denominator;
|
|
384
|
+
if (denominator < 0) {
|
|
385
|
+
if (t > tmin) {
|
|
386
|
+
tmin = t;
|
|
387
|
+
enterNx = nx;
|
|
388
|
+
enterNy = ny;
|
|
389
|
+
entered = true;
|
|
390
|
+
}
|
|
391
|
+
} else if (t < tmax) tmax = t;
|
|
392
|
+
if (tmin > tmax) return null;
|
|
393
|
+
}
|
|
394
|
+
if (!entered || tmin < 0 || tmin > maxDistance) return null;
|
|
395
|
+
return {
|
|
396
|
+
collider,
|
|
397
|
+
body: collider.body,
|
|
398
|
+
point: {
|
|
399
|
+
x: ox + dx * tmin,
|
|
400
|
+
y: oy + dy * tmin
|
|
401
|
+
},
|
|
402
|
+
normal: {
|
|
403
|
+
x: enterNx,
|
|
404
|
+
y: enterNy
|
|
405
|
+
},
|
|
406
|
+
distance: tmin
|
|
407
|
+
};
|
|
234
408
|
};
|
|
409
|
+
/**
|
|
410
|
+
* Throwaway collision proxies for a query shape placed at `(x, y)` with `angle`:
|
|
411
|
+
* one, or one per edge for a chain.
|
|
412
|
+
*/
|
|
413
|
+
const queryProxies = (shape, x, y, angle) => {
|
|
414
|
+
if (shape.type !== "chain") return [makeProxy(shape, x, y, angle)];
|
|
415
|
+
return shape.edges.map((edge) => makeProxy(edge, x, y, angle));
|
|
416
|
+
};
|
|
417
|
+
/** World AABB covering every proxy of a query shape. */
|
|
418
|
+
const proxiesAabb = (shape, proxies, out) => {
|
|
419
|
+
if (shape.type !== "chain") return proxyAabb(shape, proxies[0], out);
|
|
420
|
+
let minX = Infinity;
|
|
421
|
+
let minY = Infinity;
|
|
422
|
+
let maxX = -Infinity;
|
|
423
|
+
let maxY = -Infinity;
|
|
424
|
+
for (const proxy of proxies) {
|
|
425
|
+
const edgeBounds = proxyAabb(proxy.shape, proxy, _scratchEdgeAabb);
|
|
426
|
+
minX = edgeBounds.minX < minX ? edgeBounds.minX : minX;
|
|
427
|
+
minY = edgeBounds.minY < minY ? edgeBounds.minY : minY;
|
|
428
|
+
maxX = edgeBounds.maxX > maxX ? edgeBounds.maxX : maxX;
|
|
429
|
+
maxY = edgeBounds.maxY > maxY ? edgeBounds.maxY : maxY;
|
|
430
|
+
}
|
|
431
|
+
out.minX = minX;
|
|
432
|
+
out.minY = minY;
|
|
433
|
+
out.maxX = maxX;
|
|
434
|
+
out.maxY = maxY;
|
|
435
|
+
return out;
|
|
436
|
+
};
|
|
437
|
+
/** Scratch for the per-edge bounds a chain query shape unions. */
|
|
438
|
+
const _scratchEdgeAabb = createAabb();
|
|
439
|
+
/**
|
|
440
|
+
* `true` when `authored` is already in `reported`. Only a chain can be reached
|
|
441
|
+
* twice in one query (once per edge proxy), so nothing else pays for the scan.
|
|
442
|
+
*/
|
|
443
|
+
const alreadyReported = (candidate, authored, reported) => candidate !== authored && reported.includes(authored);
|
|
235
444
|
/** Build a throwaway collision proxy for a shape placed at `(x, y)` with `angle`. */
|
|
236
445
|
const makeProxy = (shape, x, y, angle) => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
446
|
+
if (shape.type === "circle") return {
|
|
447
|
+
shape,
|
|
448
|
+
worldCenter: {
|
|
449
|
+
x,
|
|
450
|
+
y
|
|
451
|
+
},
|
|
452
|
+
worldVertices: [],
|
|
453
|
+
worldNormals: []
|
|
454
|
+
};
|
|
455
|
+
const count = shape.type === "polygon" ? shape.count : 2;
|
|
456
|
+
const transform = createTransform(x, y, angle);
|
|
457
|
+
const worldVertices = [];
|
|
458
|
+
const worldNormals = [];
|
|
459
|
+
const out = {
|
|
460
|
+
x: 0,
|
|
461
|
+
y: 0
|
|
462
|
+
};
|
|
463
|
+
for (let i = 0; i < count; i++) {
|
|
464
|
+
applyTransform(transform, shape.vertices[i * 2], shape.vertices[i * 2 + 1], out);
|
|
465
|
+
worldVertices.push(out.x, out.y);
|
|
466
|
+
applyRotation(transform, shape.normals[i * 2], shape.normals[i * 2 + 1], out);
|
|
467
|
+
worldNormals.push(out.x, out.y);
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
shape,
|
|
471
|
+
worldCenter: {
|
|
472
|
+
x,
|
|
473
|
+
y
|
|
474
|
+
},
|
|
475
|
+
worldVertices,
|
|
476
|
+
worldNormals
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
/** Compute the world AABB of an already-built collision proxy (reuses its cached vertices/centre - no extra transform work). */
|
|
480
|
+
const proxyAabb = (shape, proxy, out) => {
|
|
481
|
+
if (shape.type === "circle") {
|
|
482
|
+
const r = shape.radius;
|
|
483
|
+
out.minX = proxy.worldCenter.x - r;
|
|
484
|
+
out.minY = proxy.worldCenter.y - r;
|
|
485
|
+
out.maxX = proxy.worldCenter.x + r;
|
|
486
|
+
out.maxY = proxy.worldCenter.y + r;
|
|
487
|
+
return out;
|
|
488
|
+
}
|
|
489
|
+
const verts = proxy.worldVertices;
|
|
490
|
+
let minX = Infinity;
|
|
491
|
+
let minY = Infinity;
|
|
492
|
+
let maxX = -Infinity;
|
|
493
|
+
let maxY = -Infinity;
|
|
494
|
+
for (let i = 0; i < verts.length; i += 2) {
|
|
495
|
+
const x = verts[i];
|
|
496
|
+
const y = verts[i + 1];
|
|
497
|
+
minX = x < minX ? x : minX;
|
|
498
|
+
minY = y < minY ? y : minY;
|
|
499
|
+
maxX = x > maxX ? x : maxX;
|
|
500
|
+
maxY = y > maxY ? y : maxY;
|
|
501
|
+
}
|
|
502
|
+
const radius = shape.type === "capsule" ? shape.radius : 0;
|
|
503
|
+
out.minX = minX - radius;
|
|
504
|
+
out.minY = minY - radius;
|
|
505
|
+
out.maxX = maxX + radius;
|
|
506
|
+
out.maxY = maxY + radius;
|
|
507
|
+
return out;
|
|
252
508
|
};
|
|
253
509
|
|
|
510
|
+
//#endregion
|
|
254
511
|
export { QueryEngine };
|
|
255
|
-
//# sourceMappingURL=QueryEngine.js.map
|
|
512
|
+
//# sourceMappingURL=QueryEngine.js.map
|