diff options
author | Loek Le Blansch <loek@pipeframe.xyz> | 2024-12-06 19:19:56 +0100 |
---|---|---|
committer | Loek Le Blansch <loek@pipeframe.xyz> | 2024-12-06 19:19:56 +0100 |
commit | b9f22bb78b2d5f565451d9cd97c523fbedadaa18 (patch) | |
tree | f8c406cdcf12a9a996b7aa2eaa3f7036c9697c5d /src/crepe | |
parent | 0bed469e2ececfa240085a34252dd7ed23519de3 (diff) | |
parent | 70c1cd14ec4c4aca185d5a7e4a3749169f6425f4 (diff) |
Merge branch 'jaro/collision-system' of github.com:lonkaars/crepe
Diffstat (limited to 'src/crepe')
-rw-r--r-- | src/crepe/Collider.cpp | 2 | ||||
-rw-r--r-- | src/crepe/Collider.h | 13 | ||||
-rw-r--r-- | src/crepe/api/BoxCollider.cpp | 10 | ||||
-rw-r--r-- | src/crepe/api/BoxCollider.h | 22 | ||||
-rw-r--r-- | src/crepe/api/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/crepe/api/CircleCollider.cpp | 8 | ||||
-rw-r--r-- | src/crepe/api/CircleCollider.h | 16 | ||||
-rw-r--r-- | src/crepe/api/Event.h | 4 | ||||
-rw-r--r-- | src/crepe/api/LoopManager.cpp | 12 | ||||
-rw-r--r-- | src/crepe/api/Rigidbody.cpp | 4 | ||||
-rw-r--r-- | src/crepe/api/Rigidbody.h | 124 | ||||
-rw-r--r-- | src/crepe/api/Script.h | 1 | ||||
-rw-r--r-- | src/crepe/system/CollisionSystem.cpp | 548 | ||||
-rw-r--r-- | src/crepe/system/CollisionSystem.h | 299 | ||||
-rw-r--r-- | src/crepe/system/PhysicsSystem.cpp | 13 |
15 files changed, 1034 insertions, 46 deletions
diff --git a/src/crepe/Collider.cpp b/src/crepe/Collider.cpp index bbec488..77e11c8 100644 --- a/src/crepe/Collider.cpp +++ b/src/crepe/Collider.cpp @@ -2,4 +2,4 @@ using namespace crepe; -Collider::Collider(game_object_id_t id) : Component(id) {} +Collider::Collider(game_object_id_t id, const vec2 & offset) : Component(id), offset(offset) {} diff --git a/src/crepe/Collider.h b/src/crepe/Collider.h index 827f83d..a08a68e 100644 --- a/src/crepe/Collider.h +++ b/src/crepe/Collider.h @@ -1,14 +1,23 @@ #pragma once #include "Component.h" +#include "types.h" namespace crepe { class Collider : public Component { public: - Collider(game_object_id_t id); + Collider(game_object_id_t id, const vec2 & offset); - int size; +public: + /** + * \brief Offset of the collider relative to the rigidbody position. + * + * The `offset` defines the positional shift applied to the collider relative to the position of the rigidbody it is attached to. + * This allows the collider to be placed at a different position than the rigidbody. + * + */ + vec2 offset; }; } // namespace crepe diff --git a/src/crepe/api/BoxCollider.cpp b/src/crepe/api/BoxCollider.cpp new file mode 100644 index 0000000..c097a24 --- /dev/null +++ b/src/crepe/api/BoxCollider.cpp @@ -0,0 +1,10 @@ +#include "BoxCollider.h" + +#include "../Collider.h" + +using namespace crepe; + +BoxCollider::BoxCollider(game_object_id_t game_object_id, const vec2 & offset, + const vec2 & dimensions) + : Collider(game_object_id, offset), + dimensions(dimensions) {} diff --git a/src/crepe/api/BoxCollider.h b/src/crepe/api/BoxCollider.h new file mode 100644 index 0000000..89e43d8 --- /dev/null +++ b/src/crepe/api/BoxCollider.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../Collider.h" +#include "Vector2.h" +#include "types.h" + +namespace crepe { + +/** + * \brief A class representing a box-shaped collider. + * + * This class is used for collision detection with other colliders (e.g., CircleCollider). + */ +class BoxCollider : public Collider { +public: + BoxCollider(game_object_id_t game_object_id, const vec2 & offset, const vec2 & dimensions); + + //! Width and height of the box collider + vec2 dimensions; +}; + +} // namespace crepe diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index ac8f301..b2e3df8 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -13,6 +13,8 @@ target_sources(crepe PUBLIC Metadata.cpp Camera.cpp Animator.cpp + BoxCollider.cpp + CircleCollider.cpp IKeyListener.cpp IMouseListener.cpp LoopManager.cpp @@ -44,6 +46,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES Metadata.h Camera.h Animator.h + BoxCollider.h + CircleCollider.h EventHandler.h EventHandler.hpp Event.h diff --git a/src/crepe/api/CircleCollider.cpp b/src/crepe/api/CircleCollider.cpp new file mode 100644 index 0000000..a4271e9 --- /dev/null +++ b/src/crepe/api/CircleCollider.cpp @@ -0,0 +1,8 @@ +#include "CircleCollider.h" + +using namespace crepe; + +CircleCollider::CircleCollider(game_object_id_t game_object_id, const vec2 & offset, + float radius) + : Collider(game_object_id, offset), + radius(radius) {} diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h index e77a592..ebd1cb2 100644 --- a/src/crepe/api/CircleCollider.h +++ b/src/crepe/api/CircleCollider.h @@ -1,14 +1,22 @@ #pragma once + +#include "Vector2.h" + #include "../Collider.h" namespace crepe { +/** + * \brief A class representing a circle-shaped collider. + * + * This class is used for collision detection with other colliders (e.g., BoxCollider). + */ class CircleCollider : public Collider { public: - CircleCollider(game_object_id_t game_object_id, int radius) - : Collider(game_object_id), - radius(radius) {} - int radius; + CircleCollider(game_object_id_t game_object_id, const vec2 & offset, float radius); + + //! Radius of the circle collider. + float radius; }; } // namespace crepe diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index 6298118..f2f3daf 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -112,10 +112,6 @@ public: //! scroll amount in y axis (from and away from the person). float scroll_delta = 0; }; -/** - * \brief Event triggered during a collision between objects. - */ -class CollisionEvent : public Event {}; /** * \brief Event triggered when text is submitted, e.g., from a text input. diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index 8fef0c4..8ccc0a4 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -5,6 +5,7 @@ #include "../system/PhysicsSystem.h" #include "../system/RenderSystem.h" #include "../system/ScriptSystem.h" +#include "manager/EventManager.h" #include "LoopManager.h" @@ -29,7 +30,14 @@ void LoopManager::start() { } void LoopManager::set_running(bool running) { this->game_running = running; } -void LoopManager::fixed_update() {} +void LoopManager::fixed_update() { + // TODO: retrieve EventManager from direct member after singleton refactor + EventManager & ev = this->mediator.event_manager; + ev.dispatch_events(); + this->get_system<ScriptSystem>().update(); + this->get_system<PhysicsSystem>().update(); + this->get_system<CollisionSystem>().update(); +} void LoopManager::loop() { LoopTimer & timer = this->loop_timer; @@ -53,8 +61,8 @@ void LoopManager::loop() { void LoopManager::setup() { LoopTimer & timer = this->loop_timer; - this->game_running = true; + this->scene_manager.load_next_scene(); timer.start(); timer.set_fps(200); } diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp index 576ca45..8213afb 100644 --- a/src/crepe/api/Rigidbody.cpp +++ b/src/crepe/api/Rigidbody.cpp @@ -10,6 +10,4 @@ void crepe::Rigidbody::add_force_linear(const vec2 & force) { this->data.linear_velocity += force; } -void crepe::Rigidbody::add_force_angular(double force) { - this->data.angular_velocity += force; -} +void crepe::Rigidbody::add_force_angular(float force) { this->data.angular_velocity += force; } diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h index 3b0588f..8265ba5 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -1,5 +1,8 @@ #pragma once +#include <cmath> +#include <set> + #include "../Component.h" #include "types.h" @@ -34,11 +37,11 @@ public: * the systems will not move the object. */ struct PhysicsConstraints { - //! X constraint + //! Prevent movement along X axis bool x = false; - //! Y constraint + //! Prevent movement along Y axis bool y = false; - //! rotation constraint + //! Prevent rotation bool rotation = false; }; @@ -50,29 +53,93 @@ public: */ struct Data { //! objects mass - double mass = 0.0; - //! gravtiy scale - double gravity_scale = 0.0; - //! Changes if physics apply + float mass = 0.0; + /** + * \brief Gravity scale factor. + * + * The `gravity_scale` controls how much gravity affects the object. It is a multiplier applied to the default + * gravity force, allowing for fine-grained control over how the object responds to gravity. + * + */ + float gravity_scale = 0; + + //! Defines the type of the physics body, which determines how the physics system interacts with the object. BodyType body_type = BodyType::DYNAMIC; - //! linear velocity of object + + /** + * \name Linear (positional) motion + * + * These variables define the linear motion (movement along the position) of an object. + * The linear velocity is applied to the object's position in each update of the PhysicsSystem. + * The motion is affected by the object's linear velocity, its maximum velocity, and a coefficient + * that can scale the velocity over time. + * + * \{ + */ + //! Linear velocity of the object (speed and direction). vec2 linear_velocity; - //! maximum linear velocity of object - vec2 max_linear_velocity; - //! linear damping of object - vec2 linear_damping; - //! angular velocity of object - double angular_velocity = 0.0; - //! max angular velocity of object - double max_angular_velocity = 0.0; - //! angular damping of object - double angular_damping = 0.0; - //! movements constraints of object + //! Maximum linear velocity of the object. This limits the object's speed. + vec2 max_linear_velocity = {INFINITY, INFINITY}; + //! Linear velocity coefficient. This scales the object's velocity for adjustment or damping. + vec2 linear_velocity_coefficient = {1, 1}; + //! \} + + /** + * \name Angular (rotational) motion + * + * These variables define the angular motion (rotation) of an object. + * The angular velocity determines how quickly the object rotates, while the maximum angular velocity + * sets a limit on the rotation speed. The angular velocity coefficient applies damping or scaling + * to the angular velocity, which can be used to simulate friction or other effects that slow down rotation. + * + * \{ + */ + //! Angular velocity of the object, representing the rate of rotation (in degrees). + float angular_velocity = 0; + //! Maximum angular velocity of the object. This limits the maximum rate of rotation. + float max_angular_velocity = INFINITY; + //! Angular velocity coefficient. This scales the object's angular velocity, typically used for damping. + float angular_velocity_coefficient = 1; + //! \} + + /** + * \brief Movement constraints for an object. + * + * The `PhysicsConstraints` struct defines the constraints that restrict an object's movement + * in certain directions or prevent rotation. These constraints effect only the physics system + * to prevent the object from moving or rotating in specified ways. + * + */ PhysicsConstraints constraints; - //! if gravity applies - bool use_gravity = true; - //! if object bounces - bool bounce = false; + + /** + * \brief Elasticity factor of the material (bounce factor). + * + * The `elasticity_coefficient` controls how much of the object's velocity is retained after a collision. + * It represents the material's ability to bounce or recover velocity upon impact. The coefficient is a value + * above 0.0. + * + */ + float elastisity_coefficient = 0.0; + + /** + * \brief Offset of all colliders relative to the object's transform position. + * + * The `offset` defines a positional shift applied to all colliders associated with the object, relative to the object's + * transform position. This allows for the colliders to be placed at a different position than the object's actual + * position, without modifying the object's transform itself. + * + */ + vec2 offset; + + /** + * \brief Defines the collision layers of a GameObject. + * + * The `collision_layers` specifies the layers that the GameObject will collide with. + * Each element represents a layer ID, and the GameObject will only detect + * collisions with other GameObjects that belong to these layers. + */ + std::set<int> collision_layers; }; public: @@ -96,7 +163,16 @@ public: * * \param force Vector2 that is added to the angular force. */ - void add_force_angular(double force); + void add_force_angular(float force); + +protected: + /** + * Ensures there is at most one Rigidbody component per entity. + * \return Always returns 1, indicating this constraint. + */ + virtual int get_instances_max() const { return 1; } + //! ComponentManager instantiates all components + friend class ComponentManager; }; } // namespace crepe diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index 1b339b0..d1be1dc 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -4,6 +4,7 @@ #include "../manager/EventManager.h" #include "../manager/Mediator.h" +#include "../system/CollisionSystem.h" #include "../types.h" #include "../util/OptionalRef.h" diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp index c74ca1d..44a0431 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -1,5 +1,551 @@ +#include <algorithm> +#include <cmath> +#include <cstddef> +#include <functional> +#include <optional> +#include <utility> +#include <variant> + +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" + +#include "Collider.h" #include "CollisionSystem.h" +#include "types.h" +#include "util/OptionalRef.h" using namespace crepe; -void CollisionSystem::update() {} +void CollisionSystem::update() { + std::vector<CollisionInternal> all_colliders; + game_object_id_t id = 0; + ComponentManager & mgr = this->mediator.component_manager; + RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); + // Collisions can only happen on object with a rigidbody + for (Rigidbody & rigidbody : rigidbodies) { + if (!rigidbody.active) continue; + id = rigidbody.game_object_id; + Transform & transform = mgr.get_components_by_id<Transform>(id).front().get(); + // Check if the boxcollider is active and has the same id as the rigidbody. + RefVector<BoxCollider> boxcolliders = mgr.get_components_by_type<BoxCollider>(); + for (BoxCollider & boxcollider : boxcolliders) { + if (boxcollider.game_object_id != id) continue; + if (!boxcollider.active) continue; + all_colliders.push_back({.id = id, + .collider = collider_variant{boxcollider}, + .transform = transform, + .rigidbody = rigidbody}); + } + // Check if the circlecollider is active and has the same id as the rigidbody. + RefVector<CircleCollider> circlecolliders + = mgr.get_components_by_type<CircleCollider>(); + for (CircleCollider & circlecollider : circlecolliders) { + if (circlecollider.game_object_id != id) continue; + if (!circlecollider.active) continue; + all_colliders.push_back({.id = id, + .collider = collider_variant{circlecollider}, + .transform = transform, + .rigidbody = rigidbody}); + } + } + + // Check between all colliders if there is a collision + std::vector<std::pair<CollisionInternal, CollisionInternal>> collided + = this->gather_collisions(all_colliders); + + // For both objects call the collision handler + for (auto & collision_pair : collided) { + this->collision_handler_request(collision_pair.first, collision_pair.second); + this->collision_handler_request(collision_pair.second, collision_pair.first); + } +} + +void CollisionSystem::collision_handler_request(CollisionInternal & this_data, + CollisionInternal & other_data) { + + CollisionInternalType type + = this->get_collider_type(this_data.collider, other_data.collider); + std::pair<vec2, CollisionSystem::Direction> resolution_data + = this->collision_handler(this_data, other_data, type); + ComponentManager & mgr = this->mediator.component_manager; + OptionalRef<Metadata> this_metadata + = mgr.get_components_by_id<Metadata>(this_data.id).front().get(); + OptionalRef<Metadata> other_metadata + = mgr.get_components_by_id<Metadata>(other_data.id).front().get(); + OptionalRef<Collider> this_collider; + OptionalRef<Collider> other_collider; + switch (type) { + case CollisionInternalType::BOX_BOX: { + this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); + other_collider + = std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); + break; + } + case CollisionInternalType::BOX_CIRCLE: { + this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); + other_collider + = std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); + break; + } + case CollisionInternalType::CIRCLE_BOX: { + this_collider + = std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); + other_collider + = std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); + break; + } + case CollisionInternalType::CIRCLE_CIRCLE: { + this_collider + = std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); + other_collider + = std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); + break; + } + } + + // collision info + crepe::CollisionSystem::CollisionInfo collision_info{ + .this_collider = this_collider, + .this_transform = this_data.transform, + .this_rigidbody = this_data.rigidbody, + .this_metadata = this_metadata, + .other_collider = other_collider, + .other_transform = other_data.transform, + .other_rigidbody = other_data.rigidbody, + .other_metadata = other_metadata, + .resolution = resolution_data.first, + .resolution_direction = resolution_data.second, + }; + + // Determine if static needs to be called + this->determine_collision_handler(collision_info); +} + +std::pair<vec2, CollisionSystem::Direction> +CollisionSystem::collision_handler(CollisionInternal & data1, CollisionInternal & data2, + CollisionInternalType type) { + vec2 resolution; + switch (type) { + case CollisionInternalType::BOX_BOX: { + const BoxCollider & collider1 + = std::get<std::reference_wrapper<BoxCollider>>(data1.collider); + const BoxCollider & collider2 + = std::get<std::reference_wrapper<BoxCollider>>(data2.collider); + vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, + data1.rigidbody); + vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, + data2.rigidbody); + resolution = this->get_box_box_resolution(collider1, collider2, collider_pos1, + collider_pos2); + break; + } + case CollisionInternalType::BOX_CIRCLE: { + const BoxCollider & collider1 + = std::get<std::reference_wrapper<BoxCollider>>(data1.collider); + const CircleCollider & collider2 + = std::get<std::reference_wrapper<CircleCollider>>(data2.collider); + vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, + data1.rigidbody); + vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, + data2.rigidbody); + resolution = -this->get_circle_box_resolution(collider2, collider1, collider_pos2, + collider_pos1); + break; + } + case CollisionInternalType::CIRCLE_CIRCLE: { + const CircleCollider & collider1 + = std::get<std::reference_wrapper<CircleCollider>>(data1.collider); + const CircleCollider & collider2 + = std::get<std::reference_wrapper<CircleCollider>>(data2.collider); + vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, + data1.rigidbody); + vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, + data2.rigidbody); + resolution = this->get_circle_circle_resolution(collider1, collider2, + collider_pos1, collider_pos2); + break; + } + case CollisionInternalType::CIRCLE_BOX: { + const CircleCollider & collider1 + = std::get<std::reference_wrapper<CircleCollider>>(data1.collider); + const BoxCollider & collider2 + = std::get<std::reference_wrapper<BoxCollider>>(data2.collider); + vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, + data1.rigidbody); + vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, + data2.rigidbody); + resolution = this->get_circle_box_resolution(collider1, collider2, collider_pos1, + collider_pos2); + break; + } + } + + Direction resolution_direction = Direction::NONE; + if (resolution.x != 0 && resolution.y != 0) { + resolution_direction = Direction::BOTH; + } else if (resolution.x != 0) { + resolution_direction = Direction::X_DIRECTION; + if (data1.rigidbody.data.linear_velocity.y != 0) + resolution.y = data1.rigidbody.data.linear_velocity.y + * (resolution.x / data1.rigidbody.data.linear_velocity.x); + } else if (resolution.y != 0) { + resolution_direction = Direction::Y_DIRECTION; + if (data1.rigidbody.data.linear_velocity.x != 0) + resolution.x = data1.rigidbody.data.linear_velocity.x + * (resolution.y / data1.rigidbody.data.linear_velocity.y); + } + + return std::make_pair(resolution, resolution_direction); +} + +vec2 CollisionSystem::get_box_box_resolution(const BoxCollider & box_collider1, + const BoxCollider & box_collider2, + const vec2 & final_position1, + const vec2 & final_position2) const { + vec2 resolution; // Default resolution vector + vec2 delta = final_position2 - final_position1; + + // Compute half-dimensions of the boxes + float half_width1 = box_collider1.dimensions.x / 2.0; + float half_height1 = box_collider1.dimensions.y / 2.0; + float half_width2 = box_collider2.dimensions.x / 2.0; + float half_height2 = box_collider2.dimensions.y / 2.0; + + // Calculate overlaps along X and Y axes + float overlap_x = (half_width1 + half_width2) - std::abs(delta.x); + float overlap_y = (half_height1 + half_height2) - std::abs(delta.y); + + // Check if there is a collision should always be true + if (overlap_x > 0 && overlap_y > 0) { + // Determine the direction of resolution + if (overlap_x < overlap_y) { + // Resolve along the X-axis (smallest overlap) + resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; + } else if (overlap_y < overlap_x) { + // Resolve along the Y-axis (smallest overlap) + resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; + } else { + // Equal overlap, resolve both directions with preference + resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; + resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; + } + } + + return resolution; +} + +vec2 CollisionSystem::get_circle_circle_resolution(const CircleCollider & circle_collider1, + const CircleCollider & circle_collider2, + const vec2 & final_position1, + const vec2 & final_position2) const { + vec2 delta = final_position2 - final_position1; + + // Compute the distance between the two circle centers + float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); + + // Compute the combined radii of the two circles + float combined_radius = circle_collider1.radius + circle_collider2.radius; + + // Compute the penetration depth + float penetration_depth = combined_radius - distance; + + // Normalize the delta vector to get the collision direction + vec2 collision_normal = delta / distance; + + // Compute the resolution vector + vec2 resolution = -collision_normal * penetration_depth; + + return resolution; +} + +vec2 CollisionSystem::get_circle_box_resolution(const CircleCollider & circle_collider, + const BoxCollider & box_collider, + const vec2 & circle_position, + const vec2 & box_position) const { + vec2 delta = circle_position - box_position; + + // Compute half-dimensions of the box + float half_width = box_collider.dimensions.x / 2.0f; + float half_height = box_collider.dimensions.y / 2.0f; + + // Clamp circle center to the nearest point on the box + vec2 closest_point; + closest_point.x = std::clamp(delta.x, -half_width, half_width); + closest_point.y = std::clamp(delta.y, -half_height, half_height); + + // Find the vector from the circle center to the closest point + vec2 closest_delta = delta - closest_point; + + // Normalize the delta to get the collision direction + float distance + = std::sqrt(closest_delta.x * closest_delta.x + closest_delta.y * closest_delta.y); + vec2 collision_normal = closest_delta / distance; + + // Compute penetration depth + float penetration_depth = circle_collider.radius - distance; + + // Compute the resolution vector + vec2 resolution = collision_normal * penetration_depth; + + return resolution; +} + +void CollisionSystem::determine_collision_handler(CollisionInfo & info) { + // Check rigidbody type for static + if (info.this_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) return; + // If second body is static perform the static collision handler in this system + if (info.other_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) { + this->static_collision_handler(info); + }; + // Call collision event for user + CollisionEvent data(info); + EventManager & emgr = this->mediator.event_manager; + emgr.trigger_event<CollisionEvent>(data, info.this_collider.game_object_id); +} + +void CollisionSystem::static_collision_handler(CollisionInfo & info) { + // Move object back using calculate move back value + info.this_transform.position += info.resolution; + + // If bounce is enabled mirror velocity + if (info.this_rigidbody.data.elastisity_coefficient > 0) { + if (info.resolution_direction == Direction::BOTH) { + info.this_rigidbody.data.linear_velocity.y + = -info.this_rigidbody.data.linear_velocity.y + * info.this_rigidbody.data.elastisity_coefficient; + info.this_rigidbody.data.linear_velocity.x + = -info.this_rigidbody.data.linear_velocity.x + * info.this_rigidbody.data.elastisity_coefficient; + } else if (info.resolution_direction == Direction::Y_DIRECTION) { + info.this_rigidbody.data.linear_velocity.y + = -info.this_rigidbody.data.linear_velocity.y + * info.this_rigidbody.data.elastisity_coefficient; + } else if (info.resolution_direction == Direction::X_DIRECTION) { + info.this_rigidbody.data.linear_velocity.x + = -info.this_rigidbody.data.linear_velocity.x + * info.this_rigidbody.data.elastisity_coefficient; + } + } + // Stop movement if bounce is disabled + else { + info.this_rigidbody.data.linear_velocity = {0, 0}; + } +} + +std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>> +CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) { + + // TODO: + // If no colliders skip + // Check if colliders has rigidbody if not skip + + // TODO: + // If amount is higer than lets say 16 for now use quadtree otwerwise skip + // Quadtree code + // Quadtree is placed over the input vector + + // Return data of collided colliders which are variants + std::vector<std::pair<CollisionInternal, CollisionInternal>> collisions_ret; + //using visit to visit the variant to access the active and id. + for (size_t i = 0; i < colliders.size(); ++i) { + for (size_t j = i + 1; j < colliders.size(); ++j) { + if (colliders[i].id == colliders[j].id) continue; + if (!have_common_layer(colliders[i].rigidbody.data.collision_layers, + colliders[j].rigidbody.data.collision_layers)) + continue; + CollisionInternalType type + = get_collider_type(colliders[i].collider, colliders[j].collider); + if (!get_collision( + { + .collider = colliders[i].collider, + .transform = colliders[i].transform, + .rigidbody = colliders[i].rigidbody, + }, + { + .collider = colliders[j].collider, + .transform = colliders[j].transform, + .rigidbody = colliders[j].rigidbody, + }, + type)) + continue; + collisions_ret.emplace_back(colliders[i], colliders[j]); + } + } + + return collisions_ret; +} + +bool CollisionSystem::have_common_layer(const std::set<int> & layers1, + const std::set<int> & layers2) { + + // Check if any number is equal in the layers + for (int num : layers1) { + if (layers2.contains(num)) { + // Common layer found + return true; + break; + } + } + // No common layer found + return false; +} + +CollisionSystem::CollisionInternalType +CollisionSystem::get_collider_type(const collider_variant & collider1, + const collider_variant & collider2) const { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider1)) { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { + return CollisionInternalType::CIRCLE_CIRCLE; + } else { + return CollisionInternalType::CIRCLE_BOX; + } + } else { + if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { + return CollisionInternalType::BOX_CIRCLE; + } else { + return CollisionInternalType::BOX_BOX; + } + } +} + +bool CollisionSystem::get_collision(const CollisionInternal & first_info, + const CollisionInternal & second_info, + CollisionInternalType type) const { + switch (type) { + case CollisionInternalType::BOX_BOX: { + const BoxCollider & box_collider1 + = std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); + const BoxCollider & box_collider2 + = std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); + return this->get_box_box_collision(box_collider1, box_collider2, + first_info.transform, second_info.transform, + second_info.rigidbody, second_info.rigidbody); + } + case CollisionInternalType::BOX_CIRCLE: { + const BoxCollider & box_collider + = std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); + const CircleCollider & circle_collider + = std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); + return this->get_box_circle_collision( + box_collider, circle_collider, first_info.transform, second_info.transform, + second_info.rigidbody, second_info.rigidbody); + } + case CollisionInternalType::CIRCLE_CIRCLE: { + const CircleCollider & circle_collider1 + = std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); + const CircleCollider & circle_collider2 + = std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); + return this->get_circle_circle_collision( + circle_collider1, circle_collider2, first_info.transform, + second_info.transform, second_info.rigidbody, second_info.rigidbody); + } + case CollisionInternalType::CIRCLE_BOX: { + const CircleCollider & circle_collider + = std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); + const BoxCollider & box_collider + = std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); + return this->get_box_circle_collision( + box_collider, circle_collider, first_info.transform, second_info.transform, + second_info.rigidbody, second_info.rigidbody); + } + } + return false; +} + +bool CollisionSystem::get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, + const Transform & transform1, + const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const { + // Get current positions of colliders + vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); + vec2 final_position2 = this->get_current_position(box2.offset, transform2, rigidbody2); + + // Calculate half-extents (half width and half height) + float half_width1 = box1.dimensions.x / 2.0; + float half_height1 = box1.dimensions.y / 2.0; + float half_width2 = box2.dimensions.x / 2.0; + float half_height2 = box2.dimensions.y / 2.0; + + // Check if the boxes overlap along the X and Y axes + return (final_position1.x + half_width1 > final_position2.x - half_width2 + && final_position1.x - half_width1 < final_position2.x + half_width2 + && final_position1.y + half_height1 > final_position2.y - half_height2 + && final_position1.y - half_height1 < final_position2.y + half_height2); +} + +bool CollisionSystem::get_box_circle_collision(const BoxCollider & box1, + const CircleCollider & circle2, + const Transform & transform1, + const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const { + // Get current positions of colliders + vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); + vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2); + + // Calculate box half-extents + float half_width = box1.dimensions.x / 2.0; + float half_height = box1.dimensions.y / 2.0; + + // Find the closest point on the box to the circle's center + float closest_x = std::max(final_position1.x - half_width, + std::min(final_position2.x, final_position1.x + half_width)); + float closest_y = std::max(final_position1.y - half_height, + std::min(final_position2.y, final_position1.y + half_height)); + + // Calculate the distance squared between the circle's center and the closest point on the box + float distance_x = final_position2.x - closest_x; + float distance_y = final_position2.y - closest_y; + float distance_squared = distance_x * distance_x + distance_y * distance_y; + + // Compare distance squared with the square of the circle's radius + return distance_squared < circle2.radius * circle2.radius; +} + +bool CollisionSystem::get_circle_circle_collision(const CircleCollider & circle1, + const CircleCollider & circle2, + const Transform & transform1, + const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const { + // Get current positions of colliders + vec2 final_position1 = this->get_current_position(circle1.offset, transform1, rigidbody1); + vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2); + + float distance_x = final_position1.x - final_position2.x; + float distance_y = final_position1.y - final_position2.y; + float distance_squared = distance_x * distance_x + distance_y * distance_y; + + // Calculate the sum of the radii + float radius_sum = circle1.radius + circle2.radius; + + // Check if the distance between the centers is less than or equal to the sum of the radii + return distance_squared < radius_sum * radius_sum; +} + +vec2 CollisionSystem::get_current_position(const vec2 & collider_offset, + const Transform & transform, + const Rigidbody & rigidbody) const { + // Get the rotation in radians + float radians1 = transform.rotation * (M_PI / 180.0); + + // Calculate total offset with scale + vec2 total_offset = (rigidbody.data.offset + collider_offset) * transform.scale; + + // Rotate + float rotated_total_offset_x1 + = total_offset.x * cos(radians1) - total_offset.y * sin(radians1); + float rotated_total_offset_y1 + = total_offset.x * sin(radians1) + total_offset.y * cos(radians1); + + // Final positions considering scaling and rotation + return (transform.position + vec2(rotated_total_offset_x1, rotated_total_offset_y1)); +} diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h index c1a70d8..7e893c8 100644 --- a/src/crepe/system/CollisionSystem.h +++ b/src/crepe/system/CollisionSystem.h @@ -1,13 +1,312 @@ #pragma once +#include <optional> +#include <variant> +#include <vector> + +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" + +#include "Collider.h" #include "System.h" namespace crepe { +//! A system responsible for detecting and handling collisions between colliders. class CollisionSystem : public System { public: using System::System; + +private: + //! A variant type that can hold either a BoxCollider or a CircleCollider. + using collider_variant = std::variant<std::reference_wrapper<BoxCollider>, + std::reference_wrapper<CircleCollider>>; + + //! Enum representing the types of collider pairs for collision detection. + enum class CollisionInternalType { + BOX_BOX, + CIRCLE_CIRCLE, + BOX_CIRCLE, + CIRCLE_BOX, + }; + + /** + * \brief A structure to store the collision data of a single collider. + * + * This structure all components and id that are for needed within this system when calculating or handeling collisions. + * The transform and rigidbody are mostly needed for location and rotation. + * In rigidbody additional info is written about what the body of the object is, + * and how it should respond on a collision. + */ + struct CollisionInternal { + game_object_id_t id = 0; + collider_variant collider; + Transform & transform; + Rigidbody & rigidbody; + }; + + //! Enum representing movement directions during collision resolution. + enum class Direction { + //! No movement required. + NONE, + //! Movement in the X direction. + X_DIRECTION, + //! Movement in the Y direction. + Y_DIRECTION, + //! Movement in both X and Y directions. + BOTH + }; + +public: + /** + * \brief Structure representing detailed collision information between two colliders. + * + * Includes information about the colliding objects and the resolution data for handling the collision. + */ + struct CollisionInfo { + Collider & this_collider; + Transform & this_transform; + Rigidbody & this_rigidbody; + Metadata & this_metadata; + Collider & other_collider; + Transform & other_transform; + Rigidbody & other_rigidbody; + Metadata & other_metadata; + //! The resolution vector for the collision. + vec2 resolution; + //! The direction of movement for resolving the collision. + Direction resolution_direction = Direction::NONE; + }; + +public: + //! Updates the collision system by checking for collisions between colliders and handling them. void update() override; + +private: + /** + * \brief Determines the type of collider pair from two colliders. + * + * Uses std::holds_alternative to identify the types of the provided colliders. + * + * \param collider1 First collider variant (BoxCollider or CircleCollider). + * \param collider2 Second collider variant (BoxCollider or CircleCollider). + * \return The combined type of the two colliders. + */ + CollisionInternalType get_collider_type(const collider_variant & collider1, + const collider_variant & collider2) const; + + /** + * \brief Calculates the current position of a collider. + * + * Combines the Collider offset, Transform position, and Rigidbody offset to compute the position of the collider. + * + * \param collider_offset The offset of the collider. + * \param transform The Transform of the associated game object. + * \param rigidbody The Rigidbody of the associated game object. + * \return The calculated position of the collider. + */ + vec2 get_current_position(const vec2 & collider_offset, const Transform & transform, + const Rigidbody & rigidbody) const; + +private: + /** + * \brief Handles collision resolution between two colliders. + * + * Processes collision data and adjusts objects to resolve collisions and/or calls the user oncollision script function. + * + * \param data1 Collision data for the first collider. + * \param data2 Collision data for the second collider. + */ + void collision_handler_request(CollisionInternal & data1, CollisionInternal & data2); + + /** + * \brief Resolves collision between two colliders and calculates the movement required. + * + * Determines the displacement and direction needed to separate colliders based on their types. + * + * \param data1 Collision data for the first collider. + * \param data2 Collision data for the second collider. + * \param type The type of collider pair. + * \return A pair containing the resolution vector and direction for the first collider. + */ + std::pair<vec2, Direction> collision_handler(CollisionInternal & data1, + CollisionInternal & data2, + CollisionInternalType type); + + /** + * \brief Calculates the resolution vector for two BoxColliders. + * + * Computes the displacement required to separate two overlapping BoxColliders. + * + * \param box_collider1 The first BoxCollider. + * \param box_collider2 The second BoxCollider. + * \param position1 The position of the first BoxCollider. + * \param position2 The position of the second BoxCollider. + * \return The resolution vector for the collision. + */ + vec2 get_box_box_resolution(const BoxCollider & box_collider1, + const BoxCollider & box_collider2, const vec2 & position1, + const vec2 & position2) const; + + /** + * \brief Calculates the resolution vector for two CircleCollider. + * + * Computes the displacement required to separate two overlapping CircleCollider. + * + * \param circle_collider1 The first CircleCollider. + * \param circle_collider2 The second CircleCollider. + * \param position1 The position of the first CircleCollider. + * \param position2 The position of the second CircleCollider. + * \return The resolution vector for the collision. + */ + vec2 get_circle_circle_resolution(const CircleCollider & circle_collider1, + const CircleCollider & circle_collider2, + const vec2 & final_position1, + const vec2 & final_position2) const; + + /** + * \brief Calculates the resolution vector for two CircleCollider. + * + * Computes the displacement required to separate two overlapping CircleCollider. + * + * \param circle_collider The first CircleCollider. + * \param box_collider The second CircleCollider. + * \param circle_position The position of the CircleCollider. + * \param box_position The position of the BoxCollider. + * \param inverse Inverted true if box circle collision, false if circle box collision (inverts the direction). + * \return The resolution vector for the collision. + */ + vec2 get_circle_box_resolution(const CircleCollider & circle_collider, + const BoxCollider & box_collider, + const vec2 & circle_position, + const vec2 & box_position) const; + + /** + * \brief Determines the appropriate collision handler for a collision. + * + * Decides the correct resolution process based on the dynamic or static nature of the colliders involved. + * + * \param info Collision information containing data about both colliders. + */ + void determine_collision_handler(CollisionInfo & info); + + /** + * \brief Handles collisions involving static objects. + * + * Resolves collisions by adjusting positions and modifying velocities if bounce is enabled. + * + * \param info Collision information containing data about both colliders. + */ + void static_collision_handler(CollisionInfo & info); + +private: + /** + * \brief Checks for collisions between colliders. + * + * Identifies collisions and generates pairs of colliding objects for further processing. + * + * \param colliders A collection of all active colliders. + * \return A list of collision pairs with their associated data. + */ + std::vector<std::pair<CollisionInternal, CollisionInternal>> + gather_collisions(std::vector<CollisionInternal> & colliders); + + /** + * \brief Checks if two collision layers have at least one common layer. + * + * This function checks if there is any overlapping layer between the two inputs. + * It compares each layer from the first input to see + * if it exists in the second input. If at least one common layer is found, + * the function returns true, indicating that the two colliders share a common + * collision layer. + * + * \param layers1 all collision layers for the first collider. + * \param layers2 all collision layers for the second collider. + * \return Returns true if there is at least one common layer, false otherwise. + */ + + bool have_common_layer(const std::set<int> & layers1, const std::set<int> & layers2); + + /** + * \brief Checks for collision between two colliders. + * + * Calls the appropriate collision detection function based on the collider types. + * + * \param first_info Collision data for the first collider. + * \param second_info Collision data for the second collider. + * \param type The type of collider pair. + * \return True if a collision is detected, otherwise false. + */ + bool get_collision(const CollisionInternal & first_info, + const CollisionInternal & second_info, + CollisionInternalType type) const; + + /** + * \brief Detects collisions between two BoxColliders. + * + * \param box1 The first BoxCollider. + * \param box2 The second BoxCollider. + * \param transform1 Transform of the first object. + * \param transform2 Transform of the second object. + * \param rigidbody1 Rigidbody of the first object. + * \param rigidbody2 Rigidbody of the second object. + * \return True if a collision is detected, otherwise false. + */ + bool get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, + const Transform & transform1, const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const; + + /** + * \brief Check collision for box on circle collider + * + * \param box1 The BoxCollider + * \param circle2 The CircleCollider + * \param transform1 Transform of the first object. + * \param transform2 Transform of the second object. + * \param rigidbody1 Rigidbody of the first object. + * \param rigidbody2 Rigidbody of the second object. + * \return True if a collision is detected, otherwise false. + */ + bool get_box_circle_collision(const BoxCollider & box1, const CircleCollider & circle2, + const Transform & transform1, const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const; + + /** + * \brief Check collision for circle on circle collider + * + * \param circle1 First CircleCollider + * \param circle2 Second CircleCollider + * \param transform1 Transform of the first object. + * \param transform2 Transform of the second object. + * \param rigidbody1 Rigidbody of the first object. + * \param rigidbody2 Rigidbody of the second object. + * \return True if a collision is detected, otherwise false. + * + * \return status of collision + */ + bool get_circle_circle_collision(const CircleCollider & circle1, + const CircleCollider & circle2, + const Transform & transform1, + const Transform & transform2, + const Rigidbody & rigidbody1, + const Rigidbody & rigidbody2) const; +}; + +/** + * \brief Event triggered during a collision between objects. + */ +class CollisionEvent : public Event { +public: + crepe::CollisionSystem::CollisionInfo info; + CollisionEvent(const crepe::CollisionSystem::CollisionInfo & collisionInfo) + : info(collisionInfo) {} }; } // namespace crepe diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index bebcf3d..ebf4439 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -25,17 +25,20 @@ void PhysicsSystem::update() { if (transform.game_object_id == rigidbody.game_object_id) { // Add gravity - if (rigidbody.data.use_gravity) { + if (rigidbody.data.gravity_scale > 0) { rigidbody.data.linear_velocity.y += (rigidbody.data.mass * rigidbody.data.gravity_scale * gravity); } // Add damping - if (rigidbody.data.angular_damping != 0) { - rigidbody.data.angular_velocity *= rigidbody.data.angular_damping; + if (rigidbody.data.angular_velocity_coefficient > 0) { + rigidbody.data.angular_velocity + *= rigidbody.data.angular_velocity_coefficient; } - if (rigidbody.data.linear_damping != vec2{0, 0}) { - rigidbody.data.linear_velocity *= rigidbody.data.linear_damping; + if (rigidbody.data.linear_velocity_coefficient.x > 0 + && rigidbody.data.linear_velocity_coefficient.y > 0) { + rigidbody.data.linear_velocity + *= rigidbody.data.linear_velocity_coefficient; } // Max velocity check |