From c5a680caf4e9b9088d4b41dd263b6049df4dece6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lu=C3=ADs=20Fonseca?= Date: Wed, 4 Oct 2023 21:58:20 +0100 Subject: [PATCH] refactor: separate collider from shapes --- .../include/cubos/engine/collisions/aabb.hpp | 4 +- .../cubos/engine/collisions/collider.hpp | 23 +++++++++ .../cubos/engine/collisions/colliders/box.hpp | 31 ------------ .../engine/collisions/colliders/capsule.hpp | 25 ---------- .../cubos/engine/collisions/shapes/box.hpp | 24 ++++++++++ .../engine/collisions/shapes/capsule.hpp | 17 +++++++ engine/samples/collisions/main.cpp | 18 +++---- .../cubos/engine/collisions/broad_phase.cpp | 48 ++++++++++++------- .../cubos/engine/collisions/broad_phase.hpp | 32 ++++++------- engine/src/cubos/engine/collisions/plugin.cpp | 13 +++-- 10 files changed, 128 insertions(+), 107 deletions(-) create mode 100644 engine/include/cubos/engine/collisions/collider.hpp delete mode 100644 engine/include/cubos/engine/collisions/colliders/box.hpp delete mode 100644 engine/include/cubos/engine/collisions/colliders/capsule.hpp create mode 100644 engine/include/cubos/engine/collisions/shapes/box.hpp create mode 100644 engine/include/cubos/engine/collisions/shapes/capsule.hpp diff --git a/engine/include/cubos/engine/collisions/aabb.hpp b/engine/include/cubos/engine/collisions/aabb.hpp index 4ab19b2572..c6de91c36b 100644 --- a/engine/include/cubos/engine/collisions/aabb.hpp +++ b/engine/include/cubos/engine/collisions/aabb.hpp @@ -13,9 +13,9 @@ namespace cubos::engine { - /// @brief Component which stores the AABB of an entity with a collider component. + /// @brief AABB of a collider. /// @ingroup collisions-plugin - struct [[cubos::component("cubos/aabb", VecStorage)]] ColliderAABB + struct ColliderAABB { /// @brief Minimum point of the diagonal of the AABB. glm::vec3 min = glm::vec3{-INFINITY}; diff --git a/engine/include/cubos/engine/collisions/collider.hpp b/engine/include/cubos/engine/collisions/collider.hpp new file mode 100644 index 0000000000..7c83eb2a6b --- /dev/null +++ b/engine/include/cubos/engine/collisions/collider.hpp @@ -0,0 +1,23 @@ +/// @file +/// @brief Component @ref cubos::engine::Collider. +/// @ingroup collisions-plugin + +#pragma once + +#include + +#include + +namespace cubos::engine +{ + /// @brief Component which adds a collider to an entity. + /// @ingroup collisions-plugin + struct [[cubos::component("cubos/collider", VecStorage)]] Collider + { + glm::mat4 transform{1.0F}; ///< Transform of the collider. + + bool fresh = true; ///< Whether the collider is fresh. This is an hack and should be done in ECS. + ColliderAABB localAABB; ///< Local space AABB of the collider. + ColliderAABB worldAABB; ///< World space AABB of the collider. + }; +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/collisions/colliders/box.hpp b/engine/include/cubos/engine/collisions/colliders/box.hpp deleted file mode 100644 index 24fce6a979..0000000000 --- a/engine/include/cubos/engine/collisions/colliders/box.hpp +++ /dev/null @@ -1,31 +0,0 @@ -/// @file -/// @brief Component @ref cubos::engine::BoxCollider. -/// @ingroup collisions-plugin - -#pragma once - -#include - -namespace cubos::engine -{ - /// @brief Component which adds a box collider to an entity. - /// - /// - Convex: For any 2 points inside the shape, the line segment connecting those points lies - /// inside the shape. - /// - Positive margin: Requires a positive margin to round off sharp corners. Defined in - /// physics-space units. - /// - /// @ingroup collisions-plugin - struct [[cubos::component("cubos/box_collider", VecStorage)]] BoxCollider - { - glm::mat4 transform{1.0F}; ///< Transform of the collider. - cubos::core::geom::Box shape; ///< Box shape of the collider. - - /// @brief Margin of the collider. Needed for collision stability. - /// - /// The collider margin avoids collision errors by rounding off sharp corners. It is - /// absolute so the collider's transform won't affect it. Shouldn't be changed without good - /// reason, as it's preferable to scale down the collider to account for it. - float margin = 0.04F; - }; -} // namespace cubos::engine diff --git a/engine/include/cubos/engine/collisions/colliders/capsule.hpp b/engine/include/cubos/engine/collisions/colliders/capsule.hpp deleted file mode 100644 index a8a696ab84..0000000000 --- a/engine/include/cubos/engine/collisions/colliders/capsule.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/// @file -/// @brief Component @ref cubos::engine::CapsuleCollider. -/// @ingroup collisions-plugin - -#pragma once - -#include - -#include - -namespace cubos::engine -{ - /// @brief Component which adds a capsule collider to an entity. - /// - /// - Convex: For any 2 points inside the shape, the line segment connecting those points lies - /// inside the shape. - /// - Zero margin: No margin is needed, since the shape has no sharp corners. - /// - /// @ingroup collisions-plugin - struct [[cubos::component("cubos/capsule_collider", VecStorage)]] CapsuleCollider - { - glm::mat4 transform{1.0F}; ///< Transform of the collider. - cubos::core::geom::Capsule shape; ///< Capsule shape of the collider. - }; -} // namespace cubos::engine diff --git a/engine/include/cubos/engine/collisions/shapes/box.hpp b/engine/include/cubos/engine/collisions/shapes/box.hpp new file mode 100644 index 0000000000..d41c1cca33 --- /dev/null +++ b/engine/include/cubos/engine/collisions/shapes/box.hpp @@ -0,0 +1,24 @@ +/// @file +/// @brief Component @ref cubos::engine::BoxCollisionShape. +/// @ingroup collisions-plugin + +#pragma once + +#include + +namespace cubos::engine +{ + /// @brief Component which adds a box collision shape to an entity, used with a collider component. + /// @ingroup collisions-plugin + struct [[cubos::component("cubos/box_collision_shape", VecStorage)]] BoxCollisionShape + { + cubos::core::geom::Box shape; ///< Box shape. + + /// @brief Margin of the collider. Needed for collision stability. + /// + /// The collider margin avoids collision errors by rounding off sharp corners. It is + /// absolute so the collider's transform won't affect it. Shouldn't be changed without good + /// reason, as it's preferable to scale down the collider shape to account for it. + float margin = 0.04F; + }; +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/collisions/shapes/capsule.hpp b/engine/include/cubos/engine/collisions/shapes/capsule.hpp new file mode 100644 index 0000000000..b0285801fb --- /dev/null +++ b/engine/include/cubos/engine/collisions/shapes/capsule.hpp @@ -0,0 +1,17 @@ +/// @file +/// @brief Component @ref cubos::engine::CapsuleCollisionShape. +/// @ingroup collisions-plugin + +#pragma once + +#include + +namespace cubos::engine +{ + /// @brief Component which adds a capsule collision shape to an entity, used with a collider component. + /// @ingroup collisions-plugin + struct [[cubos::component("cubos/capsule_collision_shape", VecStorage)]] CapsuleCollisionShape + { + cubos::core::geom::Capsule shape; ///< Capsule shape. + }; +} // namespace cubos::engine diff --git a/engine/samples/collisions/main.cpp b/engine/samples/collisions/main.cpp index 222b498bea..fd71a01883 100644 --- a/engine/samples/collisions/main.cpp +++ b/engine/samples/collisions/main.cpp @@ -6,8 +6,9 @@ #include #include -#include +#include #include +#include #include #include #include @@ -60,7 +61,7 @@ static void init(Commands commands, Write input, Write cam static void addColliders(Write state, Commands commands) { state->a = commands.create() - .add(BoxCollider{}) + .add(BoxCollisionShape{}) .add(LocalToWorld{}) .add(Position{glm::vec3{0.0F, 0.0F, -2.0F}}) .add(Rotation{}) @@ -68,7 +69,7 @@ static void addColliders(Write state, Commands commands) state->aRotationAxis = glm::sphericalRand(1.0F); state->b = commands.create() - .add(BoxCollider{}) + .add(BoxCollisionShape{}) .add(LocalToWorld{}) .add(Position{glm::vec3{0.0F, 0.0F, 2.0F}}) .add(Rotation{}) @@ -105,7 +106,7 @@ static void updateTransform(Write state, Read input, Queryvec += glm::vec3{0.0F, 0.0F, -0.01F}; } -static void updateCollided(Query> query, Write state, Read collisions) +static void updateCollided(Query> query, Write state, Read collisions) { for (auto [entity, collider] : query) { @@ -120,12 +121,13 @@ static void updateCollided(Query> query, Write state, R } } -static void render(Query, Read, Read> query) +static void render(Query, Read, Read> query) { - for (auto [entity, localToWorld, collider, aabb] : query) + for (auto [entity, localToWorld, collider, shape] : query) { - cubos::core::gl::Debug::drawWireBox(collider->shape, localToWorld->mat * collider->transform); - cubos::core::gl::Debug::drawWireBox(aabb->box(), glm::translate(glm::mat4{1.0}, aabb->center()), + cubos::core::gl::Debug::drawWireBox(shape->shape, localToWorld->mat * collider->transform); + cubos::core::gl::Debug::drawWireBox(collider->worldAABB.box(), + glm::translate(glm::mat4{1.0}, collider->worldAABB.center()), glm::vec3{1.0, 0.0, 0.0}); } } diff --git a/engine/src/cubos/engine/collisions/broad_phase.cpp b/engine/src/cubos/engine/collisions/broad_phase.cpp index f83586ddb3..9e81e893ac 100644 --- a/engine/src/cubos/engine/collisions/broad_phase.cpp +++ b/engine/src/cubos/engine/collisions/broad_phase.cpp @@ -1,14 +1,24 @@ #include "broad_phase.hpp" +using cubos::engine::ColliderAABB; + using CollisionType = BroadPhaseCollisions::CollisionType; -void updateBoxAABBs(Query, Read, Write> query) +void updateBoxAABBs(Query, Read, Write> query) { - for (auto [entity, localToWorld, collider, aabb] : query) + for (auto [entity, localToWorld, shape, collider] : query) { + if (collider->fresh) + { + glm::vec3 diag[2]; + shape->shape.diag(diag); + collider->localAABB = ColliderAABB{diag[0], diag[1]}; + collider->fresh = false; + } + // Get the 4 points of the collider. glm::vec3 corners[4]; - collider->shape.corners4(corners); + collider->localAABB.box().corners4(corners); // Pack the 3 points of the collider into a matrix. auto points = glm::mat4{glm::vec4{corners[0], 1.0F}, glm::vec4{corners[1], 1.0F}, glm::vec4{corners[2], 1.0F}, @@ -30,22 +40,21 @@ void updateBoxAABBs(Query, Read, Writemargin}; + max += glm::vec3{shape->margin}; // Set the AABB. - aabb->max = translation + max; - aabb->min = translation - max; + collider->worldAABB = ColliderAABB{translation - max, translation + max}; } } -void updateCapsuleAABBs(Query, Read, Write> query, +void updateCapsuleAABBs(Query, Read, Read> query, Write collisions) { (void)query; (void)collisions; } -void updateMarkers(Query> query, Write collisions) +void updateMarkers(Query> query, Write collisions) { // TODO: This is parallelizable. for (glm::length_t axis = 0; axis < 3; axis++) @@ -54,10 +63,10 @@ void updateMarkers(Query> query, Write std::sort( collisions->markersPerAxis[axis].begin(), collisions->markersPerAxis[axis].end(), [axis, &query](const BroadPhaseCollisions::SweepMarker& a, const BroadPhaseCollisions::SweepMarker& b) { - auto [aAABB] = query[a.entity].value(); - auto [bAABB] = query[b.entity].value(); - auto aPos = a.isMin ? aAABB->min : aAABB->max; - auto bPos = b.isMin ? bAABB->min : bAABB->max; + auto [aCollider] = query[a.entity].value(); + auto [bCollider] = query[b.entity].value(); + auto aPos = a.isMin ? aCollider->worldAABB.min : aCollider->worldAABB.max; + auto bPos = b.isMin ? bCollider->worldAABB.min : bCollider->worldAABB.max; return aPos[axis] < bPos[axis]; }); } @@ -106,7 +115,7 @@ CollisionType getCollisionType(bool box, bool capsule) return CollisionType::CapsuleCapsule; } -void findPairs(Query, OptRead, Read> query, +void findPairs(Query, OptRead, Read> query, Write collisions) { collisions->clearCandidates(); @@ -115,10 +124,10 @@ void findPairs(Query, OptRead, ReadsweepOverlapMaps[axis]) { - auto [box, capsule, aabb] = query[entity].value(); + auto [box, capsule, collider] = query[entity].value(); for (auto& other : overlaps) { - auto [otherBox, otherCapsule, otherAabb] = query[other].value(); + auto [otherBox, otherCapsule, otherCollider] = query[other].value(); // TODO: Should this be inside the if statement? auto type = getCollisionType(box || otherBox, capsule || otherCapsule); @@ -126,19 +135,22 @@ void findPairs(Query, OptRead, ReadoverlapsY(*otherAabb) && aabb->overlapsZ(*otherAabb)) + if (collider->worldAABB.overlapsY(otherCollider->worldAABB) && + collider->worldAABB.overlapsZ(otherCollider->worldAABB)) { collisions->addCandidate(type, {entity, other}); } break; case 1: // Y - if (aabb->overlapsX(*otherAabb) && aabb->overlapsZ(*otherAabb)) + if (collider->worldAABB.overlapsX(otherCollider->worldAABB) && + collider->worldAABB.overlapsZ(otherCollider->worldAABB)) { collisions->addCandidate(type, {entity, other}); } break; case 2: // Z - if (aabb->overlapsX(*otherAabb) && aabb->overlapsY(*otherAabb)) + if (collider->worldAABB.overlapsX(otherCollider->worldAABB) && + collider->worldAABB.overlapsY(otherCollider->worldAABB)) { collisions->addCandidate(type, {entity, other}); } diff --git a/engine/src/cubos/engine/collisions/broad_phase.hpp b/engine/src/cubos/engine/collisions/broad_phase.hpp index acb1520180..856bef9e09 100644 --- a/engine/src/cubos/engine/collisions/broad_phase.hpp +++ b/engine/src/cubos/engine/collisions/broad_phase.hpp @@ -5,10 +5,10 @@ #include -#include #include -#include -#include +#include +#include +#include #include using cubos::core::ecs::Commands; @@ -17,38 +17,38 @@ using cubos::core::ecs::Query; using cubos::core::ecs::Read; using cubos::core::ecs::Write; -using cubos::engine::BoxCollider; +using cubos::engine::BoxCollisionShape; using cubos::engine::BroadPhaseCollisions; -using cubos::engine::CapsuleCollider; -using cubos::engine::ColliderAABB; +using cubos::engine::CapsuleCollisionShape; +using cubos::engine::Collider; using cubos::engine::LocalToWorld; -/// @brief Adds collision tracking to all new entities with colliders. -template -void trackNewEntities(Query, OptRead> query, Write collisions, +/// @brief Adds collider to all new entities with collision shape. +template +void trackNewEntities(Query, OptRead> query, Write collisions, Commands commands) { - // TODO: This query should eventually be replaced by Query, Without> + // TODO: This is a bit of a hack. We should detect newly added colliders instead of using them as a marker. - for (auto [entity, collider, aabb] : query) + for (auto [entity, shape, aabb] : query) { if (!aabb) { - commands.add(entity, ColliderAABB{}); + commands.add(entity, Collider{}); collisions->addEntity(entity); } } } /// @brief Updates the AABBs of all box colliders. -void updateBoxAABBs(Query, Read, Write> query); +void updateBoxAABBs(Query, Read, Write> query); /// @brief Updates the AABBs of all capsule colliders. -void updateCapsuleAABBs(Query, Read, Write> query, +void updateCapsuleAABBs(Query, Read, Read> query, Write collisions); /// @brief Updates the sweep markers of all colliders. -void updateMarkers(Query> query, Write collisions); +void updateMarkers(Query> query, Write collisions); /// @brief Performs a sweep of all colliders. void sweep(Write collisions); @@ -58,5 +58,5 @@ void sweep(Write collisions); /// @details /// TODO: This query is disgusting. We need a way to find if a component is present without reading it. /// Maybe something like Commands but for reads? -void findPairs(Query, OptRead, Read> query, +void findPairs(Query, OptRead, Read> query, Write collisions); \ No newline at end of file diff --git a/engine/src/cubos/engine/collisions/plugin.cpp b/engine/src/cubos/engine/collisions/plugin.cpp index 0c04f3838b..ba3d66e0e9 100644 --- a/engine/src/cubos/engine/collisions/plugin.cpp +++ b/engine/src/cubos/engine/collisions/plugin.cpp @@ -1,4 +1,3 @@ -#include #include #include @@ -10,13 +9,13 @@ void cubos::engine::collisionsPlugin(Cubos& cubos) cubos.addResource(); - cubos.addComponent(); - cubos.addComponent(); - cubos.addComponent(); + cubos.addComponent(); + cubos.addComponent(); + cubos.addComponent(); - cubos.system(trackNewEntities).tagged("cubos.collisions.aabb.missing"); - cubos.system(trackNewEntities).tagged("cubos.collisions.aabb.missing"); - cubos.tag("cubos.collisions.aabb.missing").before("cubos.collisions.aabb"); + cubos.system(trackNewEntities).tagged("cubos.collisions.missing"); + cubos.system(trackNewEntities).tagged("cubos.collisions.missing"); + cubos.tag("cubos.collisions.missing").before("cubos.collisions.aabb"); cubos.system(updateBoxAABBs).tagged("cubos.collisions.aabb"); cubos.system(updateCapsuleAABBs).tagged("cubos.collisions.aabb");