VEX Engine
Retro looking game engine made in vulkan mostly because i wanted to understand it better.
Loading...
Searching...
No Matches
PhysicsSystem.cpp
1#ifndef GLM_ENABLE_EXPERIMENTAL
2 #define GLM_ENABLE_EXPERIMENTAL 1
3#endif
5#include <glm/gtc/constants.hpp>
6#include <glm/gtx/matrix_decompose.hpp>
7#include <components/ErrorUtils.hpp>
8#include <thread>
9
10#if defined(__cpp_lib_execution) && defined(__cpp_lib_parallel_algorithm)
11 #include <execution>
12 #define VEX_HAS_PARALLEL_EXECUTION
13#endif
14
15#include <map>
16
18
19#ifdef _WIN32
20#pragma push_macro("max")
21#undef max
22#endif
23
24namespace vex {
25
26 inline void fastSyncTransform(const glm::vec3& pos, const glm::quat& rot,
27 JPH::BodyInterface& bi, const JPH::BodyID& id) {
28 JPH::RVec3 jPos(pos.x, pos.y, pos.z);
29 JPH::Quat jRot(rot.x, rot.y, rot.z, rot.w);
30
31 bi.SetPositionAndRotation(id, jPos, jRot, JPH::EActivation::DontActivate);
32 }
33
34 bool PhysicsSystem::init(size_t maxBodies) {
35 JPH::RegisterDefaultAllocator();
36
37 if (JPH::Factory::sInstance == nullptr) {
38 JPH::Factory::sInstance = new JPH::Factory();
39 }
40
41 JPH::RegisterTypes();
42
44 m_tempAllocator = new JPH::TempAllocatorImpl(10 * 1024 * 1024);
45
46 m_jobSystem = new JPH::JobSystemThreadPool(
47 1024,
48 256,
49 std::max(1u, std::thread::hardware_concurrency() - 1)
50 );
51
52 m_physicsSystem = new JPH::PhysicsSystem();
53
54 m_physicsSystem->Init(
55 static_cast<uint32_t>(maxBodies),
56 0,
57 1024,
58 1024,
59 m_bpInterface,
60 m_objVsBpFilter,
61 m_objLayerPairFilter
62 );
63 m_physicsSystem->SetGravity(JPH::Vec3(0.0f, -9.81f, 0.0f));
64
65 m_activationListener = std::make_unique<MyActivationListener>();
66 m_physicsSystem->SetBodyActivationListener(m_activationListener.get());
67
68 m_contactListener = std::make_unique<MyContactListener>(*this);
69 m_physicsSystem->SetContactListener(m_contactListener.get());
70
71 m_registry.on_destroy<PhysicsComponent>([this](vex::Entity entity, PhysicsComponent& pc) {
72 this->onPhysicsComponentDestroy(m_registry, entity);
73 });
74
75 return true;
76 }
77
81
83 //if (m_destroyConnection) m_destroyConnection.disconnect();
84
85 try {
86 if (m_physicsSystem) {
89 });
90 }
91 } catch (const std::exception& e) {
93 }
94
95 if (m_physicsSystem) {
96 delete m_physicsSystem;
97 m_physicsSystem = nullptr;
98 }
99 if (m_jobSystem) {
100 delete m_jobSystem;
101 m_jobSystem = nullptr;
102 }
103 if (m_tempAllocator) {
104 delete m_tempAllocator;
105 m_tempAllocator = nullptr;
106 }
107 JPH::UnregisterTypes();
108 if (JPH::Factory::sInstance != nullptr) {
109 delete JPH::Factory::sInstance;
110 JPH::Factory::sInstance = nullptr;
111 }
112 }
113
114 void PhysicsSystem::setDebugRenderer(JPH::DebugRenderer* renderer) {
115 m_debugRenderer = renderer;
116 }
117
118 void PhysicsSystem::drawDebug(bool drawConstraints, bool drawWireframe) {
119 #ifdef DEBUG
120 if (m_physicsSystem && m_debugRenderer) {
121 JPH::BodyManager::DrawSettings settings;
122 settings.mDrawShape = drawWireframe;
123 settings.mDrawShapeWireframe = drawWireframe;
124
125 DebugDrawFilter filter(*this);
126
127 m_physicsSystem->DrawBodies(settings, m_debugRenderer, &filter);
128
129 if (drawConstraints) {
130 m_physicsSystem->DrawConstraints(m_debugRenderer);
131 }
132 }
133 #endif
134 }
135
136 JPH::Quat glmToJph(const glm::quat& q) {
137 return JPH::Quat(q.x, q.y, q.z, q.w);
138 }
139
141 #if DEBUG
142 if (!m_physicsSystem) return;
143
144 auto& bodyInterface = m_physicsSystem->GetBodyInterface();
145
147 if (pc.bodyId.GetIndexAndSequenceNumber() == JPH::BodyID::cInvalidBodyID) [[unlikely]] {
148 CreateBodyForEntity(e, m_registry, pc);
149 return;
150 }
151 else if(pc.updated){
153 return;
154 }
155
156 fastSyncTransform(tc.getWorldPosition(), tc.getWorldQuaternion(), bodyInterface, pc.bodyId);
157 });
158 #else
159 log("This method is meant for debug builds only");
160 #endif
161 }
162
163 void PhysicsSystem::InitializeCharacter(vex::Entity e, CharacterComponent& cc) {
164 if (!m_registry.has<TransformComponent>(e)) return;
165 auto& tc = m_registry.get<TransformComponent>(e);
166
167 JPH::Ref<JPH::Shape> shape = new JPH::CapsuleShape(
168 0.5f * (cc.standingHeight - 2.0f * cc.standingRadius),
169 cc.standingRadius
170 );
171
172 JPH::CharacterVirtualSettings settings;
173 settings.mShape = shape;
174 settings.mMaxSlopeAngle = JPH::DegreesToRadians(cc.maxSlopeAngle);
175 settings.mMass = cc.mass;
176
177 JPH::RVec3 pos(tc.getWorldPosition().x, tc.getWorldPosition().y, tc.getWorldPosition().z);
178 JPH::Quat rot = glmToJph(tc.getWorldQuaternion());
179
180 cc.character = new JPH::CharacterVirtual(&settings, pos, rot, 0, m_physicsSystem);
181 }
182
183 void PhysicsSystem::update(float deltaTime) {
184 if (!m_physicsSystem) return;
185
186 auto& bodyInterface = m_physicsSystem->GetBodyInterface();
187
189 if (pc.bodyId.GetIndexAndSequenceNumber() == JPH::BodyID::cInvalidBodyID) {
190 CreateBodyForEntity(e, m_registry, pc);
191 return;
192 }
193
194 auto& cache = m_interpCache[pc.bodyId];
195
196 glm::vec3 vexPos = tc.getWorldPosition();
197 glm::quat vexRot = tc.getWorldQuaternion();
198
199 float posDiff = glm::distance(vexPos, cache.lastVisualPos);
200 glm::quat jRotGlm(cache.lastVisualRot.w, cache.lastVisualRot.x, cache.lastVisualRot.y, cache.lastVisualRot.z);
201 float rotDiff = 1.0f - std::abs(glm::dot(vexRot, jRotGlm));
202
203 if (posDiff > 0.0001f || rotDiff > 0.0001f || cache.desynced) {
204 bodyInterface.SetPositionAndRotation(
205 pc.bodyId,
206 JPH::RVec3(vexPos.x, vexPos.y, vexPos.z),
207 glmToJph(vexRot),
208 JPH::EActivation::Activate
209 );
210
211 cache.prevPos = cache.currPos = cache.lastVisualPos = vexPos;
212 cache.prevRot = cache.currRot = cache.lastVisualRot = vexRot;
213 cache.desynced = false;
214 }
215 });
216
217 m_accumulator += deltaTime;
218
219 while (m_accumulator >= m_fixedDt) {
221 auto& cache = m_interpCache[pc.bodyId];
222 cache.prevPos = cache.currPos;
223 cache.prevRot = cache.currRot;
224 });
225
227 if (!cc.isInitialized()) InitializeCharacter(e, cc);
228
229 glm::vec3 vexPos = tc.getWorldPosition();
230 JPH::RVec3 charPos = cc.character->GetPosition();
231 if (glm::distance(vexPos, glm::vec3(charPos.GetX(), charPos.GetY(), charPos.GetZ())) > 0.0001f) {
232 cc.character->SetPosition(JPH::RVec3(vexPos.x, vexPos.y, vexPos.z));
233 }
234
235 JPH::Vec3 currentVelocity = cc.character->GetLinearVelocity();
236
237 float newVerticalVel = currentVelocity.GetY() + m_physicsSystem->GetGravity().GetY() * m_fixedDt;
238 if (cc.character->IsSupported()) {
239 newVerticalVel = std::max(0.0f, newVerticalVel);
240 }
241
242 JPH::Vec3 finalVelocity(cc.controlInput.x, (newVerticalVel + cc.controlInput.y), cc.controlInput.z);
243 cc.character->SetLinearVelocity(finalVelocity);
244
245 JPH::CharacterVirtual::ExtendedUpdateSettings updateSettings;
246 JPH::ObjectLayer charLayer = 0;
247
248 cc.character->ExtendedUpdate(
249 m_fixedDt,
250 m_physicsSystem->GetGravity(),
251 updateSettings,
252 m_physicsSystem->GetDefaultBroadPhaseLayerFilter(charLayer),
253 m_physicsSystem->GetDefaultLayerFilter(charLayer),
254 {}, {}, *m_tempAllocator
255 );
256
257 JPH::RVec3 newPos = cc.character->GetPosition();
258 tc.setWorldPositionPhys(glm::vec3(newPos.GetX(), newPos.GetY(), newPos.GetZ()));
259 });
260
261 m_physicsSystem->Update(m_fixedDt, 1, m_tempAllocator, m_jobSystem);
262
264 auto& cache = m_interpCache[pc.bodyId];
265 JPH::RVec3 pos = bodyInterface.GetCenterOfMassPosition(pc.bodyId);
266 JPH::Quat rot = bodyInterface.GetRotation(pc.bodyId);
267 cache.currPos = glm::vec3(pos.GetX(), pos.GetY(), pos.GetZ());
268 cache.currRot = glm::quat(rot.GetW(), rot.GetX(), rot.GetY(), rot.GetZ());
269 });
270
271 m_accumulator -= m_fixedDt;
272 }
273
275 cc.controlInput = glm::vec3(0.0f);
276 });
277
278 float alpha = m_accumulator / m_fixedDt;
279
281 if (pc.bodyId.GetIndexAndSequenceNumber() != JPH::BodyID::cInvalidBodyID) {
282 SyncBodyToTransform(e, m_registry, pc.bodyId, alpha);
283 }
284 });
285 }
286
287 void PhysicsSystem::WeldVertices(const std::vector<glm::vec3>& inVerts, const std::vector<uint32_t>& inIndices,
288 JPH::VertexList& outVerts, JPH::IndexedTriangleList& outTris) {
289
290 struct Vec3Key {
291 glm::vec3 m_v;
292 bool operator<(const Vec3Key& other) const {
293 if (m_v.x != other.m_v.x) return m_v.x < other.m_v.x;
294 if (m_v.y != other.m_v.y) return m_v.y < other.m_v.y;
295 return m_v.z < other.m_v.z;
296 }
297 };
298
299 std::map<Vec3Key, uint32_t> uniqueMap;
300 outVerts.clear();
301 outTris.clear();
302 outVerts.reserve(inVerts.size());
303 outTris.reserve(inIndices.size() / 3);
304
305 std::vector<uint32_t> remappedIndices;
306 remappedIndices.resize(inIndices.size());
307
308 for (size_t i = 0; i < inIndices.size(); ++i) {
309 uint32_t originalIdx = inIndices[i];
310 if(originalIdx >= inVerts.size()) continue;
311
312 glm::vec3 pos = inVerts[originalIdx];
313 Vec3Key key{pos};
314
315 if (uniqueMap.find(key) == uniqueMap.end()) {
316 uint32_t newIdx = (uint32_t)outVerts.size();
317 uniqueMap[key] = newIdx;
318 outVerts.emplace_back(pos.x, pos.y, pos.z);
319 remappedIndices[i] = newIdx;
320 } else {
321 remappedIndices[i] = uniqueMap[key];
322 }
323 }
324
325 for (size_t i = 0; i < remappedIndices.size(); i += 3) {
326 outTris.emplace_back(remappedIndices[i], remappedIndices[i+1], remappedIndices[i+2]);
327 }
328 }
329
331 if (!m_physicsSystem || !r.has<TransformComponent>(e)) return std::nullopt;
332
333 auto& t = r.get<TransformComponent>(e);
334 JPH::RVec3 pos(t.getWorldPosition().x, t.getWorldPosition().y, t.getWorldPosition().z);
335 JPH::Quat rot = glmToJph(t.getWorldQuaternion());
336
337 JPH::ShapeRefC shape;
338 switch (pc.shape) {
339 case ShapeType::BOX:
340 shape = new JPH::BoxShape(JPH::Vec3(pc.boxHalfExtents.x, pc.boxHalfExtents.y, pc.boxHalfExtents.z));
341 break;
342 case ShapeType::ROUNDED_BOX:
343 {
344 JPH::Vec3 halfExtents(
345 std::max(pc.roundedRadius, pc.boxHalfExtents.x) - pc.roundedRadius,
346 std::max(pc.roundedRadius, pc.boxHalfExtents.y) - pc.roundedRadius,
347 std::max(pc.roundedRadius, pc.boxHalfExtents.z) - pc.roundedRadius
348 );
349
350 JPH::BoxShapeSettings settings(halfExtents, pc.roundedRadius);
351 shape = settings.Create().Get();
352 }
353 break;
354 case ShapeType::SPHERE:
355 shape = new JPH::SphereShape(pc.sphereRadius);
356 break;
357 case ShapeType::CAPSULE:
358 shape = new JPH::CapsuleShape(pc.capsuleHeight / 2.0f, pc.capsuleRadius);
359 break;
360 case ShapeType::CYLINDER:
361 shape = new JPH::CylinderShape(pc.cylinderHeight / 2.0f, pc.cylinderRadius);
362 break;
363 case ShapeType::CONVEX_HULL:
364 if (pc.convexPoints.empty()) {
365 log(LogLevel::ERROR, "Convex hull shape has no points");
366 return std::nullopt;
367 }
368 {
369 JPH::Array<JPH::Vec3> vertices;
370 vertices.reserve(pc.convexPoints.size()/3);
371 for (const auto& p : pc.convexPoints) {
372 vertices.emplace_back(JPH::Vec3(p.GetX(), p.GetY(), p.GetZ()));
373 }
374 JPH::ConvexHullShapeSettings settings(vertices);
375 shape = settings.Create().Get();
376 }
377 break;
378 case ShapeType::MESH:
379 if (pc.meshVertices.empty() || pc.meshIndices.empty()) {
380 log(LogLevel::ERROR, "Mesh shape has no vertices or indices");
381
382 if (r.has<MeshComponent>(e)) {
383 auto& mc = r.get<MeshComponent>(e);
384
385 if (!mc.meshData.submeshes.empty()) {
386 pc.meshVertices.clear();
387 pc.meshIndices.clear();
388
389 size_t vertexOffset = 0;
390
391 for (const auto& sm : mc.meshData.submeshes) {
392 for (const auto& v : sm.vertices) {
393 pc.meshVertices.push_back(v.position);
394 }
395 for (auto idx : sm.indices) {
396 pc.meshIndices.push_back(static_cast<uint32_t>(idx + vertexOffset));
397 }
398 vertexOffset += sm.vertices.size();
399 }
400
401 if (pc.meshVertices.empty() || pc.meshIndices.empty()) {
402 log(LogLevel::ERROR, "Mesh shape has no vertices or indices. I have no fucking idea why because it just copied those and would error out if there were none :C");
403 return std::nullopt;
404 }
405 }else{
406 log(LogLevel::ERROR, "MeshComponent has no verticies");
407 return std::nullopt;
408 }
409 }else{
410 log(LogLevel::ERROR, "MeshComponent also not found");
411 return std::nullopt;
412 }
413 }
414
415 if (pc.bodyType == BodyType::DYNAMIC) {
416 JPH::Array<JPH::Vec3> verts;
417 verts.reserve(pc.meshVertices.size());
418 for (const auto& v : pc.meshVertices) {
419 verts.emplace_back(JPH::Vec3(v.x, v.y, v.z));
420 }
421 log(LogLevel::WARNING, "Dynamic mesh fallback to convex hull");
422 JPH::ConvexHullShapeSettings settings(verts);
423
424 auto result = settings.Create();
425 if (result.IsValid()) {
426 shape = result.Get();
427 } else {
428 log(LogLevel::ERROR, "Failed to create Convex Hull: %s", result.GetError().c_str());
429 return std::nullopt;
430 }
431 } else {
432 /*JPH::Array<JPH::Float3> verts;
433 verts.reserve(pc.meshVertices.size());
434 for (const auto& v : pc.meshVertices) {
435 verts.emplace_back(v.x, v.y, v.z);
436 }
437 JPH::IndexedTriangleList tris;
438 tris.reserve(pc.meshIndices.size() / 3);
439 for (size_t i = 0; i < pc.meshIndices.size(); i += 3) {
440 tris.emplace_back(pc.meshIndices[i], pc.meshIndices[i + 1], pc.meshIndices[i + 2]);
441 }*/
442
443 JPH::VertexList verts;
444 JPH::IndexedTriangleList tris;
445 WeldVertices(pc.meshVertices, pc.meshIndices, verts, tris);
446
447 JPH::MeshShapeSettings settings(verts, tris);
448 settings.mActiveEdgeCosThresholdAngle = cos(JPH::DegreesToRadians(25.0f));
449 shape = settings.Create().Get();
450 }
451 break;
452 }
453
454 JPH::EMotionType motion = JPH::EMotionType::Static;
455 if (pc.bodyType == BodyType::DYNAMIC) motion = JPH::EMotionType::Dynamic;
456 else if (pc.bodyType == BodyType::KINEMATIC) motion = JPH::EMotionType::Kinematic;
457 else if (pc.bodyType == BodyType::SENSOR) motion = JPH::EMotionType::Static;
458
459 JPH::BodyCreationSettings settings(shape, pos, rot, motion, pc.objectLayer);
460 settings.mLinearDamping = pc.linearDamping;
461 settings.mAngularDamping = pc.angularDamping;
462 settings.mAllowSleeping = pc.allowSleeping;
463 settings.mIsSensor = pc.isSensor || pc.bodyType == BodyType::SENSOR;
464 if (pc.bodyType == BodyType::DYNAMIC || pc.bodyType == BodyType::KINEMATIC) {
465 settings.mOverrideMassProperties = JPH::EOverrideMassProperties::CalculateInertia;
466 settings.mMassPropertiesOverride.mMass = pc.mass;
467 }
468
469 auto& bodyInterface = m_physicsSystem->GetBodyInterface();
470 JPH::BodyID bodyId = bodyInterface.CreateAndAddBody(settings, JPH::EActivation::Activate);
471 if (bodyId.GetIndexAndSequenceNumber() == JPH::BodyID::cInvalidBodyID) return std::nullopt;
472
473 bodyInterface.SetFriction(bodyId, pc.friction);
474 bodyInterface.SetRestitution(bodyId, pc.bounce);
475
476 pc.bodyId = bodyId;
477 m_bodyToEntity[bodyId] = e;
478
479 JPH::RVec3 jPos = bodyInterface.GetCenterOfMassPosition(bodyId);
480 JPH::Quat jRot = bodyInterface.GetRotation(bodyId);
481
482 InterpCache cache;
483 cache.prevPos = cache.currPos = cache.lastVisualPos = glm::vec3(jPos.GetX(), jPos.GetY(), jPos.GetZ());
484 cache.prevRot = cache.currRot = cache.lastVisualRot = glm::quat(jRot.GetW(), jRot.GetX(), jRot.GetY(), jRot.GetZ());
485 m_interpCache[bodyId] = cache;
486
487 return bodyId;
488 }
489
492 return CreateBodyForEntity(e, m_registry, pc);
493 }
494
496 if (!m_physicsSystem || pc.bodyId.GetIndexAndSequenceNumber() == JPH::BodyID::cInvalidBodyID) return;
497 auto& bi = m_physicsSystem->GetBodyInterface();
498 bi.RemoveBody(pc.bodyId);
499 bi.DestroyBody(pc.bodyId);
500 m_bodyToEntity.erase(pc.bodyId);
501 m_interpCache.erase(pc.bodyId);
502 pc.bodyId = JPH::BodyID(JPH::BodyID::cInvalidBodyID);
503 }
504
505 void PhysicsSystem::onPhysicsComponentDestroy(vex::Registry& reg, vex::Entity e) {
506 if (reg.has<PhysicsComponent>(e)) {
507 auto& pc = reg.get<PhysicsComponent>(e);
509 }
510 }
511
512 JPH::Quat PhysicsSystem::EulerToQuat(const glm::vec3& eulerDeg) {
513 glm::vec3 eulerRad = glm::radians(eulerDeg);
514 glm::quat q = glm::yawPitchRoll(eulerRad.y, eulerRad.x, eulerRad.z);
515 return JPH::Quat(q.x, q.y, q.z, q.w);
516 }
517
518 glm::vec3 PhysicsSystem::QuatToEuler(const JPH::Quat& q) {
519 glm::quat gq(q.GetX(), q.GetY(), q.GetZ(), q.GetW());
520 return glm::degrees(glm::eulerAngles(gq));
521 }
522
523 void PhysicsSystem::SyncBodyToTransform(vex::Entity e, vex::Registry& r, const JPH::BodyID& id, float alpha) {
524 if(!enableSmoothing) alpha = 1.f;
525
526 auto& t = r.get<TransformComponent>(e);
527 auto& cache = m_interpCache[id];
528
529 glm::vec3 vexPos = t.getWorldPosition();
530 glm::quat vexRot = t.getWorldQuaternion();
531
532 float posDiff = glm::distance(vexPos, cache.lastVisualPos);
533 glm::quat jRotGlm(cache.lastVisualRot.w, cache.lastVisualRot.x, cache.lastVisualRot.y, cache.lastVisualRot.z);
534 float rotDiff = 1.0f - std::abs(glm::dot(vexRot, jRotGlm));
535
536 const float epsilon = 0.0001f;
537
538 if (posDiff > epsilon || rotDiff > epsilon) {
539 glm::vec3 deltaPos = vexPos - cache.lastVisualPos;
540 glm::quat deltaRot = vexRot * glm::inverse(cache.lastVisualRot);
541
542 cache.prevPos += deltaPos;
543 cache.currPos += deltaPos;
544 cache.prevRot = glm::normalize(deltaRot * cache.prevRot);
545 cache.currRot = glm::normalize(deltaRot * cache.currRot);
546 cache.desynced = true;
547 }
548
549 glm::vec3 interpPos = glm::mix(cache.prevPos, cache.currPos, alpha);
550
551 glm::quat targetRot = cache.currRot;
552 if (glm::dot(cache.prevRot, targetRot) < 0.0f) {
553 targetRot = -targetRot;
554 }
555 glm::quat interpRot = glm::slerp(cache.prevRot, targetRot, alpha);
556
557 t.setWorldPositionPhys(interpPos);
558 t.setWorldQuaternionPhys(interpRot);
559
560 cache.lastVisualPos = interpPos;
561 cache.lastVisualRot = interpRot;
562 }
563
564 PhysicsComponent& PhysicsSystem::getPhysicsComponentByBodyId(JPH::BodyID id) {
565 auto it = m_bodyToEntity.find(id);
566 if (it != m_bodyToEntity.end()) {
567 return m_registry.get<PhysicsComponent>(it->second);
568 }
569 static PhysicsComponent dummy;
570 return dummy;
571 }
572
573 vex::Entity PhysicsSystem::getEntityByBodyId(JPH::BodyID id) {
574 auto it = m_bodyToEntity.find(id);
575 return it != m_bodyToEntity.end() ? it->second : vex::NULL_ENTITY;
576 }
577
578 class IgnoreSensorFilter : public JPH::BodyFilter {
579 public:
580 IgnoreSensorFilter(const JPH::BodyLockInterface& lockInterface) : m_lockInterface(lockInterface) {}
581
582 bool ShouldCollide(const JPH::BodyID& inBodyID) const override {
583 JPH::BodyLockRead lock(m_lockInterface, inBodyID);
584 if (lock.Succeeded()) {
585 return !lock.GetBody().IsSensor();
586 }
587 return false;
588 }
589
590 bool ShouldCollideLocked(const JPH::Body& inBody) const override {
591 return !inBody.IsSensor();
592 }
593
594 private:
595 const JPH::BodyLockInterface& m_lockInterface;
596 };
597
598 bool PhysicsSystem::raycast(const glm::vec3& origin, const glm::vec3& direction, float maxDistance, RaycastHit& hit) {
599 if (!m_physicsSystem) return false;
600
601 JPH::RVec3 start(origin.x, origin.y, origin.z);
602 JPH::Vec3 dir(direction.x, direction.y, direction.z);
603 JPH::RRayCast ray(start, dir * maxDistance);
604 JPH::RayCastResult result;
605
606 IgnoreSensorFilter filter(m_physicsSystem->GetBodyLockInterface());
607
608 if (m_physicsSystem->GetNarrowPhaseQuery().CastRay(ray, result, { }, { }, filter)) {
609 hit.bodyId = result.mBodyID;
610 hit.distance = result.mFraction * maxDistance;
611 hit.position = origin + direction * hit.distance;
612 hit.normal = glm::vec3(0.0f); // Normal requires collector
613 return true;
614 }
615 return false;
616 }
617
618 void PhysicsSystem::SetFriction(JPH::BodyID bodyId, float friction) {
619 auto& bi = m_physicsSystem->GetBodyInterface();
620 bi.SetFriction(bodyId, friction);
621 getPhysicsComponentByBodyId(bodyId).friction = friction;
622 }
623
624 float PhysicsSystem::GetFriction(JPH::BodyID bodyId) {
625 return m_physicsSystem->GetBodyInterface().GetFriction(bodyId);
626 }
627
628 void PhysicsSystem::SetBounciness(JPH::BodyID bodyId, float bounciness) {
629 auto& bi = m_physicsSystem->GetBodyInterface();
630 bi.SetRestitution(bodyId, bounciness);
631 getPhysicsComponentByBodyId(bodyId).bounce = bounciness;
632 }
633
634 float PhysicsSystem::GetBounciness(JPH::BodyID bodyId) {
635 return m_physicsSystem->GetBodyInterface().GetRestitution(bodyId);
636 }
637
638 void PhysicsSystem::SetLinearVelocity(JPH::BodyID bodyId, const glm::vec3& velocity) {
639 m_physicsSystem->GetBodyInterface().SetLinearVelocity(bodyId, JPH::Vec3(velocity.x, velocity.y, velocity.z));
640 }
641
642 glm::vec3 PhysicsSystem::GetLinearVelocity(JPH::BodyID bodyId) {
643 auto vel = m_physicsSystem->GetBodyInterface().GetLinearVelocity(bodyId);
644 return {vel.GetX(), vel.GetY(), vel.GetZ()};
645 }
646
647 glm::vec3 PhysicsSystem::GetVelocityAtPosition(JPH::BodyID bodyId, const glm::vec3& point) {
648 JPH::RVec3 joltPoint(point.x, point.y, point.z);
649 JPH::Vec3 joltVel = m_physicsSystem->GetBodyInterface().GetPointVelocity(bodyId, joltPoint);
650 return glm::vec3(joltVel.GetX(), joltVel.GetY(), joltVel.GetZ());
651 }
652
653 void PhysicsSystem::AddLinearVelocity(JPH::BodyID bodyId, const glm::vec3& velocity) {
654 auto& bi = m_physicsSystem->GetBodyInterface();
655 JPH::Vec3 current = bi.GetLinearVelocity(bodyId);
656 bi.SetLinearVelocity(bodyId, current + JPH::Vec3(velocity.x, velocity.y, velocity.z));
657 }
658
659 void PhysicsSystem::SetAngularVelocity(JPH::BodyID bodyId, const glm::vec3& velocity) {
660 m_physicsSystem->GetBodyInterface().SetAngularVelocity(bodyId, JPH::Vec3(velocity.x, velocity.y, velocity.z));
661 }
662
663 glm::vec3 PhysicsSystem::GetAngularVelocity(JPH::BodyID bodyId) {
664 auto vel = m_physicsSystem->GetBodyInterface().GetAngularVelocity(bodyId);
665 return {vel.GetX(), vel.GetY(), vel.GetZ()};
666 }
667
668 void PhysicsSystem::AddAngularVelocity(JPH::BodyID bodyId, const glm::vec3& velocity) {
669 auto& bi = m_physicsSystem->GetBodyInterface();
670 JPH::Vec3 current = bi.GetAngularVelocity(bodyId);
671 bi.SetAngularVelocity(bodyId, current + JPH::Vec3(velocity.x, velocity.y, velocity.z));
672 }
673
674 void PhysicsSystem::AddTorque(JPH::BodyID bodyId, const glm::vec3& torque) {
675 m_physicsSystem->GetBodyInterface().AddTorque(bodyId, JPH::Vec3(torque.x, torque.y, torque.z));
676 }
677
678 void PhysicsSystem::AddForce(JPH::BodyID bodyId, const glm::vec3& force) {
679 m_physicsSystem->GetBodyInterface().AddForce(bodyId, JPH::Vec3(force.x, force.y, force.z));
680 }
681
682 void PhysicsSystem::AddForceAtPosition(JPH::BodyID bodyId, const glm::vec3& force, const glm::vec3& position) {
683 m_physicsSystem->GetBodyInterface().AddForce(bodyId, JPH::Vec3(force.x, force.y, force.z), JPH::RVec3(position.x, position.y, position.z));
684 }
685
686 void PhysicsSystem::AddImpulse(JPH::BodyID bodyId, const glm::vec3& impulse) {
687 m_physicsSystem->GetBodyInterface().AddImpulse(bodyId, JPH::Vec3(impulse.x, impulse.y, impulse.z));
688 }
689
690 void PhysicsSystem::AddImpulseAtPosition(JPH::BodyID bodyId, const glm::vec3& impulse, const glm::vec3& position) {
691 m_physicsSystem->GetBodyInterface().AddImpulse(bodyId, JPH::Vec3(impulse.x, impulse.y, impulse.z), JPH::RVec3(position.x, position.y, position.z));
692 }
693
694 void PhysicsSystem::AddAngularImpulse(JPH::BodyID bodyId, const glm::vec3& impulse) {
695 m_physicsSystem->GetBodyInterface().AddAngularImpulse(bodyId, JPH::Vec3(impulse.x, impulse.y, impulse.z));
696 }
697
698 void PhysicsSystem::SetBodyActive(JPH::BodyID bodyId, bool active) {
699 auto& bi = m_physicsSystem->GetBodyInterface();
700 active ? bi.ActivateBody(bodyId) : bi.DeactivateBody(bodyId);
701 }
702
703 bool PhysicsSystem::GetBodyActive(JPH::BodyID bodyId) {
704 return m_physicsSystem->GetBodyInterface().IsActive(bodyId);
705 }
706
707 glm::vec3 PhysicsSystem::GetPhysicsPosition(JPH::BodyID bodyId) {
708 if (!m_physicsSystem || bodyId.IsInvalid()) return glm::vec3(0.0f);
709 JPH::RVec3 pos = m_physicsSystem->GetBodyInterfaceNoLock().GetCenterOfMassPosition(bodyId);
710 return glm::vec3(pos.GetX(), pos.GetY(), pos.GetZ());
711 }
712
713 glm::quat PhysicsSystem::GetPhysicsRotation(JPH::BodyID bodyId) {
714 if (!m_physicsSystem || bodyId.IsInvalid()) return glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
715 JPH::Quat rot = m_physicsSystem->GetBodyInterfaceNoLock().GetRotation(bodyId);
716 return glm::quat(rot.GetW(), rot.GetX(), rot.GetY(), rot.GetZ());
717 }
718
719 void MyContactListener::OnContactAdded(const JPH::Body& inBody1, const JPH::Body& inBody2, const JPH::ContactManifold& inManifold, JPH::ContactSettings& ioSettings) {
720 vex::Entity e1 = m_system.getEntityByBodyId(inBody1.GetID());
721 vex::Entity e2 = m_system.getEntityByBodyId(inBody2.GetID());
722 CollisionHit hit;
723 hit.position = glm::vec3(inManifold.mWorldSpaceNormal.GetX(), inManifold.mWorldSpaceNormal.GetY(), inManifold.mWorldSpaceNormal.GetZ());
724 hit.normal = hit.position;
725 hit.impulse = ioSettings.mCombinedRestitution;
726
727 if (e1 != vex::NULL_ENTITY && m_system.m_registry.has<PhysicsComponent>(e1)) {
728 auto& pc = m_system.m_registry.get<PhysicsComponent>(e1);
729 if (pc.onCollisionEnter) pc.onCollisionEnter(e1, e2, hit);
730 }
731 if (e2 != vex::NULL_ENTITY && m_system.m_registry.has<PhysicsComponent>(e2)) {
732 auto& pc = m_system.m_registry.get<PhysicsComponent>(e2);
733 if (pc.onCollisionEnter) pc.onCollisionEnter(e2, e1, hit);
734 }
735 }
736
737 void MyContactListener::OnContactPersisted(const JPH::Body& inBody1, const JPH::Body& inBody2, const JPH::ContactManifold& inManifold, JPH::ContactSettings& ioSettings) {
738 vex::Entity e1 = m_system.getEntityByBodyId(inBody1.GetID());
739 vex::Entity e2 = m_system.getEntityByBodyId(inBody2.GetID());
740 CollisionHit hit;
741 hit.position = glm::vec3(inManifold.mWorldSpaceNormal.GetX(), inManifold.mWorldSpaceNormal.GetY(), inManifold.mWorldSpaceNormal.GetZ());
742 hit.normal = hit.position;
743 hit.impulse = ioSettings.mCombinedRestitution;
744
745 if (e1 != vex::NULL_ENTITY && m_system.m_registry.has<PhysicsComponent>(e1)) {
746 auto& pc = m_system.m_registry.get<PhysicsComponent>(e1);
747 if (pc.onCollisionStay) pc.onCollisionStay(e1, e2, hit);
748 }
749 if (e2 != vex::NULL_ENTITY && m_system.m_registry.has<PhysicsComponent>(e2)) {
750 auto& pc = m_system.m_registry.get<PhysicsComponent>(e2);
751 if (pc.onCollisionStay) pc.onCollisionStay(e2, e1, hit);
752 }
753 }
754
755 void MyContactListener::OnContactRemoved(const JPH::SubShapeIDPair& inSubShapePair) {
756 JPH::BodyID id1 = inSubShapePair.GetBody1ID();
757 JPH::BodyID id2 = inSubShapePair.GetBody2ID();
758 vex::Entity e1 = m_system.getEntityByBodyId(id1);
759 vex::Entity e2 = m_system.getEntityByBodyId(id2);
760
761 if (e1 != vex::NULL_ENTITY && m_system.m_registry.has<PhysicsComponent>(e1)) {
762 auto& pc = m_system.m_registry.get<PhysicsComponent>(e1);
763 if (pc.onCollisionExit) pc.onCollisionExit(e1, e2);
764 }
765 if (e2 != vex::NULL_ENTITY && m_system.m_registry.has<PhysicsComponent>(e2)) {
766 auto& pc = m_system.m_registry.get<PhysicsComponent>(e2);
767 if (pc.onCollisionExit) pc.onCollisionExit(e2, e1);
768 }
769 }
770}
This file exist only because of coliding macros.
This file defines PhysicsSystem class.
Implementation of JPH::BodyDrawFilter for debugging physics bodies.
void SetLinearVelocity(JPH::BodyID bodyId, const glm::vec3 &velocity)
Allows for updating linear velocity.
void SyncBodies()
Scans registry for PhysicsComponents without bodies and creates them.
void SetBodyActive(JPH::BodyID bodyId, bool active)
Allows for setting body active state.
void AddForceAtPosition(JPH::BodyID bodyId, const glm::vec3 &force, const glm::vec3 &position)
Applies force at the given position of the body, it is reset next physics tick.
void SetFriction(JPH::BodyID bodyId, float friction)
Allows for updating friction.
float GetFriction(JPH::BodyID bodyId)
Allows for getting friction.
void AddLinearVelocity(JPH::BodyID bodyId, const glm::vec3 &velocity)
Allows for adding to linear velocity.
void setDebugRenderer(JPH::DebugRenderer *renderer)
Sets the debug renderer for the physics system.
bool raycast(const glm::vec3 &origin, const glm::vec3 &direction, float maxDistance, RaycastHit &hit)
Performs a raycast in the physics world.
void WeldVertices(const std::vector< glm::vec3 > &inVerts, const std::vector< uint32_t > &inIndices, JPH::VertexList &outVerts, JPH::IndexedTriangleList &outTris)
Helper to weld vertices based on position ONLY (ignoring UVs/Normals which split render meshes).
void update(float deltaTime)
Updates physics, it is technically fixed time but needs delta to track if required time already passe...
void AddForce(JPH::BodyID bodyId, const glm::vec3 &force)
Applies force at the mass center of the body, it is reset next physics tick.
void AddAngularImpulse(JPH::BodyID bodyId, const glm::vec3 &impulse)
Applies angular impulse at the mass center of the body, it is reset next physics tick.
void AddTorque(JPH::BodyID bodyId, const glm::vec3 &torque)
Applies torque to the body, it is reset next physics tick.
void AddAngularVelocity(JPH::BodyID bodyId, const glm::vec3 &velocity)
Allows for adding to angular velocity.
void AddImpulse(JPH::BodyID bodyId, const glm::vec3 &impulse)
Applies impulse at the mass center of the body, it is reset next physics tick.
void shutdown()
clears all physics objects
void SetAngularVelocity(JPH::BodyID bodyId, const glm::vec3 &velocity)
Allows for setting angular velocity.
glm::vec3 GetPhysicsPosition(JPH::BodyID bodyId)
Retrieves the physics position of a body.
glm::vec3 GetLinearVelocity(JPH::BodyID bodyId)
Allows for getting linear velocity.
std::optional< JPH::BodyID > CreateBodyForEntity(vex::Entity e, vex::Registry &r, PhysicsComponent &pc)
Allows to recreate physics body at runtime.
std::optional< JPH::BodyID > RecreateBodyForEntity(vex::Entity e, PhysicsComponent &pc)
Allows to recreate physics body at runtime.
bool init(size_t maxBodies=1024)
Initializes jolts physics system.
void AddImpulseAtPosition(JPH::BodyID bodyId, const glm::vec3 &impulse, const glm::vec3 &position)
Applies impulse at the given position of the body, it is reset next physics tick.
void drawDebug(bool drawConstraints=true, bool drawWireframe=true)
Draws debug information for the physics system.
void DestroyBodyForEntity(PhysicsComponent &pc)
Destroys a physics body.
void SetBounciness(JPH::BodyID bodyId, float bounciness)
Allows for updating bounciness.
glm::vec3 GetAngularVelocity(JPH::BodyID bodyId)
Allows for getting angular velocity.
bool GetBodyActive(JPH::BodyID bodyId)
Allows for getting body active state.
glm::vec3 GetVelocityAtPosition(JPH::BodyID bodyId, const glm::vec3 &point)
Gets the velocity of a specific point on the body (in World Space).
~PhysicsSystem()
Destructor, simply calls shutdown().
glm::quat GetPhysicsRotation(JPH::BodyID bodyId)
Retrieves the physics rotation of a body.
float GetBounciness(JPH::BodyID bodyId)
Allows for getting bounciness.
Central registry for managing entities and their components in the ECS system.
Definition Registry.hpp:29
bool has(Entity entity)
Checks if an entity has a component of type T.
Definition Registry.hpp:137
T & get(Entity entity)
Retrieves a component from an entity.
Definition Registry.hpp:118
Provides iteration over entities that have all specified component types.
Definition View.hpp:17
void each(Func func)
Iterates over all entities with the specified component types.
Definition View.hpp:40
uint32_t Entity
Type alias representing a unique entity identifier in the ECS system.
Definition Types.hpp:11
void VEX_EXPORT log(const char *fmt,...)
Logs a formatted message.
void VEX_EXPORT handle_exception(const std::exception &e)
Handles an exception based on build configuration.
constexpr Entity NULL_ENTITY
Special entity value indicating an invalid or null entity.
Definition Types.hpp:15
Character component for player or NPC characters.
Structure representing a collision hit.
Struct containing raw meshData, mesh id, texture paths and material properties. It just template and ...
Structure representing a physics component.
Structure representing a raycast hit.
Struct containing transform data and methods.
void setWorldPositionPhys(glm::vec3 newPosition)
Method to set world position by physics component.
glm::quat getWorldQuaternion() const
Method to get the world rotation as a quaternion.
glm::vec3 getWorldPosition()
Method to get world position, needed when object is parented as position parameter stores local posit...