SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
Colliders.hpp
Go to the documentation of this file.
1#ifndef _COLLIDERS_HPP_
2#define _COLLIDERS_HPP_
3
4#include <Math/Vector.hpp>
6#include <variant>
7#include <vector>
8#include <cstdint>
9#include <algorithm>
10#include <cmath>
11#include <limits>
12
13namespace Sleak {
14namespace Physics {
15
16 using Math::Vector3D;
17
18 /// Axis-aligned bounding box; the workhorse broadphase and collision shape.
19 /// @ingroup physics
20 struct AABB {
23
24 AABB() : min(Vector3D(0, 0, 0)), max(Vector3D(0, 0, 0)) {}
25 AABB(const Vector3D& min, const Vector3D& max) : min(min), max(max) {}
26
28 return (min + max) * 0.5f;
29 }
30
32 return (max - min) * 0.5f;
33 }
34
35 float GetSurfaceArea() const {
36 Vector3D d = max - min;
37 return 2.0f * (d.GetX() * d.GetY() + d.GetY() * d.GetZ() + d.GetZ() * d.GetX());
38 }
39
40 bool Contains(const Vector3D& point) const {
41 return point.GetX() >= min.GetX() && point.GetX() <= max.GetX() &&
42 point.GetY() >= min.GetY() && point.GetY() <= max.GetY() &&
43 point.GetZ() >= min.GetZ() && point.GetZ() <= max.GetZ();
44 }
45
46 bool Overlaps(const AABB& other) const {
47 if (max.GetX() < other.min.GetX() || min.GetX() > other.max.GetX()) return false;
48 if (max.GetY() < other.min.GetY() || min.GetY() > other.max.GetY()) return false;
49 if (max.GetZ() < other.min.GetZ() || min.GetZ() > other.max.GetZ()) return false;
50 return true;
51 }
52
53 AABB Merge(const AABB& other) const {
54 return AABB(
55 Vector3D(std::min(min.GetX(), other.min.GetX()),
56 std::min(min.GetY(), other.min.GetY()),
57 std::min(min.GetZ(), other.min.GetZ())),
58 Vector3D(std::max(max.GetX(), other.max.GetX()),
59 std::max(max.GetY(), other.max.GetY()),
60 std::max(max.GetZ(), other.max.GetZ()))
61 );
62 }
63
64 AABB Fatten(float margin) const {
65 return AABB(
66 Vector3D(min.GetX() - margin, min.GetY() - margin, min.GetZ() - margin),
67 Vector3D(max.GetX() + margin, max.GetY() + margin, max.GetZ() + margin)
68 );
69 }
70
71 /// Computes a tight AABB over raw interleaved vertex positions.
72 static AABB FromVertices(const float* positions, size_t count, size_t stride) {
73 if (count == 0) return AABB();
74
75 float minX = std::numeric_limits<float>::max();
76 float minY = std::numeric_limits<float>::max();
77 float minZ = std::numeric_limits<float>::max();
78 float maxX = std::numeric_limits<float>::lowest();
79 float maxY = std::numeric_limits<float>::lowest();
80 float maxZ = std::numeric_limits<float>::lowest();
81
82 const char* ptr = reinterpret_cast<const char*>(positions);
83 for (size_t i = 0; i < count; ++i) {
84 const float* pos = reinterpret_cast<const float*>(ptr + i * stride);
85 if (pos[0] < minX) minX = pos[0];
86 if (pos[1] < minY) minY = pos[1];
87 if (pos[2] < minZ) minZ = pos[2];
88 if (pos[0] > maxX) maxX = pos[0];
89 if (pos[1] > maxY) maxY = pos[1];
90 if (pos[2] > maxZ) maxZ = pos[2];
91 }
92
93 return AABB(Vector3D(minX, minY, minZ), Vector3D(maxX, maxY, maxZ));
94 }
95 };
96
97 /// Sphere collider, cheapest shape to test against.
98 /// @ingroup physics
101 float radius;
102
103 BoundingSphere() : center(Vector3D(0, 0, 0)), radius(0.0f) {}
106
107 bool Contains(const Vector3D& point) const {
108 return (point - center).Magnitude() <= radius;
109 }
110
111 bool Overlaps(const BoundingSphere& other) const {
112 float dist = (center - other.center).Magnitude();
113 return dist <= (radius + other.radius);
114 }
115
116 AABB ToAABB() const {
117 return AABB(
118 Vector3D(center.GetX() - radius, center.GetY() - radius, center.GetZ() - radius),
119 Vector3D(center.GetX() + radius, center.GetY() + radius, center.GetZ() + radius)
120 );
121 }
122
123 /// Builds the sphere circumscribing the given AABB.
124 static BoundingSphere FromAABB(const AABB& aabb) {
125 Vector3D center = aabb.GetCenter();
126 float radius = (aabb.max - center).Magnitude();
128 }
129 };
130
131 /// Cylinder-plus-hemispherical-caps shape, commonly used for character controllers.
132 /// @ingroup physics
135 float radius;
137 int axis; // 0=X, 1=Y, 2=Z
138
140 : center(Vector3D(0, 0, 0)), radius(0.5f), halfHeight(0.5f), axis(1) {}
143
144 /// World position of the capsule's positive-axis cap center.
146 Vector3D offset(0, 0, 0);
147 if (axis == 0) offset.SetX(halfHeight);
148 else if (axis == 1) offset.SetY(halfHeight);
149 else offset.SetZ(halfHeight);
150 return center + offset;
151 }
152
153 /// World position of the capsule's negative-axis cap center.
155 Vector3D offset(0, 0, 0);
156 if (axis == 0) offset.SetX(-halfHeight);
157 else if (axis == 1) offset.SetY(-halfHeight);
158 else offset.SetZ(-halfHeight);
159 return center + offset;
160 }
161
162 AABB ToAABB() const {
163 Vector3D a = GetPointA();
164 Vector3D b = GetPointB();
165 Vector3D minPt(
166 std::min(a.GetX(), b.GetX()) - radius,
167 std::min(a.GetY(), b.GetY()) - radius,
168 std::min(a.GetZ(), b.GetZ()) - radius
169 );
170 Vector3D maxPt(
171 std::max(a.GetX(), b.GetX()) + radius,
172 std::max(a.GetY(), b.GetY()) + radius,
173 std::max(a.GetZ(), b.GetZ()) + radius
174 );
175 return AABB(minPt, maxPt);
176 }
177
178 /// Fits a capsule inside the AABB, picking its longest axis as the capsule axis.
179 static BoundingCapsule FromAABB(const AABB& aabb) {
180 Vector3D center = aabb.GetCenter();
181 Vector3D extents = aabb.GetExtents();
182
183 // Choose longest axis
184 int axis = 1;
185 float maxExt = extents.GetY();
186 if (extents.GetX() > maxExt) { axis = 0; maxExt = extents.GetX(); }
187 if (extents.GetZ() > maxExt) { axis = 2; maxExt = extents.GetZ(); }
188
189 // Radius from the shorter two axes
190 float r = 0.0f;
191 if (axis == 0) r = std::max(extents.GetY(), extents.GetZ());
192 else if (axis == 1) r = std::max(extents.GetX(), extents.GetZ());
193 else r = std::max(extents.GetX(), extents.GetY());
194
195 float halfHeight = maxExt - r;
196 if (halfHeight < 0.0f) halfHeight = 0.0f;
197
199 }
200 };
201
202 /// Exact triangle-soup collision shape; heavier than the primitive shapes, used for static geometry.
203 /// @ingroup physics
205 std::vector<Vector3D> vertices;
206 std::vector<uint32_t> indices;
208
209 /// Copies vertex positions and indices out of raw mesh buffers and recomputes bounds.
210 void Build(const float* positions, size_t count, size_t stride,
211 const uint32_t* indexData, size_t indexCount) {
212 vertices.resize(count);
213 const char* ptr = reinterpret_cast<const char*>(positions);
214 for (size_t i = 0; i < count; ++i) {
215 const float* pos = reinterpret_cast<const float*>(ptr + i * stride);
216 vertices[i] = Vector3D(pos[0], pos[1], pos[2]);
217 }
218 indices.assign(indexData, indexData + indexCount);
219 bounds = AABB::FromVertices(positions, count, stride);
220 }
221 };
222
223 /// Discriminates which alternative of ColliderShape a collider currently holds.
224 /// @ingroup physics
231
232 /// Tagged union of the shapes a ColliderComponent can hold.
233 using ColliderShape = std::variant<AABB, BoundingSphere, BoundingCapsule, TriangleMesh>;
234
235 /// Transforms a local-space collider shape into a world-space AABB for broadphase queries.
236 inline AABB GetWorldAABB(const ColliderShape& shape, const Vector3D& worldPos, const Vector3D& worldScale) {
237 AABB local;
238
239 if (auto* aabb = std::get_if<AABB>(&shape)) {
240 local = *aabb;
241 } else if (auto* sphere = std::get_if<BoundingSphere>(&shape)) {
242 local = sphere->ToAABB();
243 } else if (auto* capsule = std::get_if<BoundingCapsule>(&shape)) {
244 local = capsule->ToAABB();
245 } else if (auto* mesh = std::get_if<TriangleMesh>(&shape)) {
246 local = mesh->bounds;
247 }
248
249 // Scale then translate
250 Vector3D scaledMin = local.min * worldScale + worldPos;
251 Vector3D scaledMax = local.max * worldScale + worldPos;
252
253 // Fix inverted axes from negative scale
254 return AABB(
255 Vector3D(std::min(scaledMin.GetX(), scaledMax.GetX()),
256 std::min(scaledMin.GetY(), scaledMax.GetY()),
257 std::min(scaledMin.GetZ(), scaledMax.GetZ())),
258 Vector3D(std::max(scaledMin.GetX(), scaledMax.GetX()),
259 std::max(scaledMin.GetY(), scaledMax.GetY()),
260 std::max(scaledMin.GetZ(), scaledMax.GetZ()))
261 );
262 }
263
264} // namespace Physics
265} // namespace Sleak
266
267#endif // _COLLIDERS_HPP_
float GetY() const
Definition Vector.hpp:361
void SetY(float val)
Definition Vector.hpp:366
float GetX() const
Definition Vector.hpp:360
float GetZ() const
Definition Vector.hpp:362
void SetX(float val)
Definition Vector.hpp:365
void SetZ(float val)
Definition Vector.hpp:367
Collision shapes, the broadphase tree, and the world that steps them.
Definition SceneBase.hpp:29
std::variant< AABB, BoundingSphere, BoundingCapsule, TriangleMesh > ColliderShape
Tagged union of the shapes a ColliderComponent can hold.
AABB GetWorldAABB(const ColliderShape &shape, const Vector3D &worldPos, const Vector3D &worldScale)
Transforms a local-space collider shape into a world-space AABB for broadphase queries.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
Vector3D GetCenter() const
Definition Colliders.hpp:27
AABB Fatten(float margin) const
Definition Colliders.hpp:64
bool Contains(const Vector3D &point) const
Definition Colliders.hpp:40
AABB(const Vector3D &min, const Vector3D &max)
Definition Colliders.hpp:25
Vector3D GetExtents() const
Definition Colliders.hpp:31
AABB Merge(const AABB &other) const
Definition Colliders.hpp:53
float GetSurfaceArea() const
Definition Colliders.hpp:35
bool Overlaps(const AABB &other) const
Definition Colliders.hpp:46
static AABB FromVertices(const float *positions, size_t count, size_t stride)
Computes a tight AABB over raw interleaved vertex positions.
Definition Colliders.hpp:72
BoundingCapsule(const Vector3D &center, float radius, float halfHeight, int axis=1)
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 BoundingCapsule FromAABB(const AABB &aabb)
Fits a capsule inside the AABB, picking its longest axis as the capsule axis.
static BoundingSphere FromAABB(const AABB &aabb)
Builds the sphere circumscribing the given AABB.
bool Contains(const Vector3D &point) const
bool Overlaps(const BoundingSphere &other) const
BoundingSphere(const Vector3D &center, float radius)
std::vector< Vector3D > vertices
void Build(const float *positions, size_t count, size_t stride, const uint32_t *indexData, size_t indexCount)
Copies vertex positions and indices out of raw mesh buffers and recomputes bounds.
std::vector< uint32_t > indices