@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.
Files changed (201) hide show
  1. package/README.md +51 -23
  2. package/dist/esm/Aabb.d.ts +6 -10
  3. package/dist/esm/Aabb.d.ts.map +1 -0
  4. package/dist/esm/Aabb.js +9 -5
  5. package/dist/esm/Aabb.js.map +1 -1
  6. package/dist/esm/Collider.d.ts +62 -10
  7. package/dist/esm/Collider.d.ts.map +1 -0
  8. package/dist/esm/Collider.js +263 -157
  9. package/dist/esm/Collider.js.map +1 -1
  10. package/dist/esm/ContactGraph.d.ts +51 -5
  11. package/dist/esm/ContactGraph.d.ts.map +1 -0
  12. package/dist/esm/ContactGraph.js +282 -223
  13. package/dist/esm/ContactGraph.js.map +1 -1
  14. package/dist/esm/ContactModifier.d.ts +85 -0
  15. package/dist/esm/ContactModifier.d.ts.map +1 -0
  16. package/dist/esm/ContactModifier.js +49 -0
  17. package/dist/esm/ContactModifier.js.map +1 -0
  18. package/dist/esm/PhysicsBody.d.ts +56 -22
  19. package/dist/esm/PhysicsBody.d.ts.map +1 -0
  20. package/dist/esm/PhysicsBody.js +461 -443
  21. package/dist/esm/PhysicsBody.js.map +1 -1
  22. package/dist/esm/PhysicsWorld.d.ts +276 -43
  23. package/dist/esm/PhysicsWorld.d.ts.map +1 -0
  24. package/dist/esm/PhysicsWorld.js +890 -626
  25. package/dist/esm/PhysicsWorld.js.map +1 -1
  26. package/dist/esm/TimeStepper.d.ts +3 -2
  27. package/dist/esm/TimeStepper.d.ts.map +1 -0
  28. package/dist/esm/TimeStepper.js +56 -67
  29. package/dist/esm/TimeStepper.js.map +1 -1
  30. package/dist/esm/backend/NativePhysicsBackend.d.ts +8 -4
  31. package/dist/esm/backend/NativePhysicsBackend.d.ts.map +1 -0
  32. package/dist/esm/backend/NativePhysicsBackend.js +48 -41
  33. package/dist/esm/backend/NativePhysicsBackend.js.map +1 -1
  34. package/dist/esm/backend/PhysicsBackend.d.ts +9 -2
  35. package/dist/esm/backend/PhysicsBackend.d.ts.map +1 -0
  36. package/dist/esm/binding/BindingRegistry.d.ts +25 -4
  37. package/dist/esm/binding/BindingRegistry.d.ts.map +1 -0
  38. package/dist/esm/binding/BindingRegistry.js +70 -36
  39. package/dist/esm/binding/BindingRegistry.js.map +1 -1
  40. package/dist/esm/binding/PhysicsBinding.d.ts +22 -5
  41. package/dist/esm/binding/PhysicsBinding.d.ts.map +1 -0
  42. package/dist/esm/binding/PhysicsBinding.js +46 -21
  43. package/dist/esm/binding/PhysicsBinding.js.map +1 -1
  44. package/dist/esm/broadphase/AabbTreeBroadPhase.d.ts +68 -0
  45. package/dist/esm/broadphase/AabbTreeBroadPhase.d.ts.map +1 -0
  46. package/dist/esm/broadphase/AabbTreeBroadPhase.js +149 -0
  47. package/dist/esm/broadphase/AabbTreeBroadPhase.js.map +1 -0
  48. package/dist/esm/broadphase/BroadPhase.d.ts +7 -4
  49. package/dist/esm/broadphase/BroadPhase.d.ts.map +1 -0
  50. package/dist/esm/broadphase/index.d.ts +2 -1
  51. package/dist/esm/broadphase/index.d.ts.map +1 -0
  52. package/dist/esm/collision/CollisionProxy.d.ts +3 -2
  53. package/dist/esm/collision/CollisionProxy.d.ts.map +1 -0
  54. package/dist/esm/collision/Manifold.d.ts +5 -4
  55. package/dist/esm/collision/Manifold.d.ts.map +1 -0
  56. package/dist/esm/collision/Manifold.js +34 -25
  57. package/dist/esm/collision/Manifold.js.map +1 -1
  58. package/dist/esm/collision/chainAdjacency.d.ts +29 -0
  59. package/dist/esm/collision/chainAdjacency.d.ts.map +1 -0
  60. package/dist/esm/collision/chainAdjacency.js +44 -0
  61. package/dist/esm/collision/chainAdjacency.js.map +1 -0
  62. package/dist/esm/collision/dispatch.d.ts +35 -0
  63. package/dist/esm/collision/dispatch.d.ts.map +1 -0
  64. package/dist/esm/collision/dispatch.js +58 -0
  65. package/dist/esm/collision/dispatch.js.map +1 -0
  66. package/dist/esm/collision/index.d.ts +2 -0
  67. package/dist/esm/collision/index.d.ts.map +1 -0
  68. package/dist/esm/collision/narrowphase.d.ts +5 -2
  69. package/dist/esm/collision/narrowphase.d.ts.map +1 -0
  70. package/dist/esm/collision/narrowphase.js +524 -399
  71. package/dist/esm/collision/narrowphase.js.map +1 -1
  72. package/dist/esm/collision/segments.d.ts +19 -0
  73. package/dist/esm/collision/segments.d.ts.map +1 -0
  74. package/dist/esm/collision/segments.js +36 -0
  75. package/dist/esm/collision/segments.js.map +1 -0
  76. package/dist/esm/collision/sweep.d.ts +35 -0
  77. package/dist/esm/collision/sweep.d.ts.map +1 -0
  78. package/dist/esm/collision/sweep.js +437 -0
  79. package/dist/esm/collision/sweep.js.map +1 -0
  80. package/dist/esm/debug/PhysicsDebugDraw.d.ts +23 -4
  81. package/dist/esm/debug/PhysicsDebugDraw.d.ts.map +1 -0
  82. package/dist/esm/debug/PhysicsDebugDraw.js +230 -188
  83. package/dist/esm/debug/PhysicsDebugDraw.js.map +1 -1
  84. package/dist/esm/debug/index.d.ts +1 -0
  85. package/dist/esm/debug/index.d.ts.map +1 -0
  86. package/dist/esm/debug/index.js +3 -2
  87. package/dist/esm/events.d.ts +1 -0
  88. package/dist/esm/events.d.ts.map +1 -0
  89. package/dist/esm/index.d.ts +1 -0
  90. package/dist/esm/index.d.ts.map +1 -0
  91. package/dist/esm/index.js +24 -19
  92. package/dist/esm/joints/DistanceJoint.d.ts +4 -3
  93. package/dist/esm/joints/DistanceJoint.d.ts.map +1 -0
  94. package/dist/esm/joints/DistanceJoint.js +160 -172
  95. package/dist/esm/joints/DistanceJoint.js.map +1 -1
  96. package/dist/esm/joints/Joint.d.ts +5 -4
  97. package/dist/esm/joints/Joint.d.ts.map +1 -0
  98. package/dist/esm/joints/Joint.js +23 -21
  99. package/dist/esm/joints/Joint.js.map +1 -1
  100. package/dist/esm/joints/MouseJoint.d.ts +8 -7
  101. package/dist/esm/joints/MouseJoint.d.ts.map +1 -0
  102. package/dist/esm/joints/MouseJoint.js +132 -133
  103. package/dist/esm/joints/MouseJoint.js.map +1 -1
  104. package/dist/esm/joints/PrismaticJoint.d.ts +5 -4
  105. package/dist/esm/joints/PrismaticJoint.d.ts.map +1 -0
  106. package/dist/esm/joints/PrismaticJoint.js +211 -240
  107. package/dist/esm/joints/PrismaticJoint.js.map +1 -1
  108. package/dist/esm/joints/RevoluteJoint.d.ts +4 -3
  109. package/dist/esm/joints/RevoluteJoint.d.ts.map +1 -0
  110. package/dist/esm/joints/RevoluteJoint.js +191 -213
  111. package/dist/esm/joints/RevoluteJoint.js.map +1 -1
  112. package/dist/esm/joints/WeldJoint.d.ts +3 -2
  113. package/dist/esm/joints/WeldJoint.d.ts.map +1 -0
  114. package/dist/esm/joints/WeldJoint.js +156 -152
  115. package/dist/esm/joints/WeldJoint.js.map +1 -1
  116. package/dist/esm/joints/WheelJoint.d.ts +4 -3
  117. package/dist/esm/joints/WheelJoint.d.ts.map +1 -0
  118. package/dist/esm/joints/WheelJoint.js +228 -255
  119. package/dist/esm/joints/WheelJoint.js.map +1 -1
  120. package/dist/esm/math.d.ts +6 -9
  121. package/dist/esm/math.d.ts.map +1 -0
  122. package/dist/esm/math.js +37 -39
  123. package/dist/esm/math.js.map +1 -1
  124. package/dist/esm/physicsBuildInfo.d.ts +1 -0
  125. package/dist/esm/physicsBuildInfo.d.ts.map +1 -0
  126. package/dist/esm/physicsBuildInfo.js +6 -4
  127. package/dist/esm/physicsBuildInfo.js.map +1 -1
  128. package/dist/esm/public.d.ts +8 -3
  129. package/dist/esm/public.d.ts.map +1 -0
  130. package/dist/esm/query/QueryEngine.d.ts +56 -12
  131. package/dist/esm/query/QueryEngine.d.ts.map +1 -0
  132. package/dist/esm/query/QueryEngine.js +494 -237
  133. package/dist/esm/query/QueryEngine.js.map +1 -1
  134. package/dist/esm/query/SpatialIndex.d.ts +34 -0
  135. package/dist/esm/query/SpatialIndex.d.ts.map +1 -0
  136. package/dist/esm/shapes/AnyShape.d.ts +9 -4
  137. package/dist/esm/shapes/AnyShape.d.ts.map +1 -0
  138. package/dist/esm/shapes/BoxShape.d.ts +2 -1
  139. package/dist/esm/shapes/BoxShape.d.ts.map +1 -0
  140. package/dist/esm/shapes/BoxShape.js +37 -25
  141. package/dist/esm/shapes/BoxShape.js.map +1 -1
  142. package/dist/esm/shapes/CapsuleShape.d.ts +30 -0
  143. package/dist/esm/shapes/CapsuleShape.d.ts.map +1 -0
  144. package/dist/esm/shapes/CapsuleShape.js +88 -0
  145. package/dist/esm/shapes/CapsuleShape.js.map +1 -0
  146. package/dist/esm/shapes/ChainEdgeShape.d.ts +30 -0
  147. package/dist/esm/shapes/ChainEdgeShape.d.ts.map +1 -0
  148. package/dist/esm/shapes/ChainEdgeShape.js +66 -0
  149. package/dist/esm/shapes/ChainEdgeShape.js.map +1 -0
  150. package/dist/esm/shapes/ChainShape.d.ts +52 -0
  151. package/dist/esm/shapes/ChainShape.d.ts.map +1 -0
  152. package/dist/esm/shapes/ChainShape.js +85 -0
  153. package/dist/esm/shapes/ChainShape.js.map +1 -0
  154. package/dist/esm/shapes/CircleShape.d.ts +3 -4
  155. package/dist/esm/shapes/CircleShape.d.ts.map +1 -0
  156. package/dist/esm/shapes/CircleShape.js +27 -26
  157. package/dist/esm/shapes/CircleShape.js.map +1 -1
  158. package/dist/esm/shapes/PolygonShape.d.ts +7 -8
  159. package/dist/esm/shapes/PolygonShape.d.ts.map +1 -0
  160. package/dist/esm/shapes/PolygonShape.js +131 -162
  161. package/dist/esm/shapes/PolygonShape.js.map +1 -1
  162. package/dist/esm/shapes/SegmentShape.d.ts +31 -0
  163. package/dist/esm/shapes/SegmentShape.d.ts.map +1 -0
  164. package/dist/esm/shapes/SegmentShape.js +62 -0
  165. package/dist/esm/shapes/SegmentShape.js.map +1 -0
  166. package/dist/esm/shapes/Shape.d.ts +28 -17
  167. package/dist/esm/shapes/Shape.d.ts.map +1 -0
  168. package/dist/esm/shapes/Shape.js +12 -13
  169. package/dist/esm/shapes/Shape.js.map +1 -1
  170. package/dist/esm/shapes/convexParts.d.ts +21 -0
  171. package/dist/esm/shapes/convexParts.d.ts.map +1 -0
  172. package/dist/esm/shapes/convexParts.js +26 -0
  173. package/dist/esm/shapes/convexParts.js.map +1 -0
  174. package/dist/esm/shapes/decompose.d.ts +24 -0
  175. package/dist/esm/shapes/decompose.d.ts.map +1 -0
  176. package/dist/esm/shapes/decompose.js +272 -0
  177. package/dist/esm/shapes/decompose.js.map +1 -0
  178. package/dist/esm/shapes/index.d.ts +4 -0
  179. package/dist/esm/shapes/index.d.ts.map +1 -0
  180. package/dist/esm/solver/ContactSolver.d.ts +12 -5
  181. package/dist/esm/solver/ContactSolver.d.ts.map +1 -0
  182. package/dist/esm/solver/ContactSolver.js +425 -471
  183. package/dist/esm/solver/ContactSolver.js.map +1 -1
  184. package/dist/esm/solver/tolerances.d.ts +34 -0
  185. package/dist/esm/solver/tolerances.d.ts.map +1 -0
  186. package/dist/esm/solver/tolerances.js +38 -0
  187. package/dist/esm/solver/tolerances.js.map +1 -0
  188. package/dist/esm/sort.d.ts +5 -4
  189. package/dist/esm/sort.d.ts.map +1 -0
  190. package/dist/esm/sort.js +41 -51
  191. package/dist/esm/sort.js.map +1 -1
  192. package/dist/esm/types.d.ts +4 -8
  193. package/dist/esm/types.d.ts.map +1 -0
  194. package/dist/esm/types.js +15 -17
  195. package/dist/esm/types.js.map +1 -1
  196. package/package.json +6 -6
  197. package/dist/esm/broadphase/SweepAndPrune.d.ts +0 -15
  198. package/dist/esm/broadphase/SweepAndPrune.js +0 -77
  199. package/dist/esm/broadphase/SweepAndPrune.js.map +0 -1
  200. package/dist/esm/debug/index.js.map +0 -1
  201. package/dist/esm/index.js.map +0 -1
