Skip to content

Commit

Permalink
refactor: separate collider from shapes
Browse files Browse the repository at this point in the history
  • Loading branch information
luishfonseca committed Oct 4, 2023
1 parent 9f1b37d commit c5a680c
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 107 deletions.
4 changes: 2 additions & 2 deletions engine/include/cubos/engine/collisions/aabb.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
23 changes: 23 additions & 0 deletions engine/include/cubos/engine/collisions/collider.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// @file
/// @brief Component @ref cubos::engine::Collider.
/// @ingroup collisions-plugin

#pragma once

#include <glm/mat4x4.hpp>

#include <cubos/engine/collisions/aabb.hpp>

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
31 changes: 0 additions & 31 deletions engine/include/cubos/engine/collisions/colliders/box.hpp

This file was deleted.

25 changes: 0 additions & 25 deletions engine/include/cubos/engine/collisions/colliders/capsule.hpp

This file was deleted.

24 changes: 24 additions & 0 deletions engine/include/cubos/engine/collisions/shapes/box.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/// @file
/// @brief Component @ref cubos::engine::BoxCollisionShape.
/// @ingroup collisions-plugin

#pragma once

#include <cubos/core/geom/box.hpp>

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
17 changes: 17 additions & 0 deletions engine/include/cubos/engine/collisions/shapes/capsule.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// @file
/// @brief Component @ref cubos::engine::CapsuleCollisionShape.
/// @ingroup collisions-plugin

#pragma once

#include <cubos/core/geom/capsule.hpp>

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
18 changes: 10 additions & 8 deletions engine/samples/collisions/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

#include <cubos/engine/collisions/aabb.hpp>
#include <cubos/engine/collisions/broad_phase_collisions.hpp>
#include <cubos/engine/collisions/colliders/box.hpp>
#include <cubos/engine/collisions/collider.hpp>
#include <cubos/engine/collisions/plugin.hpp>
#include <cubos/engine/collisions/shapes/box.hpp>
#include <cubos/engine/input/plugin.hpp>
#include <cubos/engine/renderer/plugin.hpp>
#include <cubos/engine/settings/settings.hpp>
Expand Down Expand Up @@ -60,15 +61,15 @@ static void init(Commands commands, Write<Input> input, Write<ActiveCameras> cam
static void addColliders(Write<State> 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{})
.entity();
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{})
Expand Down Expand Up @@ -105,7 +106,7 @@ static void updateTransform(Write<State> state, Read<Input> input, Query<Write<P
bPos->vec += glm::vec3{0.0F, 0.0F, -0.01F};
}

static void updateCollided(Query<Read<BoxCollider>> query, Write<State> state, Read<BroadPhaseCollisions> collisions)
static void updateCollided(Query<Read<Collider>> query, Write<State> state, Read<BroadPhaseCollisions> collisions)
{
for (auto [entity, collider] : query)
{
Expand All @@ -120,12 +121,13 @@ static void updateCollided(Query<Read<BoxCollider>> query, Write<State> state, R
}
}

static void render(Query<Read<LocalToWorld>, Read<BoxCollider>, Read<ColliderAABB>> query)
static void render(Query<Read<LocalToWorld>, Read<Collider>, Read<BoxCollisionShape>> 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});
}
}
Expand Down
48 changes: 30 additions & 18 deletions engine/src/cubos/engine/collisions/broad_phase.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
#include "broad_phase.hpp"

using cubos::engine::ColliderAABB;

using CollisionType = BroadPhaseCollisions::CollisionType;

