SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
CollisionDetection.cpp
Go to the documentation of this file.
2#include <cmath>
3#include <algorithm>
4#include <limits>
5
6namespace Sleak {
7namespace Physics {
8
9Vector3D ClosestPointOnSegment(const Vector3D& point, const Vector3D& a, const Vector3D& b) {
10 Vector3D ab = b - a;
11 float t = (point - a).Dot(ab);
12 float denom = ab.Dot(ab);
13 if (denom < 1e-8f) return a;
14 t = std::clamp(t / denom, 0.0f, 1.0f);
15 return a + ab * t;
16}
17
19 const Vector3D& b1, const Vector3D& b2,
20 Vector3D& closestA, Vector3D& closestB) {
21 Vector3D d1 = a2 - a1;
22 Vector3D d2 = b2 - b1;
23 Vector3D r = a1 - b1;
24
25 float a = d1.Dot(d1);
26 float e = d2.Dot(d2);
27 float f = d2.Dot(r);
28
29 float s, t;
30
31 if (a < 1e-8f && e < 1e-8f) {
32 closestA = a1;
33 closestB = b1;
34 return;
35 }
36
37 if (a < 1e-8f) {
38 s = 0.0f;
39 t = std::clamp(f / e, 0.0f, 1.0f);
40 } else {
41 float c = d1.Dot(r);
42 if (e < 1e-8f) {
43 t = 0.0f;
44 s = std::clamp(-c / a, 0.0f, 1.0f);
45 } else {
46 float b = d1.Dot(d2);
47 float denom = a * e - b * b;
48
49 if (std::abs(denom) > 1e-8f) {
50 s = std::clamp((b * f - c * e) / denom, 0.0f, 1.0f);
51 } else {
52 s = 0.0f;
53 }
54
55 t = (b * s + f) / e;
56
57 if (t < 0.0f) {
58 t = 0.0f;
59 s = std::clamp(-c / a, 0.0f, 1.0f);
60 } else if (t > 1.0f) {
61 t = 1.0f;
62 s = std::clamp((b - c) / a, 0.0f, 1.0f);
63 }
64 }
65 }
66
67 closestA = a1 + d1 * s;
68 closestB = b1 + d2 * t;
69}
70
71/// Clamps point into the box, giving the nearest surface or interior point.
72static Vector3D ClosestPointOnAABB(const AABB& aabb, const Vector3D& point) {
73 return Vector3D(
74 std::clamp(point.GetX(), aabb.min.GetX(), aabb.max.GetX()),
75 std::clamp(point.GetY(), aabb.min.GetY(), aabb.max.GetY()),
76 std::clamp(point.GetZ(), aabb.min.GetZ(), aabb.max.GetZ())
77 );
78}
79
81 CollisionManifold result;
82
83 if (!a.Overlaps(b)) return result;
84
85 // Find minimum overlap axis
86 float overlapX1 = a.max.GetX() - b.min.GetX();
87 float overlapX2 = b.max.GetX() - a.min.GetX();
88 float overlapY1 = a.max.GetY() - b.min.GetY();
89 float overlapY2 = b.max.GetY() - a.min.GetY();
90 float overlapZ1 = a.max.GetZ() - b.min.GetZ();
91 float overlapZ2 = b.max.GetZ() - a.min.GetZ();
92
93 float minOverlap = overlapX1;
94 Vector3D normal(1, 0, 0);
95
96 if (overlapX2 < minOverlap) { minOverlap = overlapX2; normal = Vector3D(-1, 0, 0); }
97 if (overlapY1 < minOverlap) { minOverlap = overlapY1; normal = Vector3D(0, 1, 0); }
98 if (overlapY2 < minOverlap) { minOverlap = overlapY2; normal = Vector3D(0, -1, 0); }
99 if (overlapZ1 < minOverlap) { minOverlap = overlapZ1; normal = Vector3D(0, 0, 1); }
100 if (overlapZ2 < minOverlap) { minOverlap = overlapZ2; normal = Vector3D(0, 0, -1); }
101
102 result.hasCollision = true;
103 result.contact.normal = normal;
104 result.contact.penetration = minOverlap;
105 result.contact.point = (a.GetCenter() + b.GetCenter()) * 0.5f;
106 return result;
107}
108
110 CollisionManifold result;
111
112 Vector3D diff = b.center - a.center;
113 float dist = diff.Magnitude();
114 float sumR = a.radius + b.radius;
115
116 if (dist >= sumR) return result;
117
118 result.hasCollision = true;
119 if (dist > 1e-8f) {
120 result.contact.normal = diff * (1.0f / dist);
121 } else {
122 result.contact.normal = Vector3D(0, 1, 0);
123 }
124 result.contact.penetration = sumR - dist;
125 result.contact.point = a.center + result.contact.normal * a.radius;
126 return result;
127}
128
130 CollisionManifold result;
131
132 Vector3D closest = ClosestPointOnAABB(a, b.center);
133 Vector3D diff = b.center - closest;
134 float distSq = diff.Dot(diff);
135
136 if (distSq >= b.radius * b.radius) return result;
137
138 float dist = std::sqrt(distSq);
139 result.hasCollision = true;
140 if (dist > 1e-8f) {
141 result.contact.normal = diff * (1.0f / dist);
142 } else {
143 result.contact.normal = Vector3D(0, 1, 0);
144 }
145 result.contact.penetration = b.radius - dist;
146 result.contact.point = closest;
147 return result;
148}
149
151 Vector3D capA = b.GetPointA();
152 Vector3D capB = b.GetPointB();
153
154 Vector3D closest = ClosestPointOnSegment(a.center, capA, capB);
155
156 BoundingSphere capSphere(closest, b.radius);
157 return TestSphereVsSphere(a, capSphere);
158}
159
161 Vector3D capA = b.GetPointA();
162 Vector3D capB = b.GetPointB();
163
164 // Find closest point on capsule segment to AABB center, then do AABB vs Sphere
165 Vector3D aabbCenter = a.GetCenter();
166 Vector3D closestOnSeg = ClosestPointOnSegment(aabbCenter, capA, capB);
167
168 BoundingSphere testSphere(closestOnSeg, b.radius);
169 return TestAABBvsSphere(a, testSphere);
170}
171
173 Vector3D a1 = a.GetPointA(), a2 = a.GetPointB();
174 Vector3D b1 = b.GetPointA(), b2 = b.GetPointB();
175
176 Vector3D closestA, closestB;
177 ClosestPointsSegmentSegment(a1, a2, b1, b2, closestA, closestB);
178
179 BoundingSphere sA(closestA, a.radius);
180 BoundingSphere sB(closestB, b.radius);
181 return TestSphereVsSphere(sA, sB);
182}
183
185 const Vector3D& v0, const Vector3D& v1, const Vector3D& v2) {
186 CollisionManifold result;
187
188 // Project sphere center onto triangle plane
189 Vector3D edge0 = v1 - v0;
190 Vector3D edge1 = v2 - v0;
191 Vector3D n = edge0.Cross(edge1);
192 float nLen = n.Magnitude();
193 if (nLen < 1e-8f) return result;
194 n = n * (1.0f / nLen);
195
196 float dist = (sphere.center - v0).Dot(n);
197 if (std::abs(dist) > sphere.radius) return result;
198
199 // Closest point on triangle
200 Vector3D proj = sphere.center - n * dist;
201
202 // Barycentric test - check if projected point is inside triangle
203 Vector3D v0p = proj - v0;
204 float d00 = edge0.Dot(edge0);
205 float d01 = edge0.Dot(edge1);
206 float d11 = edge1.Dot(edge1);
207 float d20 = v0p.Dot(edge0);
208 float d21 = v0p.Dot(edge1);
209 float denom = d00 * d11 - d01 * d01;
210
211 if (std::abs(denom) < 1e-8f) return result;
212
213 float bv = (d11 * d20 - d01 * d21) / denom;
214 float bw = (d00 * d21 - d01 * d20) / denom;
215 float bu = 1.0f - bv - bw;
216
217 Vector3D closestPoint;
218 if (bu >= 0 && bv >= 0 && bw >= 0) {
219 closestPoint = proj;
220 } else {
221 // Closest point on edges
222 Vector3D c0 = ClosestPointOnSegment(sphere.center, v0, v1);
223 Vector3D c1 = ClosestPointOnSegment(sphere.center, v1, v2);
224 Vector3D c2 = ClosestPointOnSegment(sphere.center, v2, v0);
225
226 float d0 = (sphere.center - c0).Dot(sphere.center - c0);
227 float d1 = (sphere.center - c1).Dot(sphere.center - c1);
228 float d2 = (sphere.center - c2).Dot(sphere.center - c2);
229
230 closestPoint = c0;
231 float minD = d0;
232 if (d1 < minD) { minD = d1; closestPoint = c1; }
233 if (d2 < minD) { closestPoint = c2; }
234 }
235
236 Vector3D diff = sphere.center - closestPoint;
237 float distSq = diff.Dot(diff);
238 if (distSq >= sphere.radius * sphere.radius) return result;
239
240 float d = std::sqrt(distSq);
241 result.hasCollision = true;
242 result.contact.point = closestPoint;
243 result.contact.penetration = sphere.radius - d;
244 if (d > 1e-8f) {
245 result.contact.normal = diff * (1.0f / d);
246 } else {
247 result.contact.normal = n;
248 }
249 return result;
250}
251
253 CollisionManifold deepest;
254
255 for (size_t i = 0; i + 2 < b.indices.size(); i += 3) {
256 const Vector3D& v0 = b.vertices[b.indices[i]];
257 const Vector3D& v1 = b.vertices[b.indices[i + 1]];
258 const Vector3D& v2 = b.vertices[b.indices[i + 2]];
259
260 CollisionManifold m = TestSphereVsTriangle(a, v0, v1, v2);
261 if (m.hasCollision && m.contact.penetration > deepest.contact.penetration) {
262 deepest = m;
263 }
264 }
265
266 return deepest;
267}
268
270 // Approximate: use sphere enclosing AABB, test against mesh
272 return TestSphereVsMesh(approx, b);
273}
274
275static AABB TransformAABB(const AABB& aabb, const Vector3D& pos, const Vector3D& scale) {
276 Vector3D sMin = aabb.min * scale + pos;
277 Vector3D sMax = aabb.max * scale + pos;
278 return AABB(
279 Vector3D(std::min(sMin.GetX(), sMax.GetX()),
280 std::min(sMin.GetY(), sMax.GetY()),
281 std::min(sMin.GetZ(), sMax.GetZ())),
282 Vector3D(std::max(sMin.GetX(), sMax.GetX()),
283 std::max(sMin.GetY(), sMax.GetY()),
284 std::max(sMin.GetZ(), sMax.GetZ()))
285 );
286}
287
288static BoundingSphere TransformSphere(const BoundingSphere& s, const Vector3D& pos, const Vector3D& scale) {
289 float maxScale = std::max({std::abs(scale.GetX()), std::abs(scale.GetY()), std::abs(scale.GetZ())});
290 return BoundingSphere(s.center * scale + pos, s.radius * maxScale);
291}
292
293static BoundingCapsule TransformCapsule(const BoundingCapsule& c, const Vector3D& pos, const Vector3D& scale) {
294 float maxScale = std::max({std::abs(scale.GetX()), std::abs(scale.GetY()), std::abs(scale.GetZ())});
295 float axisScale = 1.0f;
296 if (c.axis == 0) axisScale = std::abs(scale.GetX());
297 else if (c.axis == 1) axisScale = std::abs(scale.GetY());
298 else axisScale = std::abs(scale.GetZ());
299
300 return BoundingCapsule(c.center * scale + pos, c.radius * maxScale, c.halfHeight * axisScale, c.axis);
301}
302
303static TriangleMesh TransformMesh(const TriangleMesh& m, const Vector3D& pos, const Vector3D& scale) {
304 TriangleMesh result;
305 result.indices = m.indices;
306 result.vertices.resize(m.vertices.size());
307 for (size_t i = 0; i < m.vertices.size(); ++i) {
308 result.vertices[i] = m.vertices[i] * scale + pos;
309 }
310 result.bounds = TransformAABB(m.bounds, pos, scale);
311 return result;
312}
313
314CollisionManifold TestCollision(const ColliderShape& shapeA, const Vector3D& posA, const Vector3D& scaleA,
315 const ColliderShape& shapeB, const Vector3D& posB, const Vector3D& scaleB) {
316 return std::visit([&](const auto& a, const auto& b) -> CollisionManifold {
317 using A = std::decay_t<decltype(a)>;
318 using B = std::decay_t<decltype(b)>;
319
320 if constexpr (std::is_same_v<A, AABB> && std::is_same_v<B, AABB>) {
321 return TestAABBvsAABB(TransformAABB(a, posA, scaleA), TransformAABB(b, posB, scaleB));
322 }
323 else if constexpr (std::is_same_v<A, BoundingSphere> && std::is_same_v<B, BoundingSphere>) {
324 return TestSphereVsSphere(TransformSphere(a, posA, scaleA), TransformSphere(b, posB, scaleB));
325 }
326 else if constexpr (std::is_same_v<A, AABB> && std::is_same_v<B, BoundingSphere>) {
327 return TestAABBvsSphere(TransformAABB(a, posA, scaleA), TransformSphere(b, posB, scaleB));
328 }
329 else if constexpr (std::is_same_v<A, BoundingSphere> && std::is_same_v<B, AABB>) {
330 auto m = TestAABBvsSphere(TransformAABB(b, posB, scaleB), TransformSphere(a, posA, scaleA));
331 m.contact.normal = m.contact.normal * -1.0f;
332 return m;
333 }
334 else if constexpr (std::is_same_v<A, BoundingSphere> && std::is_same_v<B, BoundingCapsule>) {
335 return TestSphereVsCapsule(TransformSphere(a, posA, scaleA), TransformCapsule(b, posB, scaleB));
336 }
337 else if constexpr (std::is_same_v<A, BoundingCapsule> && std::is_same_v<B, BoundingSphere>) {
338 auto m = TestSphereVsCapsule(TransformSphere(b, posB, scaleB), TransformCapsule(a, posA, scaleA));
339 m.contact.normal = m.contact.normal * -1.0f;
340 return m;
341 }
342 else if constexpr (std::is_same_v<A, AABB> && std::is_same_v<B, BoundingCapsule>) {
343 return TestAABBvsCapsule(TransformAABB(a, posA, scaleA), TransformCapsule(b, posB, scaleB));
344 }
345 else if constexpr (std::is_same_v<A, BoundingCapsule> && std::is_same_v<B, AABB>) {
346 auto m = TestAABBvsCapsule(TransformAABB(b, posB, scaleB), TransformCapsule(a, posA, scaleA));
347 m.contact.normal = m.contact.normal * -1.0f;
348 return m;
349 }
350 else if constexpr (std::is_same_v<A, BoundingCapsule> && std::is_same_v<B, BoundingCapsule>) {
351 return TestCapsuleVsCapsule(TransformCapsule(a, posA, scaleA), TransformCapsule(b, posB, scaleB));
352 }
353 else if constexpr (std::is_same_v<A, BoundingSphere> && std::is_same_v<B, TriangleMesh>) {
354 return TestSphereVsMesh(TransformSphere(a, posA, scaleA), TransformMesh(b, posB, scaleB));
355 }
356 else if constexpr (std::is_same_v<A, TriangleMesh> && std::is_same_v<B, BoundingSphere>) {
357 auto m = TestSphereVsMesh(TransformSphere(b, posB, scaleB), TransformMesh(a, posA, scaleA));
358 m.contact.normal = m.contact.normal * -1.0f;
359 return m;
360 }
361 else if constexpr (std::is_same_v<A, AABB> && std::is_same_v<B, TriangleMesh>) {
362 return TestAABBvsMesh(TransformAABB(a, posA, scaleA), TransformMesh(b, posB, scaleB));
363 }
364 else if constexpr (std::is_same_v<A, TriangleMesh> && std::is_same_v<B, AABB>) {
365 auto m = TestAABBvsMesh(TransformAABB(b, posB, scaleB), TransformMesh(a, posA, scaleA));
366 m.contact.normal = m.contact.normal * -1.0f;
367 return m;
368 }
369 else {
370 // TriangleMesh vs TriangleMesh or Capsule vs Mesh - not supported yet
371 return CollisionManifold{};
372 }
373 }, shapeA, shapeB);
374}
375
376} // namespace Physics
377} // namespace Sleak
float GetY() const
Definition Vector.hpp:361
float Dot(const Vector3D &other) const
Definition Vector.hpp:417
float GetX() const
Definition Vector.hpp:360
float GetZ() const
Definition Vector.hpp:362
Vector3D Cross(const Vector3D &other) const
Definition Vector.hpp:421
float Magnitude() const
Definition Vector.hpp:430
Collision shapes, the broadphase tree, and the world that steps them.
Definition SceneBase.hpp:29
void ClosestPointsSegmentSegment(const Vector3D &a1, const Vector3D &a2, const Vector3D &b1, const Vector3D &b2, Vector3D &closestA, Vector3D &closestB)
Nearest points between two line segments, used for capsule-vs-capsule tests.
CollisionManifold TestSphereVsTriangle(const BoundingSphere &sphere, const Vector3D &v0, const Vector3D &v1, const Vector3D &v2)
static BoundingSphere TransformSphere(const BoundingSphere &s, const Vector3D &pos, const Vector3D &scale)
std::variant< AABB, BoundingSphere, BoundingCapsule, TriangleMesh > ColliderShape
Tagged union of the shapes a ColliderComponent can hold.
CollisionManifold TestCapsuleVsCapsule(const BoundingCapsule &a, const BoundingCapsule &b)
Vector3D ClosestPointOnSegment(const Vector3D &point, const Vector3D &a, const Vector3D &b)
Nearest point on segment [a, b] to the given point.
CollisionManifold TestAABBvsCapsule(const AABB &a, const BoundingCapsule &b)
CollisionManifold TestSphereVsMesh(const BoundingSphere &a, const TriangleMesh &b)
CollisionManifold TestAABBvsAABB(const AABB &a, const AABB &b)
Narrow-phase collision tests; the resulting normal always points from A to B.
static Vector3D ClosestPointOnAABB(const AABB &aabb, const Vector3D &point)
Clamps point into the box, giving the nearest surface or interior point.
CollisionManifold TestAABBvsSphere(const AABB &a, const BoundingSphere &b)
CollisionManifold TestSphereVsCapsule(const BoundingSphere &a, const BoundingCapsule &b)
CollisionManifold TestSphereVsSphere(const BoundingSphere &a, const BoundingSphere &b)
static TriangleMesh TransformMesh(const TriangleMesh &m, const Vector3D &pos, const Vector3D &scale)
static BoundingCapsule TransformCapsule(const BoundingCapsule &c, const Vector3D &pos, const Vector3D &scale)
CollisionManifold TestCollision(const ColliderShape &shapeA, const Vector3D &posA, const Vector3D &scaleA, const ColliderShape &shapeB, const Vector3D &posB, const Vector3D &scaleB)
Dispatches to the right narrow-phase test based on the runtime shape held by each variant.
CollisionManifold TestAABBvsMesh(const AABB &a, const TriangleMesh &b)
static AABB TransformAABB(const AABB &aabb, const Vector3D &pos, const Vector3D &scale)
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
Vector3D GetCenter() const
Definition Colliders.hpp:27
bool Overlaps(const AABB &other) const
Definition Colliders.hpp:46
Vector3D GetPointA() const
World position of the capsule's positive-axis cap center.
Vector3D GetPointB() const
World position of the capsule's negative-axis cap center.
static BoundingSphere FromAABB(const AABB &aabb)
Builds the sphere circumscribing the given AABB.
std::vector< Vector3D > vertices
std::vector< uint32_t > indices