@@ -1,430 +1,555 @@
1
+ import { chainEdgeAdmits } from "./chainAdjacency.js";
2
+ import { needsFlip, symmetricEntry, symmetricTable } from "./dispatch.js";
3
+ import { closestPointOnSegment, pointSegmentDistanceSquared } from "./segments.js";
4
+
5
+ //#region src/collision/narrowphase.ts
1
6
  const eps = 1e-9;
7
+ /** Reused closest-point sink; the narrow phase is single-threaded and non-reentrant. */
8
+ const _segmentScratch = {
9
+ x: 0,
10
+ y: 0
11
+ };
2
12
  /**
3
- * Generate the contact manifold for `a` vs `b`, writing into `manifold` and
4
- * returning `true` when the colliders touch. The manifold normal is oriented
5
- * from `a` toward `b`. Dispatches on shape pair; polygon/circle is handled by
6
- * the circle/polygon routine with a `flip` so the normal stays `a → b`.
7
- */
13
+ * Generate the contact manifold for `a` vs `b`, writing into `manifold` and
14
+ * returning `true` when the colliders touch. The manifold normal is oriented
15
+ * from `a` toward `b`.
16
+ *
17
+ * A pair with no entry in the table is deliberately unsupported and reports no
18
+ * contact rather than an approximation.
19
+ */
8
20
  const collide = (a, b, manifold) => {
9
- manifold.reset();
10
- const ta = a.shape.type;
11
- const tb = b.shape.type;
12
- if (ta === 'circle') {
13
- return tb === 'circle' ? collideCircles(a, b, manifold) : collideCirclePolygon(a, b, manifold, false);
14
- }
15
- return tb === 'circle' ? collideCirclePolygon(b, a, manifold, true) : collidePolygons(a, b, manifold);
21
+ manifold.reset();
22
+ const ta = a.shape.type;
23
+ const tb = b.shape.type;
24
+ const solver = symmetricEntry(collideTable, ta, tb);
25
+ if (solver === void 0) return false;
26
+ if (!(needsFlip(ta, tb) ? solver(b, a, manifold, true) : solver(a, b, manifold, false))) return false;
27
+ if (!chainEdgeAdmits(a, manifold.normalX, manifold.normalY) || !chainEdgeAdmits(b, -manifold.normalX, -manifold.normalY)) {
28
+ manifold.reset();
29
+ return false;
30
+ }
31
+ return true;
32
+ };
33
+ const radiusOf = (collider) => collider.shape.type === "circle" || collider.shape.type === "capsule" ? collider.shape.radius : 0;
34
+ const countOf = (collider) => {
35
+ switch (collider.shape.type) {
36
+ case "polygon": return collider.shape.count;
37
+ case "capsule":
38
+ case "segment": return 2;
39
+ case "circle":
40
+ case "chain": return 0;
41
+ }
16
42
  };