void updateBoxAABBs(Query<Read<LocalToWorld>, Read<BoxCollider>, Write<ColliderAABB>> query)
void updateBoxAABBs(Query<Read<LocalToWorld>, Read<BoxCollisionShape>, Write<Collider>> 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},
Expand All @@ -30,22 +40,21 @@ void updateBoxAABBs(Query<Read<LocalToWorld>, Read<BoxCollider>, Write<ColliderA
max = glm::max(max, glm::abs(rotatedCorners[3]));

// Add the collider's margin.
max += glm::vec3{collider->margin};
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<LocalToWorld>, Read<CapsuleCollider>, Write<ColliderAABB>> query,
void updateCapsuleAABBs(Query<Read<LocalToWorld>, Read<CapsuleCollisionShape>, Read<Collider>> query,
Write<BroadPhaseCollisions> collisions)
{
(void)query;
(void)collisions;
}

void updateMarkers(Query<Read<ColliderAABB>> query, Write<BroadPhaseCollisions> collisions)
void updateMarkers(Query<Read<Collider>> query, Write<BroadPhaseCollisions> collisions)
{
// TODO: This is parallelizable.
for (glm::length_t axis = 0; axis < 3; axis++)
Expand All @@ -54,10 +63,10 @@ void updateMarkers(Query<Read<ColliderAABB>> query, Write<BroadPhaseCollisions>
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];
});
}
Expand Down Expand Up @@ -106,7 +115,7 @@ CollisionType getCollisionType(bool box, bool capsule)
return CollisionType::CapsuleCapsule;
}

void findPairs(Query<OptRead<BoxCollider>, OptRead<CapsuleCollider>, Read<ColliderAABB>> query,
void findPairs(Query<OptRead<BoxCollisionShape>, OptRead<CapsuleCollisionShape>, Read<Collider>> query,
Write<BroadPhaseCollisions> collisions)
{
collisions->clearCandidates();
Expand All @@ -115,30 +124,33 @@ void findPairs(Query<OptRead<BoxCollider>, OptRead<CapsuleCollider>, Read<Collid
{
for (auto& [entity, overlaps] : collisions->sweepOverlapMaps[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);

switch (axis)
{
case 0: // X
if (aabb->overlapsY(*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});
}
Expand Down
32 changes: 16 additions & 16 deletions engine/src/cubos/engine/collisions/broad_phase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

#include <cubos/core/ecs/system/query.hpp>

#include <cubos/engine/collisions/aabb.hpp>
#include <cubos/engine/collisions/broad_phase_collisions.hpp>
#include <cubos/engine/collisions/colliders/box.hpp>
#include <cubos/engine/collisions/colliders/capsule.hpp>
#include <cubos/engine/collisions/collider.hpp>
#include <cubos/engine/collisions/shapes/box.hpp>
#include <cubos/engine/collisions/shapes/capsule.hpp>
#include <cubos/engine/transform/plugin.hpp>

using cubos::core::ecs::Commands;
Expand All @@ -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 <typename C>
void trackNewEntities(Query<Read<C>, OptRead<ColliderAABB>> query, Write<BroadPhaseCollisions> collisions,
/// @brief Adds collider to all new entities with collision shape.
template <typename S>
void trackNewEntities(Query<Read<S>, OptRead<Collider>> query, Write<BroadPhaseCollisions> collisions,
Commands commands)
{
// TODO: This query should eventually be replaced by Query<With<C>, Without<ColliderAABB>>
// 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<LocalToWorld>, Read<BoxCollider>, Write<ColliderAABB>> query);
void updateBoxAABBs(Query<Read<LocalToWorld>, Read<BoxCollisionShape>, Write<Collider>> query);

/// @brief Updates the AABBs of all capsule colliders.
void updateCapsuleAABBs(Query<Read<LocalToWorld>, Read<CapsuleCollider>, Write<ColliderAABB>> query,
void updateCapsuleAABBs(Query<Read<LocalToWorld>, Read<CapsuleCollisionShape>, Read<Collider>> query,
Write<BroadPhaseCollisions> collisions);

/// @brief Updates the sweep markers of all colliders.
void updateMarkers(Query<Read<ColliderAABB>> query, Write<BroadPhaseCollisions> collisions);
void updateMarkers(Query<Read<Collider>> query, Write<BroadPhaseCollisions> collisions);

/// @brief Performs a sweep of all colliders.
void sweep(Write<BroadPhaseCollisions> collisions);
Expand All @@ -58,5 +58,5 @@ void sweep(Write<BroadPhaseCollisions> 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<BoxCollider>, OptRead<CapsuleCollider>, Read<ColliderAABB>> query,
void findPairs(Query<OptRead<BoxCollisionShape>, OptRead<CapsuleCollisionShape>, Read<Collider>> query,
Write<BroadPhaseCollisions> collisions);
Loading

0 comments on commit c5a680c

Please sign in to comment.