SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
PhysicsWorld.cpp
Go to the documentation of this file.
4#include <Core/GameObject.hpp>
5#include <Camera/Camera.hpp>
7#include <Core/Logger.hpp>
8#include <algorithm>
9
10namespace Sleak {
11namespace Physics {
12
14 if (!collider) return;
15
16 // Check not already registered
17 for (auto* c : m_colliders) {
18 if (c == collider) return;
19 }
20
21 AABB worldAABB = collider->GetWorldAABB();
22 int proxyId = m_tree.Insert(worldAABB, collider);
23 collider->SetProxyId(proxyId);
24 m_colliders.push_back(collider);
25}
26
28 if (!collider) return;
29
30 int proxyId = collider->GetProxyId();
31 if (proxyId >= 0) {
32 m_tree.Remove(proxyId);
33 collider->SetProxyId(-1);
34 }
35
36 m_colliders.erase(
37 std::remove(m_colliders.begin(), m_colliders.end(), collider),
38 m_colliders.end());
39}
40
41void PhysicsWorld::Step(float dt) {
42 // We need wasGrounded BEFORE clearing, so gravity doesn't apply while standing
43 for (auto* collider : m_colliders) {
44 if (auto* owner = collider->GetOwner()) {
45 auto* rb = owner->GetComponent<RigidbodyComponent>();
46 if (!rb) continue;
47
48 bool wasGrounded = rb->IsGrounded();
49 rb->ClearCollisionState();
50
51 if (rb->GetBodyType() == BodyType::Dynamic) {
52 // Reset grounded — collision detection will re-set it if still touching ground
53 rb->SetGrounded(false);
54
55 if (rb->GetUseGravity()) {
56 Math::Vector3D vel = rb->GetVelocity();
57
58 if (!wasGrounded) {
59 // Airborne: apply full gravity
60 vel = vel + rb->GetGravity() * dt;
61 } else {
62 // Grounded: apply small downward force to maintain ground contact
63 // This ensures collision detection keeps finding the floor
64 // without causing visible jitter
65 if (vel.GetY() <= 0.0f) {
66 vel = Math::Vector3D(vel.GetX(), -0.5f, vel.GetZ());
67 }
68 // If vel.Y > 0 (jumping), don't override — let the jump happen
69 }
70
71 // Clamp to terminal velocity
72 float termVel = rb->GetTerminalVelocity();
73 if (vel.GetY() < -termVel) {
74 vel = Math::Vector3D(vel.GetX(), -termVel, vel.GetZ());
75 }
76
77 rb->SetVelocity(vel);
78 }
79
80 Math::Vector3D vel = rb->GetVelocity();
81 Math::Vector3D delta = vel * dt;
82
83 auto* transform = owner->GetComponent<TransformComponent>();
84 if (transform) {
85 transform->Translate(delta);
86 } else if (auto* cam = dynamic_cast<Camera*>(owner)) {
87 cam->AddPosition(delta);
88 }
89 }
90 }
91 }
92
93 UpdateBroadphase();
94 FindPairsAndResolve();
95}
96
97void PhysicsWorld::UpdateBroadphase() {
98 for (auto* collider : m_colliders) {
99 int proxyId = collider->GetProxyId();
100 if (proxyId < 0) continue;
101
102 AABB newAABB = collider->GetWorldAABB();
103 m_tree.MoveProxy(proxyId, newAABB, Vector3D(0, 0, 0));
104 }
105}
106
107void PhysicsWorld::FindPairsAndResolve() {
108 for (size_t i = 0; i < m_colliders.size(); ++i) {
109 ColliderComponent* colliderA = m_colliders[i];
110 if (colliderA->GetProxyId() < 0) continue;
111
112 AABB worldA = colliderA->GetWorldAABB();
113
114 m_tree.Query(worldA, [&](int proxyId) -> bool {
115 auto* colliderB = static_cast<ColliderComponent*>(m_tree.GetUserData(proxyId));
116 if (colliderB == colliderA) return true;
117
118 // Skip duplicate pairs: only process when A < B (pointer order)
119 if (colliderA > colliderB) return true;
120
121 // Layer filtering
122 if ((colliderA->GetLayer() & colliderB->GetMask()) == 0) return true;
123 if ((colliderB->GetLayer() & colliderA->GetMask()) == 0) return true;
124
125 // Get transform data
126 auto* ownerA = colliderA->GetOwner();
127 auto* ownerB = colliderB->GetOwner();
128 if (!ownerA || !ownerB) return true;
129
130 Vector3D posA, posB;
131 Vector3D scaleA(1, 1, 1), scaleB(1, 1, 1);
132
133 auto* transformA = ownerA->GetComponent<TransformComponent>();
134 if (transformA) {
135 posA = transformA->GetWorldPosition() + colliderA->GetOffset();
136 scaleA = transformA->GetWorldScale();
137 } else if (auto* camA = dynamic_cast<Camera*>(ownerA)) {
138 posA = camA->GetPosition() + colliderA->GetOffset();
139 }
140
141 auto* transformB = ownerB->GetComponent<TransformComponent>();
142 if (transformB) {
143 posB = transformB->GetWorldPosition() + colliderB->GetOffset();
144 scaleB = transformB->GetWorldScale();
145 } else if (auto* camB = dynamic_cast<Camera*>(ownerB)) {
146 posB = camB->GetPosition() + colliderB->GetOffset();
147 }
148
149 CollisionManifold manifold = TestCollision(
150 colliderA->GetShape(), posA, scaleA,
151 colliderB->GetShape(), posB, scaleB);
152
153 if (!manifold.hasCollision) return true;
154
155 // Skip if both are triggers
156 if (colliderA->IsTrigger() && colliderB->IsTrigger()) return true;
157
158 // Resolve: push rigidbodies apart
159 auto* rbA = ownerA->GetComponent<RigidbodyComponent>();
160 auto* rbB = ownerB->GetComponent<RigidbodyComponent>();
161
162 if (rbA && rbA->GetBodyType() != BodyType::Static) {
163 rbA->ResolveCollision(manifold.contact.normal * -1.0f,
164 manifold.contact.penetration);
165 }
166 if (rbB && rbB->GetBodyType() != BodyType::Static) {
167 rbB->ResolveCollision(manifold.contact.normal,
168 manifold.contact.penetration);
169 }
170
171 return true;
172 });
173 }
174}
175
176std::vector<CollisionPair> PhysicsWorld::OverlapSphere(const Vector3D& center, float radius, uint32_t layerMask) const {
177 std::vector<CollisionPair> results;
178 BoundingSphere sphere(center, radius);
179 AABB queryAABB = sphere.ToAABB();
180
181 m_tree.Query(queryAABB, [&](int proxyId) -> bool {
182 auto* collider = static_cast<ColliderComponent*>(m_tree.GetUserData(proxyId));
183 if ((collider->GetLayer() & layerMask) == 0) return true;
184
185 CollisionPair pair;
186 pair.b = collider;
187 results.push_back(pair);
188 return true;
189 });
190
191 return results;
192}
193
194std::vector<CollisionPair> PhysicsWorld::OverlapAABB(const AABB& aabb, uint32_t layerMask) const {
195 std::vector<CollisionPair> results;
196
197 m_tree.Query(aabb, [&](int proxyId) -> bool {
198 auto* collider = static_cast<ColliderComponent*>(m_tree.GetUserData(proxyId));
199 if ((collider->GetLayer() & layerMask) == 0) return true;
200
201 CollisionPair pair;
202 pair.b = collider;
203 results.push_back(pair);
204 return true;
205 });
206
207 return results;
208}
209
211 float radius, float maxDist, uint32_t layerMask) const {
212 SweepResult result;
213
214 // Expand ray into a fat AABB for broadphase query
215 Vector3D end = start + direction * maxDist;
216 AABB sweepAABB(
217 Vector3D(std::min(start.GetX(), end.GetX()) - radius,
218 std::min(start.GetY(), end.GetY()) - radius,
219 std::min(start.GetZ(), end.GetZ()) - radius),
220 Vector3D(std::max(start.GetX(), end.GetX()) + radius,
221 std::max(start.GetY(), end.GetY()) + radius,
222 std::max(start.GetZ(), end.GetZ()) + radius)
223 );
224
225 float closestDist = maxDist;
226
227 m_tree.Query(sweepAABB, [&](int proxyId) -> bool {
228 auto* collider = static_cast<ColliderComponent*>(m_tree.GetUserData(proxyId));
229 if ((collider->GetLayer() & layerMask) == 0) return true;
230
231 // Step along the sweep direction testing sphere collisions
232 AABB targetAABB = collider->GetWorldAABB();
233 Vector3D targetCenter = targetAABB.GetCenter();
234 Vector3D targetExtents = targetAABB.GetExtents();
235
236 // Expand target AABB by sweep radius for simplified test
237 AABB expandedTarget(
238 Vector3D(targetAABB.min.GetX() - radius,
239 targetAABB.min.GetY() - radius,
240 targetAABB.min.GetZ() - radius),
241 Vector3D(targetAABB.max.GetX() + radius,
242 targetAABB.max.GetY() + radius,
243 targetAABB.max.GetZ() + radius)
244 );
245
246 // Ray vs expanded AABB
247 Vector3D invDir(
248 std::abs(direction.GetX()) > 1e-8f ? 1.0f / direction.GetX() : 1e8f,
249 std::abs(direction.GetY()) > 1e-8f ? 1.0f / direction.GetY() : 1e8f,
250 std::abs(direction.GetZ()) > 1e-8f ? 1.0f / direction.GetZ() : 1e8f
251 );
252
253 float t1x = (expandedTarget.min.GetX() - start.GetX()) * invDir.GetX();
254 float t2x = (expandedTarget.max.GetX() - start.GetX()) * invDir.GetX();
255 float t1y = (expandedTarget.min.GetY() - start.GetY()) * invDir.GetY();
256 float t2y = (expandedTarget.max.GetY() - start.GetY()) * invDir.GetY();
257 float t1z = (expandedTarget.min.GetZ() - start.GetZ()) * invDir.GetZ();
258 float t2z = (expandedTarget.max.GetZ() - start.GetZ()) * invDir.GetZ();
259
260 float tmin = std::max({std::min(t1x, t2x), std::min(t1y, t2y), std::min(t1z, t2z)});
261 float tmax = std::min({std::max(t1x, t2x), std::max(t1y, t2y), std::max(t1z, t2z)});
262
263 if (tmax < 0 || tmin > tmax || tmin > closestDist) return true;
264
265 float hitDist = std::max(tmin, 0.0f);
266 if (hitDist < closestDist) {
267 closestDist = hitDist;
268 result.hit = true;
269 result.collider = collider;
270 result.distance = hitDist;
271 result.point = start + direction * hitDist;
272
273 // Compute approximate normal from hit point
274 Vector3D hitPt = result.point;
275 Vector3D diff = hitPt - targetCenter;
276
277 // Find which face we hit
278 float ax = std::abs(diff.GetX()) / std::max(targetExtents.GetX(), 0.001f);
279 float ay = std::abs(diff.GetY()) / std::max(targetExtents.GetY(), 0.001f);
280 float az = std::abs(diff.GetZ()) / std::max(targetExtents.GetZ(), 0.001f);
281
282 if (ax > ay && ax > az) {
283 result.normal = Vector3D(diff.GetX() > 0 ? 1.0f : -1.0f, 0, 0);
284 } else if (ay > az) {
285 result.normal = Vector3D(0, diff.GetY() > 0 ? 1.0f : -1.0f, 0);
286 } else {
287 result.normal = Vector3D(0, 0, diff.GetZ() > 0 ? 1.0f : -1.0f);
288 }
289 }
290
291 return true;
292 });
293
294 return result;
295}
296
297RayHit PhysicsWorld::Raycast(const Vector3D& origin, const Vector3D& direction,
298 float maxDist, uint32_t layerMask) const {
299 // Raycast is sphere sweep with radius 0, but use direct ray-AABB for better precision
300 SweepResult sweep = SphereSweep(origin, direction, 0.01f, maxDist, layerMask);
301 RayHit result;
302 result.hit = sweep.hit;
303 result.collider = sweep.collider;
304 result.point = sweep.point;
305 result.normal = sweep.normal;
306 result.distance = sweep.distance;
307 return result;
308}
309
310} // namespace Physics
311} // namespace Sleak
Physics::AABB GetWorldAABB() const
Local shape transformed into world space by the owner's current transform.
float GetY() const
Definition Vector.hpp:361
float GetX() const
Definition Vector.hpp:360
float GetZ() const
Definition Vector.hpp:362
void Query(const AABB &queryAABB, const std::function< bool(int)> &callback) const
Visits every leaf whose fat AABB overlaps queryAABB; stop early by returning false from callback.
bool MoveProxy(int proxyId, const AABB &newAABB, const Vector3D &displacement)
Refits a proxy's fat AABB to newAABB, re-inserting it only if it moved outside the fat margin.
void * GetUserData(int proxyId) const
std::vector< CollisionPair > OverlapSphere(const Vector3D &center, float radius, uint32_t layerMask=0xFFFFFFFF) const
Query API: colliders overlapping a sphere, filtered by layerMask.
void RegisterCollider(ColliderComponent *collider)
void UnregisterCollider(ColliderComponent *collider)
SweepResult SphereSweep(const Vector3D &start, const Vector3D &direction, float radius, float maxDist, uint32_t layerMask=0xFFFFFFFF) const
Sweeps a sphere from start along direction and returns the first collider it hits within maxDist.
RayHit Raycast(const Vector3D &origin, const Vector3D &direction, float maxDist, uint32_t layerMask=0xFFFFFFFF) const
Casts a ray and returns the closest collider hit within maxDist.
std::vector< CollisionPair > OverlapAABB(const AABB &aabb, uint32_t layerMask=0xFFFFFFFF) const
Colliders overlapping an AABB, filtered by layerMask.
Represents the position, rotation, and scale of an entity in 3D space.
Collision shapes, the broadphase tree, and the world that steps them.
Definition SceneBase.hpp:29
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.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
Vector3D GetCenter() const
Definition Colliders.hpp:27
Vector3D GetExtents() const
Definition Colliders.hpp:31
ColliderComponent * collider
ColliderComponent * collider