17
- // radiusOf/countOf are only ever called after the `collide` dispatch has matched
18
- // the shape kind, so the discriminant always holds; the fallback is unreachable
19
- // but keeps these allocation-free helpers cast-free and type-safe.
20
- const radiusOf = (collider) => (collider.shape.type === 'circle' ? collider.shape.radius : 0);
21
- const countOf = (collider) => (collider.shape.type === 'polygon' ? collider.shape.count : 0);
22
43
  const collideCircles = (a, b, manifold) => {
23
- const ca = a.worldCenter;
24
- const cb = b.worldCenter;
25
- const ra = radiusOf(a);
26
- const rb = radiusOf(b);
27
- let nx = cb.x - ca.x;
28
- let ny = cb.y - ca.y;
29
- const rsum = ra + rb;
30
- const distSq = nx * nx + ny * ny;
31
- if (distSq > rsum * rsum) {
32
- return false;
33
- }
34
- const dist = Math.sqrt(distSq);
35
- if (dist > eps) {
36
- nx /= dist;
37
- ny /= dist;
38
- }
39
- else {
40
- nx = 0;
41
- ny = 1;
42
- }
43
- manifold.normalX = nx;
44
- manifold.normalY = ny;
45
- const point = manifold.points[0];
46
- // Midpoint of the two surface contact points.
47
- point.x = (ca.x + nx * ra + cb.x - nx * rb) * 0.5;
48
- point.y = (ca.y + ny * ra + cb.y - ny * rb) * 0.5;
49
- point.penetration = rsum - dist;
50
- point.id = 0;
51
- manifold.pointCount = 1;
52
- return true;
44
+ const ca = a.worldCenter;
45
+ const cb = b.worldCenter;
46
+ const ra = radiusOf(a);
47
+ const rb = radiusOf(b);
48
+ let nx = cb.x - ca.x;
49
+ let ny = cb.y - ca.y;
50
+ const rsum = ra + rb;
51
+ const distSq = nx * nx + ny * ny;
52
+ if (distSq > rsum * rsum) return false;
53
+ const dist = Math.sqrt(distSq);
54
+ if (dist > eps) {
55
+ nx /= dist;
56
+ ny /= dist;
57
+ } else {
58
+ nx = 0;
59
+ ny = 1;
60
+ }
61
+ manifold.normalX = nx;
62
+ manifold.normalY = ny;
63
+ const point = manifold.points[0];
64
+ point.x = (ca.x + nx * ra + cb.x - nx * rb) * .5;
65
+ point.y = (ca.y + ny * ra + cb.y - ny * rb) * .5;
66
+ point.penetration = rsum - dist;
67
+ point.id = 0;
68
+ manifold.pointCount = 1;
69
+ return true;
53
70
  };
