diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 4c9228c0be..cc7f8f3f42 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -47,8 +47,9 @@ set(CUBOS_ENGINE_SOURCE "src/cubos/engine/voxels/palette.cpp" "src/cubos/engine/collisions/plugin.cpp" - "src/cubos/engine/collisions/broad_phase.cpp" - "src/cubos/engine/collisions/broad_phase_collisions.cpp" + "src/cubos/engine/collisions/broad_phase/plugin.cpp" + "src/cubos/engine/collisions/broad_phase/sweep_and_prune.cpp" + "src/cubos/engine/collisions/broad_phase/candidates.cpp" "src/cubos/engine/input/plugin.cpp" "src/cubos/engine/input/input.cpp" diff --git a/engine/include/cubos/engine/collisions/broad_phase_collisions.hpp b/engine/include/cubos/engine/collisions/broad_phase/candidates.hpp similarity index 52% rename from engine/include/cubos/engine/collisions/broad_phase_collisions.hpp rename to engine/include/cubos/engine/collisions/broad_phase/candidates.hpp index 25f15d4fe1..78c4a61e55 100644 --- a/engine/include/cubos/engine/collisions/broad_phase_collisions.hpp +++ b/engine/include/cubos/engine/collisions/broad_phase/candidates.hpp @@ -1,6 +1,8 @@ /// @file -/// @brief Resource @ref cubos::engine::BroadPhaseCollisions. -/// @ingroup collisions-plugin +/// @brief Resource @ref cubos::engine::Candidates. +/// @ingroup broad-phase-collisions-plugin + +// FIXME: This should be private, but it's used in the sample. #pragma once @@ -13,9 +15,9 @@ namespace cubos::engine { - /// @brief Resource which stores data used in broad phase collision detection. - /// @ingroup collisions-plugin - struct BroadPhaseCollisions + /// @brief Resource which stores candidates found in broad phase collision detection. + /// @ingroup broad-phase-collisions-plugin + struct BroadPhaseCandidates { /// @brief Pair of entities that may collide. using Candidate = std::pair; @@ -39,41 +41,10 @@ namespace cubos::engine Count ///< Number of collision types. }; - /// @brief Marker used for sweep and prune. - struct SweepMarker - { - core::ecs::Entity entity; ///< Entity referenced by the marker. - bool isMin; ///< Whether the marker is a min or max marker. - }; - - /// @brief List of ordered sweep markers for each axis. Stores the index of the marker in mMarkers. - std::vector markersPerAxis[3]; - - /// @brief Maps of overlapping entities for each axis calculated by sweep and prune. - /// - /// For each each map, the key is an entity and the value is a list of entities that - /// overlap with the key. Symmetrical pairs are not stored. - std::unordered_map, core::ecs::EntityHash> - sweepOverlapMaps[3]; - - /// @brief Set of active entities during sweep for each axis. - std::unordered_set activePerAxis[3]; - /// @brief Sets of collision candidates for each collision type. The index of the array is /// the collision type. std::unordered_set candidatesPerType[static_cast(CollisionType::Count)]; - /// @brief Adds an entity to the list of entities tracked by sweep and prune. - /// @param entity Entity to add. - void addEntity(core::ecs::Entity entity); - - /// @brief Removes an entity from the list of entities tracked by sweep and prune. - /// @param entity Entity to remove. - void removeEntity(core::ecs::Entity entity); - - /// @brief Clears the list of entities tracked by sweep and prune. - void clearEntities(); - /// @brief Adds a collision candidate to the list of candidates for a specific collision type. /// @param type Collision type. /// @param candidate Collision candidate. diff --git a/engine/include/cubos/engine/collisions/collider.hpp b/engine/include/cubos/engine/collisions/collider.hpp index 013334db67..f8be59be41 100644 --- a/engine/include/cubos/engine/collisions/collider.hpp +++ b/engine/include/cubos/engine/collisions/collider.hpp @@ -25,6 +25,6 @@ namespace cubos::engine /// The plugin will set it based on the shape associated with the collider. float margin; - bool fresh = true; ///< Whether the collider is fresh. This is an hack and should be done in ECS. + int fresh = -1; ///< This is an hack and should be done in ECS. }; } // namespace cubos::engine diff --git a/engine/include/cubos/engine/collisions/plugin.hpp b/engine/include/cubos/engine/collisions/plugin.hpp index c29c5a7a66..cfe101991d 100644 --- a/engine/include/cubos/engine/collisions/plugin.hpp +++ b/engine/include/cubos/engine/collisions/plugin.hpp @@ -5,8 +5,11 @@ /// @brief Plugin entry point. /// @ingroup collisions-plugin -/// @dir ./colliders -/// @brief Collider components directory. +/// @dir ./shapes +/// @brief Collision shapes components directory. + +/// @dir ./broad_phase +/// @brief Broad phase collision sub-plugin directory. #pragma once @@ -19,23 +22,17 @@ namespace cubos::engine /// @brief Adds collision detection to @b CUBOS. /// /// ## Components - /// - @ref BoxCollider - holds the box collider data. - /// - @ref CapsuleCollider - holds the capsule collider data. + /// - @ref BoxCollisionShape - holds the box collision shape. + /// - @ref CapsuleCollisionShape - holds the capsule collision shape. + /// - @ref Collider - holds collider data. /// /// ## Events /// - @ref CollisionEvent - (TODO) emitted when a collision occurs. /// - @ref TriggerEvent - (TODO) emitted when a trigger is entered or exited. /// - /// ## Resources - /// - @ref BroadPhaseCollisions - stores broad phase collision data. - /// /// ## Tags - /// - `cubos.collisions.aabb.missing` - missing aabb colliders are added. - /// - `cubos.collisions.aabb` - collider aabbs are updated. - /// - `cubos.collisions.broad.markers` - sweep markers are updated. - /// - `cubos.collisions.broad.sweep` - sweep is performed. - /// - `cubos.collisions.broad` - broad phase collision detection. - /// - `cubos.collisions` - collisions are resolved. + /// - `cubos.collisions.setup` - new colliders are setup. + /// - `cubos.collisions.broad` - broad phase candidate pairs are generated. /// /// ## Dependencies /// - @ref transform-plugin diff --git a/engine/samples/collisions/main.cpp b/engine/samples/collisions/main.cpp index fe0a204265..ce3d10851e 100644 --- a/engine/samples/collisions/main.cpp +++ b/engine/samples/collisions/main.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include #include @@ -108,12 +108,12 @@ 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 candidates) { for (auto [entity, collider] : query) { for (const auto& [collider1, collider2] : - collisions->candidates(BroadPhaseCollisions::CollisionType::BoxCapsule)) + candidates->candidates(BroadPhaseCandidates::CollisionType::BoxCapsule)) { if (collider1 == entity || collider2 == entity) { diff --git a/engine/src/cubos/engine/collisions/broad_phase.cpp b/engine/src/cubos/engine/collisions/broad_phase.cpp deleted file mode 100644 index fa53ee1263..0000000000 --- a/engine/src/cubos/engine/collisions/broad_phase.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "broad_phase.hpp" - -using CollisionType = BroadPhaseCollisions::CollisionType; - -void setupNewBoxes(Query, Write> query, Write collisions) -{ - for (auto [entity, shape, collider] : query) - { - if (collider->fresh) - { - collisions->addEntity(entity); - - shape->box.diag(collider->localAABB.diag); - - collider->margin = 0.04F; - - collider->fresh = false; - } - } -} - -void setupNewCapsules(Query, Write> query, Write collisions) -{ - for (auto [entity, shape, collider] : query) - { - if (collider->fresh) - { - collisions->addEntity(entity); - - collider->localAABB = shape->capsule.aabb(); - - collider->margin = 0.0F; - - collider->fresh = false; - } - } -} - -void updateAABBs(Query, Write> query) -{ - for (auto [entity, localToWorld, collider] : query) - { - // Get the 4 points of the collider. - glm::vec3 corners[4]; - 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}, - glm::vec4{corners[3], 1.0F}}; - - // Transforms collider space to world space. - auto transform = localToWorld->mat * collider->transform; - - // Only want scale and rotation, extract translation and remove it. - auto translation = glm::vec3{transform[3]}; - transform[3] = glm::vec4{0.0F, 0.0F, 0.0F, 1.0F}; - - // Rotate and scale corners. - auto rotatedCorners = glm::mat4x3{transform * points}; - - // Get the max of the rotated corners. - auto max = glm::max(glm::abs(rotatedCorners[0]), glm::abs(rotatedCorners[1])); - max = glm::max(max, glm::abs(rotatedCorners[2])); - max = glm::max(max, glm::abs(rotatedCorners[3])); - - // Add the collider's margin. - max += glm::vec3{collider->margin}; - - // Set the AABB. - collider->worldAABB.min(translation - max); - collider->worldAABB.max(translation + max); - }; -} - -void updateMarkers(Query> query, Write collisions) -{ - // TODO: This is parallelizable. - for (glm::length_t axis = 0; axis < 3; axis++) - { - // TODO: Should use insert sort to leverage spatial coherence. - std::sort( - collisions->markersPerAxis[axis].begin(), collisions->markersPerAxis[axis].end(), - [axis, &query](const BroadPhaseCollisions::SweepMarker& a, const BroadPhaseCollisions::SweepMarker& b) { - 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]; - }); - } -} - -void sweep(Write collisions) -{ - // TODO: This is parallelizable. - for (glm::length_t axis = 0; axis < 3; axis++) - { - CUBOS_ASSERT(collisions->activePerAxis[axis].empty(), "Last sweep entered an entity but never exited"); - - collisions->sweepOverlapMaps[axis].clear(); - - for (auto& marker : collisions->markersPerAxis[axis]) - { - if (marker.isMin) - { - for (const auto& other : collisions->activePerAxis[axis]) - { - collisions->sweepOverlapMaps[axis][marker.entity].push_back(other); - } - - collisions->activePerAxis[axis].insert(marker.entity); - } - else - { - collisions->activePerAxis[axis].erase(marker.entity); - } - } - } -} - -CollisionType getCollisionType(bool box, bool capsule) -{ - if (box && capsule) - { - return CollisionType::BoxCapsule; - } - - if (box) - { - return CollisionType::BoxBox; - } - - return CollisionType::CapsuleCapsule; -} - -void findPairs(Query, OptRead, Read> query, - Write collisions) -{ - collisions->clearCandidates(); - - for (glm::length_t axis = 0; axis < 3; axis++) - { - for (auto& [entity, overlaps] : collisions->sweepOverlapMaps[axis]) - { - auto [box, capsule, collider] = query[entity].value(); - for (auto& other : overlaps) - { - auto [otherBox, otherCapsule, otherCollider] = query[other].value(); - - // TODO: Should this be inside the if statement? - auto type = getCollisionType(box || otherBox, capsule || otherCapsule); - - switch (axis) - { - case 0: // X - if (collider->worldAABB.overlapsY(otherCollider->worldAABB) && - collider->worldAABB.overlapsZ(otherCollider->worldAABB)) - { - collisions->addCandidate(type, {entity, other}); - } - break; - case 1: // Y - if (collider->worldAABB.overlapsX(otherCollider->worldAABB) && - collider->worldAABB.overlapsZ(otherCollider->worldAABB)) - { - collisions->addCandidate(type, {entity, other}); - } - break; - case 2: // Z - if (collider->worldAABB.overlapsX(otherCollider->worldAABB) && - collider->worldAABB.overlapsY(otherCollider->worldAABB)) - { - collisions->addCandidate(type, {entity, other}); - } - break; - } - } - } - } -} \ No newline at end of file diff --git a/engine/src/cubos/engine/collisions/broad_phase.hpp b/engine/src/cubos/engine/collisions/broad_phase.hpp deleted file mode 100644 index 51b8789b65..0000000000 --- a/engine/src/cubos/engine/collisions/broad_phase.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/// @file -/// @brief Broad phase collision detection systems. - -#pragma once - -#include - -#include -#include -#include -#include -#include - -using cubos::core::ecs::Commands; -using cubos::core::ecs::OptRead; -using cubos::core::ecs::Query; -using cubos::core::ecs::Read; -using cubos::core::ecs::Write; - -using cubos::engine::BoxCollisionShape; -using cubos::engine::BroadPhaseCollisions; -using cubos::engine::CapsuleCollisionShape; -using cubos::engine::Collider; -using cubos::engine::LocalToWorld; - -/// @brief Setups new box colliders. -void setupNewBoxes(Query, Write> query, Write collisions); - -/// @brief Setups new capsule colliders. -void setupNewCapsules(Query, Write> query, - Write collisions); - -/// @brief Updates the AABBs of all colliders. -void updateAABBs(Query, Write> query); - -/// @brief Updates the sweep markers of all colliders. -void updateMarkers(Query> query, Write collisions); - -/// @brief Performs a sweep of all colliders. -void sweep(Write collisions); - -/// @brief Finds all pairs of colliders which may be colliding. -/// -/// @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, - Write collisions); diff --git a/engine/src/cubos/engine/collisions/broad_phase/candidates.cpp b/engine/src/cubos/engine/collisions/broad_phase/candidates.cpp new file mode 100644 index 0000000000..d07cb01561 --- /dev/null +++ b/engine/src/cubos/engine/collisions/broad_phase/candidates.cpp @@ -0,0 +1,21 @@ +#include + +using namespace cubos::engine; + +void BroadPhaseCandidates::addCandidate(CollisionType type, Candidate candidate) +{ + candidatesPerType[static_cast(type)].insert(candidate); +} + +auto BroadPhaseCandidates::candidates(CollisionType type) const -> const std::unordered_set& +{ + return candidatesPerType[static_cast(type)]; +} + +void BroadPhaseCandidates::clearCandidates() +{ + for (auto& candidates : candidatesPerType) + { + candidates.clear(); + } +} diff --git a/engine/src/cubos/engine/collisions/broad_phase/plugin.cpp b/engine/src/cubos/engine/collisions/broad_phase/plugin.cpp new file mode 100644 index 0000000000..7838c85b5e --- /dev/null +++ b/engine/src/cubos/engine/collisions/broad_phase/plugin.cpp @@ -0,0 +1,196 @@ + +#include "plugin.hpp" + +#include +#include +#include +#include +#include + +#include "sweep_and_prune.hpp" + +using cubos::core::ecs::OptRead; +using cubos::core::ecs::Query; +using cubos::core::ecs::Read; +using cubos::core::ecs::Write; +using namespace cubos::engine; + +/// @brief Tracks all new colliders. +static void trackNewCollidersSystem(Query> query, Write sweepAndPrune) +{ + for (auto [entity, collider] : query) + { + if (collider->fresh == 0) + { + sweepAndPrune->addEntity(entity); + } + } +} + +/// @brief Updates the AABBs of all colliders. +static void updateAABBsSystem(Query, Write> query) +{ + for (auto [entity, localToWorld, collider] : query) + { + // Get the 4 points of the collider. + glm::vec3 corners[4]; + 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}, + glm::vec4{corners[3], 1.0F}}; + + // Transforms collider space to world space. + auto transform = localToWorld->mat * collider->transform; + + // Only want scale and rotation, extract translation and remove it. + auto translation = glm::vec3{transform[3]}; + transform[3] = glm::vec4{0.0F, 0.0F, 0.0F, 1.0F}; + + // Rotate and scale corners. + auto rotatedCorners = glm::mat4x3{transform * points}; + + // Get the max of the rotated corners. + auto max = glm::max(glm::abs(rotatedCorners[0]), glm::abs(rotatedCorners[1])); + max = glm::max(max, glm::abs(rotatedCorners[2])); + max = glm::max(max, glm::abs(rotatedCorners[3])); + + // Add the collider's margin. + max += glm::vec3{collider->margin}; + + // Set the AABB. + collider->worldAABB.min(translation - max); + collider->worldAABB.max(translation + max); + }; +} + +/// @brief Updates the sweep markers of all colliders. +static void updateMarkersSystem(Query> query, Write sweepAndPrune) +{ + // TODO: This is parallelizable. + for (glm::length_t axis = 0; axis < 3; axis++) + { + // TODO: Should use insert sort to leverage spatial coherence. + std::sort(sweepAndPrune->markersPerAxis[axis].begin(), sweepAndPrune->markersPerAxis[axis].end(), + [axis, &query](const BroadPhaseSweepAndPrune::SweepMarker& a, + const BroadPhaseSweepAndPrune::SweepMarker& b) { + 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]; + }); + } +} + +/// @brief Performs a sweep of all colliders. +static void sweepSystem(Write sweepAndPrune) +{ + // TODO: This is parallelizable. + for (glm::length_t axis = 0; axis < 3; axis++) + { + CUBOS_ASSERT(sweepAndPrune->activePerAxis[axis].empty(), "Last sweep entered an entity but never exited"); + + sweepAndPrune->sweepOverlapMaps[axis].clear(); + + for (auto& marker : sweepAndPrune->markersPerAxis[axis]) + { + if (marker.isMin) + { + for (const auto& other : sweepAndPrune->activePerAxis[axis]) + { + sweepAndPrune->sweepOverlapMaps[axis][marker.entity].push_back(other); + } + + sweepAndPrune->activePerAxis[axis].insert(marker.entity); + } + else + { + sweepAndPrune->activePerAxis[axis].erase(marker.entity); + } + } + } +} + +BroadPhaseCandidates::CollisionType getCollisionType(bool box, bool capsule) +{ + if (box && capsule) + { + return BroadPhaseCandidates::CollisionType::BoxCapsule; + } + + if (box) + { + return BroadPhaseCandidates::CollisionType::BoxBox; + } + + return BroadPhaseCandidates::CollisionType::CapsuleCapsule; +} + +/// @brief Finds all pairs of colliders which may be colliding. +/// +/// @details +/// TODO: This query is disgusting. We need a way to find if a component is present without reading it. +static void findPairsSystem(Query, OptRead, Read> query, + Read sweepAndPrune, Write candidates) +{ + candidates->clearCandidates(); + + for (glm::length_t axis = 0; axis < 3; axis++) + { + for (const auto& [entity, overlaps] : sweepAndPrune->sweepOverlapMaps[axis]) + { + auto [box, capsule, collider] = query[entity].value(); + for (const auto& other : overlaps) + { + auto [otherBox, otherCapsule, otherCollider] = query[other].value(); + + // TODO: Should this be inside the if statement? + auto type = getCollisionType(box || otherBox, capsule || otherCapsule); + + switch (axis) + { + case 0: // X + if (collider->worldAABB.overlapsY(otherCollider->worldAABB) && + collider->worldAABB.overlapsZ(otherCollider->worldAABB)) + { + candidates->addCandidate(type, {entity, other}); + } + break; + case 1: // Y + if (collider->worldAABB.overlapsX(otherCollider->worldAABB) && + collider->worldAABB.overlapsZ(otherCollider->worldAABB)) + { + candidates->addCandidate(type, {entity, other}); + } + break; + case 2: // Z + if (collider->worldAABB.overlapsX(otherCollider->worldAABB) && + collider->worldAABB.overlapsY(otherCollider->worldAABB)) + { + candidates->addCandidate(type, {entity, other}); + } + break; + } + } + } + } +} + +void cubos::engine::broadPhaseCollisionsPlugin(Cubos& cubos) +{ + cubos.addResource(); + cubos.addResource(); + + cubos.system(trackNewCollidersSystem).tagged("cubos.collisions.aabb.setup"); + + cubos.system(updateAABBsSystem) + .tagged("cubos.collisions.aabb.update") + .after("cubos.collisions.setup") + .after("cubos.collisions.aabb.setup") + .after("cubos.transform.update"); + + cubos.system(updateMarkersSystem).tagged("cubos.collisions.broad.markers").after("cubos.collisions.aabb.update"); + cubos.system(sweepSystem).tagged("cubos.collisions.broad.sweep").after("cubos.collisions.broad.markers"); + cubos.system(findPairsSystem).tagged("cubos.collisions.broad").after("cubos.collisions.broad.sweep"); +} diff --git a/engine/src/cubos/engine/collisions/broad_phase/plugin.hpp b/engine/src/cubos/engine/collisions/broad_phase/plugin.hpp new file mode 100644 index 0000000000..42be4d8daa --- /dev/null +++ b/engine/src/cubos/engine/collisions/broad_phase/plugin.hpp @@ -0,0 +1,26 @@ +/// @dir +/// @brief @ref broad-phase-collisions-plugin plugin directory. + +/// @file +/// @brief Plugin entry point. +/// @ingroup broad-phase-collisions-plugin + +#pragma once + +#include + +namespace cubos::engine +{ + /// @defgroup broad-phase-collisions-plugin Broad-phase Collisions + /// @ingroup engine + /// @brief Adds broad-phase collision detection to @b CUBOS. + /// + /// ## Resources + /// - @ref BroadPhaseCandidates - stores broad phase collision data. + /// - @ref BroadPhaseSweepAndPrune - stores sweep and prune markers. + + /// @brief Plugin entry function. + /// @param cubos @b CUBOS. main class. + /// @ingroup broad-phase-collisions-plugin + void broadPhaseCollisionsPlugin(Cubos& cubos); +} // namespace cubos::engine diff --git a/engine/src/cubos/engine/collisions/broad_phase/sweep_and_prune.cpp b/engine/src/cubos/engine/collisions/broad_phase/sweep_and_prune.cpp new file mode 100644 index 0000000000..bee8403b02 --- /dev/null +++ b/engine/src/cubos/engine/collisions/broad_phase/sweep_and_prune.cpp @@ -0,0 +1,33 @@ +#include + +#include "sweep_and_prune.hpp" + +using cubos::core::ecs::Entity; +using namespace cubos::engine; + +void BroadPhaseSweepAndPrune::addEntity(Entity entity) +{ + for (auto& markers : markersPerAxis) + { + markers.push_back({entity, true}); + markers.push_back({entity, false}); + } +} + +void BroadPhaseSweepAndPrune::removeEntity(Entity entity) +{ + for (auto& markers : markersPerAxis) + { + markers.erase(std::remove_if(markers.begin(), markers.end(), + [entity](const SweepMarker& m) { return m.entity == entity; }), + markers.end()); + } +} + +void BroadPhaseSweepAndPrune::clearEntities() +{ + for (auto& markers : markersPerAxis) + { + markers.clear(); + } +} diff --git a/engine/src/cubos/engine/collisions/broad_phase/sweep_and_prune.hpp b/engine/src/cubos/engine/collisions/broad_phase/sweep_and_prune.hpp new file mode 100644 index 0000000000..c24455a081 --- /dev/null +++ b/engine/src/cubos/engine/collisions/broad_phase/sweep_and_prune.hpp @@ -0,0 +1,51 @@ +/// @file +/// @brief Resource @ref cubos::engine::BroadPhaseSweepAndPrune. +/// @ingroup broad-phase-collisions-plugin + +#pragma once + +#include +#include +#include + +#include +#include + +namespace cubos::engine +{ + /// @brief Resource which stores sweep and prune data. + /// @ingroup broad-phase-collisions-plugin + struct BroadPhaseSweepAndPrune + { + /// @brief Marker used for sweep and prune. + struct SweepMarker + { + core::ecs::Entity entity; ///< Entity referenced by the marker. + bool isMin; ///< Whether the marker is a min or max marker. + }; + + /// @brief List of ordered sweep markers for each axis. Stores the index of the marker in mMarkers. + std::vector markersPerAxis[3]; + + /// @brief Maps of overlapping entities for each axis calculated by sweep and prune. + /// + /// For each each map, the key is an entity and the value is a list of entities that + /// overlap with the key. Symmetrical pairs are not stored. + std::unordered_map, core::ecs::EntityHash> + sweepOverlapMaps[3]; + + /// @brief Set of active entities during sweep for each axis. + std::unordered_set activePerAxis[3]; + + /// @brief Adds an entity to the list of entities tracked by sweep and prune. + /// @param entity Entity to add. + void addEntity(core::ecs::Entity entity); + + /// @brief Removes an entity from the list of entities tracked by sweep and prune. + /// @param entity Entity to remove. + void removeEntity(core::ecs::Entity entity); + + /// @brief Clears the list of entities tracked by sweep and prune. + void clearEntities(); + }; +} // namespace cubos::engine diff --git a/engine/src/cubos/engine/collisions/broad_phase_collisions.cpp b/engine/src/cubos/engine/collisions/broad_phase_collisions.cpp deleted file mode 100644 index 0c960253aa..0000000000 --- a/engine/src/cubos/engine/collisions/broad_phase_collisions.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include - -#include - -using cubos::core::ecs::Entity; - -using cubos::engine::BroadPhaseCollisions; - -void BroadPhaseCollisions::addEntity(Entity entity) -{ - for (auto& markers : markersPerAxis) - { - markers.push_back({entity, true}); - markers.push_back({entity, false}); - } -} - -void BroadPhaseCollisions::removeEntity(Entity entity) -{ - for (auto& markers : markersPerAxis) - { - markers.erase(std::remove_if(markers.begin(), markers.end(), - [entity](const SweepMarker& m) { return m.entity == entity; }), - markers.end()); - } -} - -void BroadPhaseCollisions::clearEntities() -{ - for (auto& markers : markersPerAxis) - { - markers.clear(); - } -} - -void BroadPhaseCollisions::addCandidate(CollisionType type, Candidate candidate) -{ - candidatesPerType[static_cast(type)].insert(candidate); -} - -auto BroadPhaseCollisions::candidates(CollisionType type) const -> const std::unordered_set& -{ - return candidatesPerType[static_cast(type)]; -} - -void BroadPhaseCollisions::clearCandidates() -{ - for (auto& candidates : candidatesPerType) - { - candidates.clear(); - } -} diff --git a/engine/src/cubos/engine/collisions/plugin.cpp b/engine/src/cubos/engine/collisions/plugin.cpp index 2fda8b64c0..08dbf99122 100644 --- a/engine/src/cubos/engine/collisions/plugin.cpp +++ b/engine/src/cubos/engine/collisions/plugin.cpp @@ -1,26 +1,66 @@ -#include +#include "broad_phase/plugin.hpp" + +#include + +#include #include +#include +#include +#include -#include "broad_phase.hpp" +using cubos::core::ecs::Query; +using cubos::core::ecs::Read; +using cubos::core::ecs::Write; +using namespace cubos::engine; + +/// @brief Setups new box colliders. +static void setupNewBoxesSystem(Query, Write> query) +{ + for (auto [entity, shape, collider] : query) + { + if (collider->fresh < 1) + { + collider->fresh++; + } + + if (collider->fresh == 0) + { + shape->box.diag(collider->localAABB.diag); + + collider->margin = 0.04F; + } + } +} + +/// @brief Setups new capsule colliders. +static void setupNewCapsulesSystem(Query, Write> query) +{ + for (auto [entity, shape, collider] : query) + { + if (collider->fresh < 1) + { + collider->fresh++; + } + + if (collider->fresh == 0) + { + collider->localAABB = shape->capsule.aabb(); + + collider->margin = 0.0F; + } + } +} void cubos::engine::collisionsPlugin(Cubos& cubos) { cubos.addPlugin(transformPlugin); - cubos.addResource(); + cubos.addPlugin(broadPhaseCollisionsPlugin); cubos.addComponent(); cubos.addComponent(); cubos.addComponent(); - cubos.system(setupNewBoxes).tagged("cubos.collisions.setup"); - cubos.system(setupNewCapsules).tagged("cubos.collisions.setup"); - cubos.system(updateAABBs) - .after("cubos.collisions.setup") - .after("cubos.transform.update") - .before("cubos.collisions.broad.markers"); - - cubos.system(updateMarkers).tagged("cubos.collisions.broad.markers"); - cubos.system(sweep).tagged("cubos.collisions.broad.sweep").after("cubos.collisions.broad.markers"); - cubos.system(findPairs).tagged("cubos.collisions.broad").after("cubos.collisions.broad.sweep"); + cubos.system(setupNewBoxesSystem).tagged("cubos.collisions.setup"); + cubos.system(setupNewCapsulesSystem).tagged("cubos.collisions.setup"); }