54
71
  /**
55
- * Circle (the `circle` collider) vs polygon. Produces the normal pointing from
56
- * the circle toward the polygon; when `flip` is set (the polygon is collider A),
57
- * the normal is negated so it stays `a → b`.
58
- */
72
+ * Circle (the `circle` collider) vs polygon. Produces the normal pointing from
73
+ * the circle toward the polygon; when `flip` is set (the polygon is collider A),
74
+ * the normal is negated so it stays `a → b`.
75
+ */
59
76
  const collideCirclePolygon = (circle, polygon, manifold, flip) => {
60
- const c = circle.worldCenter;
61
- const r = radiusOf(circle);
62
- const verts = polygon.worldVertices;
63
- const normals = polygon.worldNormals;
64
- const count = countOf(polygon);
65
- let maxSep = -Infinity;
66
- let refEdge = 0;
67
- for (let i = 0; i < count; i++) {
68
- // Loop indices are provably in-bounds (0..count-1); the `!` is zero-cost.
69
- const enx = normals[i * 2];
70
- const eny = normals[i * 2 + 1];
71
- const evx = verts[i * 2];
72
- const evy = verts[i * 2 + 1];
73
- const s = enx * (c.x - evx) + eny * (c.y - evy);
74
- if (s > r) {
75
- return false;
76
- }
77
- if (s > maxSep) {
78
- maxSep = s;
79
- refEdge = i;
80
- }
81
- }
82
- // refEdge/j stay in 0..count-1, so these reads are in-bounds too.
83
- const refNx = normals[refEdge * 2];
84
- const refNy = normals[refEdge * 2 + 1];
85
- let nx;
86
- let ny;
87
- let penetration;
88
- let px;
89
- let py;
90
- let id;
91
- if (maxSep < eps) {
92
- // Centre inside the polygon: push out along the least-penetrating face.
93
- nx = -refNx;
94
- ny = -refNy;
95
- penetration = r - maxSep;
96
- px = c.x + nx * r;
97
- py = c.y + ny * r;
98
- id = refEdge;
99
- }
100
- else {
101
- const j = (refEdge + 1) % count;
102
- const v1x = verts[refEdge * 2];
103
- const v1y = verts[refEdge * 2 + 1];
104
- const v2x = verts[j * 2];
105
- const v2y = verts[j * 2 + 1];
106
- const u1 = (c.x - v1x) * (v2x - v1x) + (c.y - v1y) * (v2y - v1y);
107
- const u2 = (c.x - v2x) * (v1x - v2x) + (c.y - v2y) * (v1y - v2y);
108
- if (u1 <= 0) {
109
- // Nearest feature is vertex v1 (Voronoi corner region).
110
- const dx = c.x - v1x;
111
- const dy = c.y - v1y;
112
- const d2 = dx * dx + dy * dy;
113
- if (d2 > r * r) {
114
- return false;
115
- }
116
- const d = Math.sqrt(d2);
117
- nx = d > eps ? -dx / d : -refNx;
118
- ny = d > eps ? -dy / d : -refNy;
119
- penetration = r - d;
120
- px = v1x;
121
- py = v1y;
122
- id = refEdge;
123
- }
124
- else if (u2 <= 0) {
125
- const dx = c.x - v2x;
126
- const dy = c.y - v2y;
127
- const d2 = dx * dx + dy * dy;
128
- if (d2 > r * r) {
129
- return false;
130
- }
131
- const d = Math.sqrt(d2);
132
- nx = d > eps ? -dx / d : -refNx;
133
- ny = d > eps ? -dy / d : -refNy;
134
- penetration = r - d;
135
- px = v2x;
136
- py = v2y;
137
- id = j;
138
- }
139
- else {
140
- // Face region.
141
- nx = -refNx;
142
- ny = -refNy;
143
- penetration = r - maxSep;
144
- px = c.x + nx * r;
145
- py = c.y + ny * r;
146
- id = refEdge;
147
- }
148
- }
149
- if (flip) {
150
- nx = -nx;
151
- ny = -ny;
152
- }
153
- manifold.normalX = nx;
154
- manifold.normalY = ny;
155
- const point = manifold.points[0];
156
- point.x = px;
157
- point.y = py;
158
- point.penetration = penetration < 0 ? 0 : penetration;
159
- point.id = id;
160
- manifold.pointCount = 1;
161
- return true;
77
+ const c = circle.worldCenter;
78
+ const r = radiusOf(circle);
79
+ const verts = polygon.worldVertices;
80
+ const normals = polygon.worldNormals;
81
+ const count = countOf(polygon);
82
+ let maxSep = -Infinity;
83
+ let refEdge = 0;
84
+ for (let i = 0; i < count; i++) {
85
+ const enx = normals[i * 2];
86
+ const eny = normals[i * 2 + 1];
87
+ const evx = verts[i * 2];
88
+ const evy = verts[i * 2 + 1];
89
+ const s = enx * (c.x - evx) + eny * (c.y - evy);
90
+ if (s > r) return false;
91
+ if (s > maxSep) {
92
+ maxSep = s;
93
+ refEdge = i;
94
+ }
95
+ }
96
+ const refNx = normals[refEdge * 2];
97
+ const refNy = normals[refEdge * 2 + 1];
98
+ let nx;
99
+ let ny;
100
+ let penetration;
101
+ let px;
102
+ let py;
103
+ let id;
104
+ if (maxSep < eps) {
105
+ nx = -refNx;
106
+ ny = -refNy;
107
+ penetration = r - maxSep;
108
+ px = c.x + nx * r;
109
+ py = c.y + ny * r;
110
+ id = refEdge;
111
+ } else {
112
+ const j = (refEdge + 1) % count;
113
+ const v1x = verts[refEdge * 2];
114
+ const v1y = verts[refEdge * 2 + 1];
115
+ const v2x = verts[j * 2];
116
+ const v2y = verts[j * 2 + 1];
117
+ const u1 = (c.x - v1x) * (v2x - v1x) + (c.y - v1y) * (v2y - v1y);
118
+ const u2 = (c.x - v2x) * (v1x - v2x) + (c.y - v2y) * (v1y - v2y);
119
+ if (u1 <= 0) {
120
+ const dx = c.x - v1x;
121
+ const dy = c.y - v1y;
122
+ const d2 = dx * dx + dy * dy;
123
+ if (d2 > r * r) return false;
124
+ const d = Math.sqrt(d2);
125
+ nx = d > eps ? -dx / d : -refNx;
126
+ ny = d > eps ? -dy / d : -refNy;
127
+ penetration = r - d;
128
+ px = v1x;
129
+ py = v1y;
130
+ id = refEdge;
131
+ } else if (u2 <= 0) {
132
+ const dx = c.x - v2x;
133
+ const dy = c.y - v2y;
134
+ const d2 = dx * dx + dy * dy;
135
+ if (d2 > r * r) return false;
136
+ const d = Math.sqrt(d2);
137
+ nx = d > eps ? -dx / d : -refNx;
138
+ ny = d > eps ? -dy / d : -refNy;
139
+ penetration = r - d;
140
+ px = v2x;
141
+ py = v2y;
142
+ id = j;
143
+ } else {
144
+ nx = -refNx;
145
+ ny = -refNy;
146
+ penetration = r - maxSep;
147
+ px = c.x + nx * r;
148
+ py = c.y + ny * r;
149
+ id = refEdge;
150
+ }
151
+ }
152
+ if (flip) {
153
+ nx = -nx;
154
+ ny = -ny;
155
+ }
156
+ manifold.normalX = nx;
157
+ manifold.normalY = ny;
158
+ const point = manifold.points[0];
159
+ point.x = px;
160
+ point.y = py;
161
+ point.penetration = penetration < 0 ? 0 : penetration;
162
+ point.id = id;
163
+ manifold.pointCount = 1;
164
+ return true;
162
165
  };
163
- const newClipPair = () => [
164
- { x: 0, y: 0, id: 0 },
165
- { x: 0, y: 0, id: 0 },
166
- ];
167
- // Module-local clip scratch reused across collidePolygons calls instead of
168
- // allocating a fresh pair per contact. The narrow phase is single-threaded and
169
- // non-reentrant — a world step calls `collide` strictly sequentially per pair,
170
- // with no nested self-call or async — so module-global scratch is safe and may
171
- // be shared across worlds (world-isolation.test.ts guards the multi-world
172
- // invariant). The three pairs MUST stay separate: incident → clipped1 →
173
- // clipped2 are simultaneously live (each clip reads the previous clip's output).
166
+ const newClipPair = () => [{
167
+ x: 0,
168
+ y: 0,
169
+ id: 0
170
+ }, {
171
+ x: 0,
172
+ y: 0,
173
+ id: 0
174
+ }];
174
175
  const _incidentScratch = newClipPair();
175
176
  const _clipped1Scratch = newClipPair();
176
177
  const _clipped2Scratch = newClipPair();
177
- // Two separate scratches: collidePolygons holds sepA and sepB at the same time.
178
- const _sepA = { separation: 0, edge: 0 };
179
- const _sepB = { separation: 0, edge: 0 };
178
+ const _sepA = {
179
+ separation: 0,
180
+ edge: 0
181
+ };
182
+ const _sepB = {
183
+ separation: 0,
184
+ edge: 0
185
+ };
180
186
  /** Max separation of `b` from any face of `a`, with the supporting face index. Writes into and returns `out` (allocation-free). */
181
187
  const findMaxSeparation = (a, b, out) => {
182
- const av = a.worldVertices;
183
- const an = a.worldNormals;
184
- const ac = countOf(a);
185
- const bv = b.worldVertices;
186
- const bc = countOf(b);
187
- let best = -Infinity;
188
- let bestEdge = 0;
189
- for (let i = 0; i < ac; i++) {
190
- // i in 0..ac-1 and j in 0..bc-1, so every read below is in-bounds.
191
- const nx = an[i * 2];
192
- const ny = an[i * 2 + 1];
193
- const vx = av[i * 2];
194
- const vy = av[i * 2 + 1];
195
- // Support of b along -n = vertex of b minimising dot(n, vertex).
196
- let minDot = Infinity;
197
- let sx = 0;
198
- let sy = 0;
199
- for (let j = 0; j < bc; j++) {
200
- const bjx = bv[j * 2];
201
- const bjy = bv[j * 2 + 1];
202
- const d = nx * bjx + ny * bjy;
203
- if (d < minDot) {
204
- minDot = d;
205
- sx = bjx;
206
- sy = bjy;
207
- }
208
- }
209
- const separation = nx * (sx - vx) + ny * (sy - vy);
210
- if (separation > best) {
211
- best = separation;
212
- bestEdge = i;
213
- }
214
- }
215
- out.separation = best;
216
- out.edge = bestEdge;
217
- return out;
188
+ const av = a.worldVertices;
189
+ const an = a.worldNormals;
190
+ const ac = countOf(a);
191
+ const bv = b.worldVertices;
192
+ const bc = countOf(b);
193
+ let best = -Infinity;
194
+ let bestEdge = 0;
195
+ for (let i = 0; i < ac; i++) {
196
+ const nx = an[i * 2];
197
+ const ny = an[i * 2 + 1];
198
+ const vx = av[i * 2];
199
+ const vy = av[i * 2 + 1];
200
+ let minDot = Infinity;
201
+ let sx = 0;
202
+ let sy = 0;
203
+ for (let j = 0; j < bc; j++) {
204
+ const bjx = bv[j * 2];
205
+ const bjy = bv[j * 2 + 1];
206
+ const d = nx * bjx + ny * bjy;
207
+ if (d < minDot) {
208
+ minDot = d;
209
+ sx = bjx;
210
+ sy = bjy;
211
+ }
212
+ }
213
+ const separation = nx * (sx - vx) + ny * (sy - vy);
214
+ if (separation > best) {
215
+ best = separation;
216
+ bestEdge = i;
217
+ }
218
+ }
219
+ out.separation = best;
220
+ out.edge = bestEdge;
221
+ return out;
218
222
  };
219
223
  /** Incident face of `inc` = the face most anti-parallel to the reference normal. */
220
224
  const findIncidentFace = (refNx, refNy, inc, out) => {
221
- const iv = inc.worldVertices;
222
- const inrm = inc.worldNormals;
223
- const ic = countOf(inc);
224
- let minDot = Infinity;
225
- let idx = 0;
226
- for (let i = 0; i < ic; i++) {
227
- const inx = inrm[i * 2];
228
- const iny = inrm[i * 2 + 1];
229
- const d = refNx * inx + refNy * iny;
230
- if (d < minDot) {
231
- minDot = d;
232
- idx = i;
233
- }
234
- }
235
- const j = (idx + 1) % ic;
236
- // idx/j in 0..ic-1; reads in-bounds. out is a fixed 2-tuple.
237
- out[0].x = iv[idx * 2];
238
- out[0].y = iv[idx * 2 + 1];
239
- out[0].id = idx;
240
- out[1].x = iv[j * 2];
241
- out[1].y = iv[j * 2 + 1];
242
- out[1].id = j;
225
+ const iv = inc.worldVertices;
226
+ const inrm = inc.worldNormals;
227
+ const ic = countOf(inc);
228
+ let minDot = Infinity;
229
+ let idx = 0;
230
+ for (let i = 0; i < ic; i++) {
231
+ const inx = inrm[i * 2];
232
+ const iny = inrm[i * 2 + 1];
233
+ const d = refNx * inx + refNy * iny;
234
+ if (d < minDot) {
235
+ minDot = d;
236
+ idx = i;
237
+ }
238
+ }
239
+ const j = (idx + 1) % ic;
240
+ out[0].x = iv[idx * 2];
241
+ out[0].y = iv[idx * 2 + 1];
242
+ out[0].id = idx;
243
+ out[1].x = iv[j * 2];
244
+ out[1].y = iv[j * 2 + 1];
245
+ out[1].id = j;
243
246
  };
244
247
  /** Clip the 2-vertex segment `input` to the half-plane `dot(n, p) ≤ offset`. */
245
248
  const clipSegment = (nx, ny, offset, input, output, syntheticId) => {
246
- // input/output are fixed 2-tuples; count never exceeds 2 (two boundary
247
- // copies, or one copy plus the guarded intersection), so output[0]/output[1]
248
- // are the only slots written and both always exist.
249
- const in0 = input[0];
250
- const in1 = input[1];
251
- let count = 0;
252
- const d1 = nx * in0.x + ny * in0.y - offset;
253
- const d2 = nx * in1.x + ny * in1.y - offset;
254
- if (d1 <= 0) {
255
- copyClip(in0, count === 0 ? output[0] : output[1]);
256
- count++;
257
- }
258
- if (d2 <= 0) {
259
- copyClip(in1, count === 0 ? output[0] : output[1]);
260
- count++;
261
- }
262
- if (d1 * d2 < 0 && count < 2) {
263
- const alpha = d1 / (d1 - d2);
264
- const target = count === 0 ? output[0] : output[1];
265
- target.x = in0.x + alpha * (in1.x - in0.x);
266
- target.y = in0.y + alpha * (in1.y - in0.y);
267
- target.id = syntheticId;
268
- count++;
269
- }
270
- return count;
249
+ const in0 = input[0];
250
+ const in1 = input[1];
251
+ let count = 0;
252
+ const d1 = nx * in0.x + ny * in0.y - offset;
253
+ const d2 = nx * in1.x + ny * in1.y - offset;
254
+ if (d1 <= 0) {
255
+ copyClip(in0, count === 0 ? output[0] : output[1]);
256
+ count++;
257
+ }
258
+ if (d2 <= 0) {
259
+ copyClip(in1, count === 0 ? output[0] : output[1]);
260
+ count++;
261
+ }
262
+ if (d1 * d2 < 0 && count < 2) {
263
+ const alpha = d1 / (d1 - d2);
264
+ const target = count === 0 ? output[0] : output[1];
265
+ target.x = in0.x + alpha * (in1.x - in0.x);
266
+ target.y = in0.y + alpha * (in1.y - in0.y);
267
+ target.id = syntheticId;
268
+ count++;
269
+ }
270
+ return count;
271
271
  };
272
272
  const copyClip = (from, to) => {
273
- to.x = from.x;
274
- to.y = from.y;
275
- to.id = from.id;
273
+ to.x = from.x;
274
+ to.y = from.y;
275
+ to.id = from.id;
276
276
  };
277
- const encodeId = (flip, refEdge, incidentId) => (flip ? 1 << 20 : 0) | ((refEdge & 0xff) << 12) | (incidentId & 0xfff);
278
- /** Convex polygon vs convex polygon: SAT reference face + Sutherland-Hodgman clip. */
279
- const collidePolygons = (a, b, manifold) => {
280
- const sepA = findMaxSeparation(a, b, _sepA);
281
- if (sepA.separation >= 0) {
282
- return false;
283
- }
284
- const sepB = findMaxSeparation(b, a, _sepB);
285
- if (sepB.separation >= 0) {
286
- return false;
287
- }
288
- let ref;
289
- let inc;
290
- let refEdge;
291
- let flip;
292
- // Bias toward keeping A as the reference for stability (reduces ref/incident
293
- // role flapping when a body rocks slightly — gate SG-M3).
294
- if (sepA.separation >= sepB.separation * 0.95 + sepA.separation * 0.01) {
295
- ref = a;
296
- inc = b;
297
- refEdge = sepA.edge;
298
- flip = false;
299
- }
300
- else {
301
- ref = b;
302
- inc = a;
303
- refEdge = sepB.edge;
304
- flip = true;
305
- }
306
- const rv = ref.worldVertices;
307
- const rn = ref.worldNormals;
308
- const rc = countOf(ref);
309
- const i2 = (refEdge + 1) % rc;
310
- // refEdge/i2 in 0..rc-1, so these reads are in-bounds.
311
- const v1x = rv[refEdge * 2];
312
- const v1y = rv[refEdge * 2 + 1];
313
- const v2x = rv[i2 * 2];
314
- const v2y = rv[i2 * 2 + 1];
315
- const refNx = rn[refEdge * 2];
316
- const refNy = rn[refEdge * 2 + 1];
317
- const incident = _incidentScratch;
318
- findIncidentFace(refNx, refNy, inc, incident);
319
- // Reference-face tangent (the side-plane direction).
320
- let tx = v2x - v1x;
321
- let ty = v2y - v1y;
322
- // sqrt(x²+y²) over Math.hypot avoids hypot's variadic/internal-allocation
323
- // path on the narrow-phase hot path; the operands are bounded edge lengths,
324
- // so the overflow protection hypot adds is not needed here.
325
- const tl = Math.sqrt(tx * tx + ty * ty);
326
- tx /= tl;
327
- ty /= tl;
328
- const negSide = -(tx * v1x + ty * v1y);
329
- const posSide = tx * v2x + ty * v2y;
330
- const clipped1 = _clipped1Scratch;
331
- if (clipSegment(-tx, -ty, negSide, incident, clipped1, encodeId(flip, refEdge, 0xffe)) < 2) {
332
- return false;
333
- }
334
- const clipped2 = _clipped2Scratch;
335
- if (clipSegment(tx, ty, posSide, clipped1, clipped2, encodeId(flip, refEdge, 0xfff)) < 2) {
336
- return false;
337
- }
338
- manifold.normalX = flip ? -refNx : refNx;
339
- manifold.normalY = flip ? -refNy : refNy;
340
- const refC = refNx * v1x + refNy * v1y;
341
- const points = manifold.points;
342
- let cp = 0;
343
- for (let k = 0; k < 2; k++) {
344
- // clipped2 is a fixed 2-tuple; k in {0,1} so the clip vertex always exists.
345
- const cv = k === 0 ? clipped2[0] : clipped2[1];
346
- const separation = refNx * cv.x + refNy * cv.y - refC;
347
- if (separation <= 0) {
348
- // cp reaches at most 2 (one per clip vertex); points[0]/points[1] exist.
349
- const point = cp === 0 ? points[0] : points[1];
350
- point.x = cv.x;
351
- point.y = cv.y;
352
- point.penetration = -separation;
353
- point.id = encodeId(flip, refEdge, cv.id);
354
- cp++;
355
- }
356
- }
357
- manifold.pointCount = cp;
358
- return cp > 0;
277
+ const encodeId = (flip, refEdge, incidentId) => (flip ? 1 << 20 : 0) | (refEdge & 255) << 12 | incidentId & 4095;
278
+ /**
279
+ * Convex ring vs convex ring: SAT reference face + Sutherland-Hodgman clip, with
280
+ * each operand allowed a radius that rounds its outline off.
281
+ *
282
+ * The separations the SAT computes are between the two **cores**; the radii only
283
+ * move where touching begins, and since both candidate axes shift by the same
284
+ * sum, the reference face the SAT picks is unaffected. A capsule is a two-vertex
285
+ * core with a radius and a polygon a many-vertex core without one, so
286
+ * polygon/polygon, capsule/polygon and capsule/capsule are all this one routine.
287
+ */
288
+ const collideRoundedPolygons = (a, b, manifold, callerFlip) => {
289
+ const totalRadius = radiusOf(a) + radiusOf(b);
290
+ const sepA = findMaxSeparation(a, b, _sepA);
291
+ if (sepA.separation >= totalRadius) return false;
292
+ const sepB = findMaxSeparation(b, a, _sepB);
293
+ if (sepB.separation >= totalRadius) return false;
294
+ let ref;
295
+ let inc;
296
+ let refEdge;
297
+ let refSwapped;
298
+ if (sepA.separation >= sepB.separation * .95 + sepA.separation * .01) {
299
+ ref = a;
300
+ inc = b;
301
+ refEdge = sepA.edge;
302
+ refSwapped = false;
303
+ } else {
304
+ ref = b;
305
+ inc = a;
306
+ refEdge = sepB.edge;
307
+ refSwapped = true;
308
+ }
309
+ const rv = ref.worldVertices;
310
+ const rn = ref.worldNormals;
311
+ const rc = countOf(ref);
312
+ const i2 = (refEdge + 1) % rc;
313
+ const v1x = rv[refEdge * 2];
314
+ const v1y = rv[refEdge * 2 + 1];
315
+ const v2x = rv[i2 * 2];
316
+ const v2y = rv[i2 * 2 + 1];
317
+ const refNx = rn[refEdge * 2];
318
+ const refNy = rn[refEdge * 2 + 1];
319
+ const incident = _incidentScratch;
320
+ findIncidentFace(refNx, refNy, inc, incident);
321
+ let tx = v2x - v1x;
322
+ let ty = v2y - v1y;
323
+ const tl = Math.sqrt(tx * tx + ty * ty);
324
+ tx /= tl;
325
+ ty /= tl;
326
+ const negSide = -(tx * v1x + ty * v1y);
327
+ const posSide = tx * v2x + ty * v2y;
328
+ const clipped1 = _clipped1Scratch;
329
+ if (clipSegment(-tx, -ty, negSide, incident, clipped1, encodeId(refSwapped, refEdge, 4094)) < 2) return false;
330
+ const clipped2 = _clipped2Scratch;
331
+ if (clipSegment(tx, ty, posSide, clipped1, clipped2, encodeId(refSwapped, refEdge, 4095)) < 2) return false;
332
+ const flipNormal = refSwapped !== callerFlip;
333
+ manifold.normalX = flipNormal ? -refNx : refNx;
334
+ manifold.normalY = flipNormal ? -refNy : refNy;
335
+ const refC = refNx * v1x + refNy * v1y;
336
+ const incidentRadius = radiusOf(inc);
337
+ const points = manifold.points;
338
+ let cp = 0;
339
+ for (let k = 0; k < 2; k++) {
340
+ const cv = k === 0 ? clipped2[0] : clipped2[1];
341
+ const separation = refNx * cv.x + refNy * cv.y - refC;
342
+ if (separation <= totalRadius) {
343
+ const point = cp === 0 ? points[0] : points[1];
344
+ point.x = cv.x - refNx * incidentRadius;
345
+ point.y = cv.y - refNy * incidentRadius;
346
+ point.penetration = totalRadius - separation;
347
+ point.id = encodeId(refSwapped, refEdge, cv.id);
348
+ cp++;
349
+ }
350
+ }
351
+ manifold.pointCount = cp;
352
+ return cp > 0;
359
353
  };
360
354
  /**
361
- * Boolean overlap test (no manifold) used by sensors and shape-overlap queries.
362
- * Exact for the circle/circle, circle/polygon and polygon/polygon pairs.
363
- */
355
+ * Boolean overlap test (no manifold) used by sensors and shape-overlap queries.
356
+ * Exact for the circle/circle, circle/polygon and polygon/polygon pairs.
357
+ */
364
358
  const testOverlap = (a, b) => {
365
- const ta = a.shape.type;
366
- const tb = b.shape.type;
367
- if (ta === 'circle') {
368
- if (tb === 'circle') {
369
- const ca = a.worldCenter;
370
- const cb = b.worldCenter;
371
- const rsum = radiusOf(a) + radiusOf(b);
372
- const dx = cb.x - ca.x;
373
- const dy = cb.y - ca.y;
374
- return dx * dx + dy * dy <= rsum * rsum;
375
- }
376
- return circlePolygonOverlap(a, b);
377
- }
378
- if (tb === 'circle') {
379
- return circlePolygonOverlap(b, a);
380
- }
381
- return findMaxSeparation(a, b, _sepA).separation < 0 && findMaxSeparation(b, a, _sepB).separation < 0;
359
+ const ta = a.shape.type;
360
+ const tb = b.shape.type;
361
+ const test = symmetricEntry(overlapTable, ta, tb);
362
+ if (test === void 0) return false;
363
+ return needsFlip(ta, tb) ? test(b, a) : test(a, b);
364
+ };
365
+ const circlesOverlap = (a, b) => {
366
+ const ca = a.worldCenter;
367
+ const cb = b.worldCenter;
368
+ const rsum = radiusOf(a) + radiusOf(b);
369
+ const dx = cb.x - ca.x;
370
+ const dy = cb.y - ca.y;
371
+ return dx * dx + dy * dy <= rsum * rsum;
372
+ };
373
+ const roundedPolygonsOverlap = (a, b) => {
374
+ const totalRadius = radiusOf(a) + radiusOf(b);
375
+ return findMaxSeparation(a, b, _sepA).separation < totalRadius && findMaxSeparation(b, a, _sepB).separation < totalRadius;
376
+ };
377
+ /**
378
+ * Circle vs capsule: a capsule is every point within its radius of the spine, so
379
+ * this is a circle/circle test against the spine point nearest the circle centre.
380
+ * One contact point - a circle meets a rounded surface in one place, and a second
381
+ * would be invented noise for the solver.
382
+ */
383
+ const collideCircleSpine = (circle, spineShape, manifold, flip) => {
384
+ const spine = spineShape.worldVertices;
385
+ const centre = circle.worldCenter;
386
+ const circleRadius = radiusOf(circle);
387
+ const radiusSum = circleRadius + radiusOf(spineShape);
388
+ closestPointOnSegment(centre.x, centre.y, spine[0], spine[1], spine[2], spine[3], _segmentScratch);
389
+ let nx = _segmentScratch.x - centre.x;
390
+ let ny = _segmentScratch.y - centre.y;
391
+ const distanceSquared = nx * nx + ny * ny;
392
+ if (distanceSquared > radiusSum * radiusSum) return false;
393
+ const distance = Math.sqrt(distanceSquared);
394
+ if (distance > eps) {
395
+ nx /= distance;
396
+ ny /= distance;
397
+ } else {
398
+ nx = spineShape.worldNormals[0];
399
+ ny = spineShape.worldNormals[1];
400
+ }
401
+ manifold.normalX = flip ? -nx : nx;
402
+ manifold.normalY = flip ? -ny : ny;
403
+ const point = manifold.points[0];
404
+ const along = circleRadius + (distance - radiusSum) * .5;
405
+ point.x = centre.x + nx * along;
406
+ point.y = centre.y + ny * along;
407
+ point.penetration = radiusSum - distance;
408
+ point.id = 0;
409
+ manifold.pointCount = 1;
410
+ return true;
411
+ };
412
+ const circleSpineOverlap = (circle, spineShape) => {
413
+ const spine = spineShape.worldVertices;
414
+ const centre = circle.worldCenter;
415
+ const radiusSum = radiusOf(circle) + radiusOf(spineShape);
416
+ return pointSegmentDistanceSquared(centre.x, centre.y, spine[0], spine[1], spine[2], spine[3], _segmentScratch) <= radiusSum * radiusSum;
382
417
  };
383
418
  const circlePolygonOverlap = (circle, polygon) => {
384
- const c = circle.worldCenter;
385
- const r = radiusOf(circle);
386
- const verts = polygon.worldVertices;
387
- const normals = polygon.worldNormals;
388
- const count = countOf(polygon);
389
- let maxSep = -Infinity;
390
- let refEdge = 0;
391
- for (let i = 0; i < count; i++) {
392
- // Loop indices are provably in-bounds (0..count-1); the `!` is zero-cost.
393
- const enx = normals[i * 2];
394
- const eny = normals[i * 2 + 1];
395
- const evx = verts[i * 2];
396
- const evy = verts[i * 2 + 1];
397
- const s = enx * (c.x - evx) + eny * (c.y - evy);
398
- if (s > r) {
399
- return false;
400
- }
401
- if (s > maxSep) {
402
- maxSep = s;
403
- refEdge = i;
404
- }
405
- }
406
- if (maxSep < eps) {
407
- return true;
408
- }
409
- const j = (refEdge + 1) % count;
410
- const v1x = verts[refEdge * 2];
411
- const v1y = verts[refEdge * 2 + 1];
412
- const v2x = verts[j * 2];
413
- const v2y = verts[j * 2 + 1];
414
- const u1 = (c.x - v1x) * (v2x - v1x) + (c.y - v1y) * (v2y - v1y);
415
- const u2 = (c.x - v2x) * (v1x - v2x) + (c.y - v2y) * (v1y - v2y);
416
- if (u1 <= 0) {
417
- const dx = c.x - v1x;
418
- const dy = c.y - v1y;
419
- return dx * dx + dy * dy <= r * r;
420
- }
421
- if (u2 <= 0) {
422
- const dx = c.x - v2x;
423
- const dy = c.y - v2y;
424
- return dx * dx + dy * dy <= r * r;
425
- }
426
- return maxSep <= r;
419
+ const c = circle.worldCenter;
420
+ const r = radiusOf(circle);
421
+ const verts = polygon.worldVertices;
422
+ const normals = polygon.worldNormals;
423
+ const count = countOf(polygon);
424
+ let maxSep = -Infinity;
425
+ let refEdge = 0;
426
+ for (let i = 0; i < count; i++) {
427
+ const enx = normals[i * 2];
428
+ const eny = normals[i * 2 + 1];
429
+ const evx = verts[i * 2];
430
+ const evy = verts[i * 2 + 1];
431
+ const s = enx * (c.x - evx) + eny * (c.y - evy);
432
+ if (s > r) return false;
433
+ if (s > maxSep) {
434
+ maxSep = s;
435
+ refEdge = i;
436
+ }
437
+ }
438
+ if (maxSep < eps) return true;
439
+ const j = (refEdge + 1) % count;
440
+ const v1x = verts[refEdge * 2];
441
+ const v1y = verts[refEdge * 2 + 1];
442
+ const v2x = verts[j * 2];
443
+ const v2y = verts[j * 2 + 1];
444
+ const u1 = (c.x - v1x) * (v2x - v1x) + (c.y - v1y) * (v2y - v1y);
445
+ const u2 = (c.x - v2x) * (v1x - v2x) + (c.y - v2y) * (v1y - v2y);
446
+ if (u1 <= 0) {
447
+ const dx = c.x - v1x;
448
+ const dy = c.y - v1y;
449
+ return dx * dx + dy * dy <= r * r;
450
+ }
451
+ if (u2 <= 0) {
452
+ const dx = c.x - v2x;
453
+ const dy = c.y - v2y;
454
+ return dx * dx + dy * dy <= r * r;
455
+ }
456
+ return maxSep <= r;
427
457
  };
458
+ const collideTable = symmetricTable([
459
+ [
460
+ "circle",
461
+ "circle",
462
+ collideCircles
463
+ ],
464
+ [
465
+ "circle",
466
+ "capsule",
467
+ collideCircleSpine
468
+ ],
469
+ [
470
+ "circle",
471
+ "segment",
472
+ collideCircleSpine
473
+ ],
474
+ [
475
+ "circle",
476
+ "polygon",
477
+ collideCirclePolygon
478
+ ],
479
+ [
480
+ "capsule",
481
+ "capsule",
482
+ collideRoundedPolygons
483
+ ],
484
+ [
485
+ "capsule",
486
+ "segment",
487
+ collideRoundedPolygons
488
+ ],
489
+ [
490
+ "capsule",
491
+ "polygon",
492
+ collideRoundedPolygons
493
+ ],
494
+ [
495
+ "segment",
496
+ "polygon",
497
+ collideRoundedPolygons
498
+ ],
499
+ [
500
+ "polygon",
501
+ "polygon",
502
+ collideRoundedPolygons
503
+ ]
504
+ ]);
505
+ const overlapTable = symmetricTable([
506
+ [
507
+ "circle",
508
+ "circle",
509
+ circlesOverlap
510
+ ],
511
+ [
512
+ "circle",
513
+ "capsule",
514
+ circleSpineOverlap
515
+ ],
516
+ [
517
+ "circle",
518
+ "segment",
519
+ circleSpineOverlap
520
+ ],
521
+ [
522
+ "circle",
523
+ "polygon",
524
+ circlePolygonOverlap
525
+ ],
526
+ [
527
+ "capsule",
528
+ "capsule",
529
+ roundedPolygonsOverlap
530
+ ],
531
+ [
532
+ "capsule",
533
+ "segment",
534
+ roundedPolygonsOverlap
535
+ ],
536
+ [
537
+ "capsule",
538
+ "polygon",
539
+ roundedPolygonsOverlap
540
+ ],
541
+ [
542
+ "segment",
543
+ "polygon",
544
+ roundedPolygonsOverlap
545
+ ],
546
+ [
547
+ "polygon",
548
+ "polygon",
549
+ roundedPolygonsOverlap
550
+ ]
551
+ ]);
428
552
 
553
+ //#endregion
429
554
  export { collide, testOverlap };
430
- //# sourceMappingURL=narrowphase.js.map
555
+ //# sourceMappingURL=narrowphase.js.map