aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xasset/texture/green_square.pngbin0 -> 135 bytes
-rwxr-xr-xasset/texture/red_square.pngbin0 -> 135 bytes
-rw-r--r--src/crepe/Collider.cpp2
-rw-r--r--src/crepe/Collider.h9
-rw-r--r--src/crepe/api/BoxCollider.cpp7
-rw-r--r--src/crepe/api/BoxCollider.h24
-rw-r--r--src/crepe/api/CMakeLists.txt4
-rw-r--r--src/crepe/api/CircleCollider.cpp6
-rw-r--r--src/crepe/api/CircleCollider.h17
-rw-r--r--src/crepe/api/Event.h9
-rw-r--r--src/crepe/api/LoopManager.cpp9
-rw-r--r--src/crepe/api/Rigidbody.h10
-rw-r--r--src/crepe/system/CollisionSystem.cpp390
-rw-r--r--src/crepe/system/CollisionSystem.h233
-rw-r--r--src/example/CMakeLists.txt1
-rw-r--r--src/example/game.cpp92
-rw-r--r--src/test/CMakeLists.txt26
-rw-r--r--src/test/CollisionTest.cpp347
-rw-r--r--src/test/Profiling.cpp230
19 files changed, 1392 insertions, 24 deletions
diff --git a/asset/texture/green_square.png b/asset/texture/green_square.png
new file mode 100755
index 0000000..7772c87
--- /dev/null
+++ b/asset/texture/green_square.png
Binary files differ
diff --git a/asset/texture/red_square.png b/asset/texture/red_square.png
new file mode 100755
index 0000000..6ffbbec
--- /dev/null
+++ b/asset/texture/red_square.png
Binary files differ
diff --git a/src/crepe/Collider.cpp b/src/crepe/Collider.cpp
index bbec488..80a944d 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, vec2 offset) : Component(id), offset(offset) {}
diff --git a/src/crepe/Collider.h b/src/crepe/Collider.h
index 827f83d..5b26af5 100644
--- a/src/crepe/Collider.h
+++ b/src/crepe/Collider.h
@@ -1,14 +1,19 @@
#pragma once
+#include "api/Vector2.h"
+
#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, vec2 offset);
- int size;
+public:
+ //! Offset of the collider relative to rigidbody position
+ vec2 offset;
};
} // namespace crepe
diff --git a/src/crepe/api/BoxCollider.cpp b/src/crepe/api/BoxCollider.cpp
new file mode 100644
index 0000000..4c767c8
--- /dev/null
+++ b/src/crepe/api/BoxCollider.cpp
@@ -0,0 +1,7 @@
+#include "BoxCollider.h"
+
+#include "../Collider.h"
+
+using namespace crepe;
+
+BoxCollider::BoxCollider(game_object_id_t game_object_id,vec2 offset, double width, double height) : Collider(game_object_id,offset), width(width), height(height) {}
diff --git a/src/crepe/api/BoxCollider.h b/src/crepe/api/BoxCollider.h
new file mode 100644
index 0000000..6135954
--- /dev/null
+++ b/src/crepe/api/BoxCollider.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "Vector2.h"
+#include "../Collider.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,vec2 offset, double width, double height);
+
+ //! Width of box collider
+ double width;
+
+ //! Height of box collider
+ double height;
+};
+
+} // namespace crepe
diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt
index 50c51ed..d8215b9 100644
--- a/src/crepe/api/CMakeLists.txt
+++ b/src/crepe/api/CMakeLists.txt
@@ -15,6 +15,8 @@ target_sources(crepe PUBLIC
SceneManager.cpp
Camera.cpp
Animator.cpp
+ BoxCollider.cpp
+ CircleCollider.cpp
EventManager.cpp
IKeyListener.cpp
IMouseListener.cpp
@@ -48,6 +50,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES
SceneManager.hpp
Camera.h
Animator.h
+ BoxCollider.h
+ CircleCollider.h
EventManager.h
EventManager.hpp
EventHandler.h
diff --git a/src/crepe/api/CircleCollider.cpp b/src/crepe/api/CircleCollider.cpp
new file mode 100644
index 0000000..43de991
--- /dev/null
+++ b/src/crepe/api/CircleCollider.cpp
@@ -0,0 +1,6 @@
+#include "CircleCollider.h"
+
+using namespace crepe;
+
+
+CircleCollider::CircleCollider(game_object_id_t game_object_id,vec2 offset, int radius) : Collider(game_object_id,offset), radius(radius) {}
diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h
index e77a592..843547f 100644
--- a/src/crepe/api/CircleCollider.h
+++ b/src/crepe/api/CircleCollider.h
@@ -1,14 +1,23 @@
#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,vec2 offset, int radius);
+
+ //! Radius of the circle collider.
+ double radius;
};
} // namespace crepe
diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h
index b267e3e..259acba 100644
--- a/src/crepe/api/Event.h
+++ b/src/crepe/api/Event.h
@@ -3,6 +3,8 @@
#include <string>
+#include "system/CollisionSystem.h"
+
#include "KeyCodes.h"
namespace crepe {
@@ -93,7 +95,12 @@ public:
/**
* \brief Event triggered during a collision between objects.
*/
-class CollisionEvent : public Event {};
+class CollisionEvent : public Event {
+public:
+ crepe::CollisionSystem::CollisionInfo info;
+ CollisionEvent(const crepe::CollisionSystem::CollisionInfo& collisionInfo)
+ : info(collisionInfo) {}
+};
/**
* \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 7edf4d1..feb1338 100644
--- a/src/crepe/api/LoopManager.cpp
+++ b/src/crepe/api/LoopManager.cpp
@@ -6,6 +6,8 @@
#include "../system/PhysicsSystem.h"
#include "../system/RenderSystem.h"
#include "../system/ScriptSystem.h"
+#include "..//system/PhysicsSystem.h"
+#include "../system/CollisionSystem.h"
#include "LoopManager.h"
#include "LoopTimer.h"
@@ -32,7 +34,11 @@ void LoopManager::start() {
}
void LoopManager::set_running(bool running) { this->game_running = running; }
-void LoopManager::fixed_update() {}
+void LoopManager::fixed_update() {
+ this->get_system<ScriptSystem>().update();
+ this->get_system<PhysicsSystem>().update();
+ this->get_system<CollisionSystem>().update();
+}
void LoopManager::loop() {
LoopTimer & timer = LoopTimer::get_instance();
@@ -58,6 +64,7 @@ void LoopManager::setup() {
this->game_running = true;
LoopTimer::get_instance().start();
LoopTimer::get_instance().set_fps(200);
+ this->scene_manager.load_next_scene();
}
void LoopManager::render() {
diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h
index 3b0588f..7939563 100644
--- a/src/crepe/api/Rigidbody.h
+++ b/src/crepe/api/Rigidbody.h
@@ -1,5 +1,7 @@
#pragma once
+#include <cmath>
+
#include "../Component.h"
#include "types.h"
@@ -58,13 +60,13 @@ public:
//! linear velocity of object
vec2 linear_velocity;
//! maximum linear velocity of object
- vec2 max_linear_velocity;
+ vec2 max_linear_velocity = {INFINITY ,INFINITY};
//! 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;
+ double max_angular_velocity = INFINITY;
//! angular damping of object
double angular_damping = 0.0;
//! movements constraints of object
@@ -73,6 +75,10 @@ public:
bool use_gravity = true;
//! if object bounces
bool bounce = false;
+ //! bounce factor of material
+ double elastisity = 0.0;
+ //! offset of all colliders relative to transform position
+ vec2 offset;
};
public:
diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp
index c74ca1d..e0c6d03 100644
--- a/src/crepe/system/CollisionSystem.cpp
+++ b/src/crepe/system/CollisionSystem.cpp
@@ -1,5 +1,393 @@
+#include <cmath>
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <utility>
+#include <variant>
+#include <optional>
+
+#include "api/Event.h"
+#include "api/EventManager.h"
+#include "api/BoxCollider.h"
+#include "api/CircleCollider.h"
+#include "api/Vector2.h"
+#include "api/Rigidbody.h"
+#include "api/Transform.h"
+
+#include "ComponentManager.h"
#include "CollisionSystem.h"
+#include "Collider.h"
+#include "types.h"
+#include "util/OptionalRef.h"
using namespace crepe;
-void CollisionSystem::update() {}
+void CollisionSystem::update() {
+ // Get collider components and keep them seperate
+ ComponentManager & mgr = this->component_manager;
+ std::vector<std::reference_wrapper<BoxCollider>> boxcolliders = mgr.get_components_by_type<BoxCollider>();
+ std::vector<std::reference_wrapper<CircleCollider>> circlecolliders = mgr.get_components_by_type<CircleCollider>();
+
+ std::vector<collider_variant> all_colliders;
+ // Add BoxCollider references
+ for (auto& box : boxcolliders) {
+ all_colliders.push_back(collider_variant{box});
+ }
+
+ // Add CircleCollider references
+ for (auto& circle : circlecolliders) {
+ all_colliders.push_back(collider_variant{circle});
+ }
+
+ // Check between all colliders if there is a collision
+ std::vector<std::pair<CollisionInternal,CollisionInternal>> collided = check_collisions(all_colliders);
+
+ // For both objects call the collision handler
+ for (auto& collision_pair : collided) {
+ collision_handler_request(collision_pair.first,collision_pair.second);
+ collision_handler_request(collision_pair.second,collision_pair.first);
+ }
+}
+
+void CollisionSystem::collision_handler_request(CollisionInternal& data1,CollisionInternal& data2){
+
+ CollisionInternalType type = check_collider_type(data1.collider,data2.collider);
+ std::pair<vec2,CollisionSystem::Direction> resolution_data = collision_handler(data1,data2,type);
+
+ OptionalRef<Collider> collider1;
+ OptionalRef<Collider> collider2;
+ switch (type) {
+ case CollisionInternalType::BOX_BOX:{
+ collider1 = std::get<std::reference_wrapper<BoxCollider>>(data1.collider);
+ collider2 = std::get<std::reference_wrapper<BoxCollider>>(data2.collider);
+ break;
+ }
+ case CollisionInternalType::BOX_CIRCLE:{
+ collider1 = std::get<std::reference_wrapper<BoxCollider>>(data1.collider);
+ collider2 = std::get<std::reference_wrapper<CircleCollider>>(data2.collider);
+ break;
+ }
+ case CollisionInternalType::CIRCLE_BOX:{
+ collider1 = std::get<std::reference_wrapper<CircleCollider>>(data1.collider);
+ collider2 = std::get<std::reference_wrapper<BoxCollider>>(data2.collider);
+ break;
+ }
+ case CollisionInternalType::CIRCLE_CIRCLE:{
+ collider1 = std::get<std::reference_wrapper<CircleCollider>>(data1.collider);
+ collider2 = std::get<std::reference_wrapper<CircleCollider>>(data2.collider);
+ break;
+ }
+ }
+
+ // collision info
+ crepe::CollisionSystem::CollisionInfo collision_info{
+ .first_collider = collider1,
+ .first_transform = data1.transform,
+ .first_rigidbody = data1.rigidbody,
+ .second_collider = collider2,
+ .second_transform = data2.transform,
+ .second_rigidbody = data2.rigidbody,
+ .resolution = resolution_data.first,
+ .resolution_direction = resolution_data.second,
+ };
+
+ // Determine if static needs to be called
+ determine_collision_handler(collision_info);
+}
+
+
+std::pair<vec2,CollisionSystem::Direction> CollisionSystem::collision_handler(CollisionInternal& data1,CollisionInternal& data2,CollisionInternalType type) {
+ vec2 move_back;
+ 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 = current_position(collider1.offset, data1.transform, data1.rigidbody);
+ vec2 collider_pos2 = current_position(collider2.offset, data2.transform, data2.rigidbody);
+ move_back = box_box_resolution(collider1,collider2,collider_pos1,collider_pos2);
+ }
+ case CollisionInternalType::BOX_CIRCLE: {
+
+ }
+ case CollisionInternalType::CIRCLE_CIRCLE: {
+
+ }
+ case CollisionInternalType::CIRCLE_BOX: {
+
+ }
+ }
+
+ Direction move_back_direction = Direction::NONE;
+ if(move_back.x != 0 && move_back.y > 0) {
+ move_back_direction = Direction::BOTH;
+ } else if (move_back.x != 0) {
+ move_back_direction = Direction::X_DIRECTION;
+ if(data1.rigidbody.data.linear_velocity.y != 0)
+ move_back.y = data1.rigidbody.data.linear_velocity.y * (move_back.x/data1.rigidbody.data.linear_velocity.x);
+ } else if (move_back.y != 0) {
+ move_back_direction = Direction::Y_DIRECTION;
+ if(data1.rigidbody.data.linear_velocity.x != 0)
+ move_back.x = data1.rigidbody.data.linear_velocity.x * (move_back.y/data1.rigidbody.data.linear_velocity.y);
+ }
+
+ return {move_back,move_back_direction};
+}
+
+vec2 CollisionSystem::box_box_resolution(const BoxCollider& box_collider1,const BoxCollider& box_collider2,vec2 final_position1,vec2 final_position2)
+{
+ vec2 resolution; // Default resolution vector
+ vec2 delta = final_position2 - final_position1;
+
+ // Compute half-dimensions of the boxes
+ float half_width1 = box_collider1.width / 2.0;
+ float half_height1 = box_collider1.height / 2.0;
+ float half_width2 = box_collider2.width / 2.0;
+ float half_height2 = box_collider2.height / 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
+ if (overlap_x > 0 && overlap_y > 0) {//should always be true check if this can be removed
+ // 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;
+}
+
+void CollisionSystem::determine_collision_handler(CollisionInfo& info){
+ // Check rigidbody type for static
+ if(info.first_rigidbody.data.body_type != Rigidbody::BodyType::STATIC)
+ {
+ // If second body is static perform the static collision handler in this system
+ if(info.second_rigidbody.data.body_type == Rigidbody::BodyType::STATIC){
+ static_collision_handler(info);
+ };
+ // Call collision event for user
+ CollisionEvent data(info);
+ EventManager::get_instance().trigger_event<CollisionEvent>(data, info.first_collider.game_object_id);
+ }
+}
+
+void CollisionSystem::static_collision_handler(CollisionInfo& info){
+ // Move object back using calculate move back value
+ info.first_transform.position += info.resolution;
+
+ // If bounce is enabled mirror velocity
+ if(info.first_rigidbody.data.bounce) {
+ if(info.resolution_direction == Direction::BOTH)
+ {
+ info.first_rigidbody.data.linear_velocity.y = -info.first_rigidbody.data.linear_velocity.y * info.first_rigidbody.data.elastisity;
+ info.first_rigidbody.data.linear_velocity.x = -info.first_rigidbody.data.linear_velocity.x * info.first_rigidbody.data.elastisity;
+ }
+ else if(info.resolution_direction == Direction::Y_DIRECTION) {
+ info.first_rigidbody.data.linear_velocity.y = -info.first_rigidbody.data.linear_velocity.y * info.first_rigidbody.data.elastisity;
+ }
+ else if(info.resolution_direction == Direction::X_DIRECTION){
+ info.first_rigidbody.data.linear_velocity.x = -info.first_rigidbody.data.linear_velocity.x * info.first_rigidbody.data.elastisity;
+ }
+ }
+ // Stop movement if bounce is disabled
+ else {
+ info.first_rigidbody.data.linear_velocity = {0,0};
+ }
+}
+
+std::vector<std::pair<CollisionSystem::CollisionInternal,CollisionSystem::CollisionInternal>> CollisionSystem::check_collisions(std::vector<collider_variant> & 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
+
+ std::vector<std::pair<CollisionInternal,CollisionInternal>> collisions_ret;
+ for (size_t i = 0; i < colliders.size(); ++i) {
+ std::visit([&](auto& inner_collider_ref) {
+ if (!inner_collider_ref.get().active) return;
+ auto inner_components = get_active_transform_and_rigidbody(inner_collider_ref.get().game_object_id);
+ if (!inner_components) return;
+ for (size_t j = i + 1; j < colliders.size(); ++j) {
+ std::visit([&](auto& outer_collider_ref) {
+ if (!outer_collider_ref.get().active) return;
+ if (inner_collider_ref.get().game_object_id == outer_collider_ref.get().game_object_id) return;
+ auto outer_components = get_active_transform_and_rigidbody(outer_collider_ref.get().game_object_id);
+ if (!outer_components) return;
+ CollisionInternalType type = check_collider_type(colliders[i],colliders[j]);
+ if(!check_collision({
+ .collider = colliders[i],
+ .transform = inner_components->first,
+ .rigidbody = inner_components->second,
+ },
+ {
+ .collider = colliders[j],
+ .transform = outer_components->first,
+ .rigidbody = outer_components->second,
+ },
+ type)) return;
+ collisions_ret.emplace_back(
+ CollisionInternal{colliders[i], inner_components->first.get(), inner_components->second.get()},
+ CollisionInternal{colliders[j], outer_components->first.get(), outer_components->second.get()}
+ );
+ }, colliders[j]);
+ }
+ }, colliders[i]);
+ }
+
+ return collisions_ret;
+}
+
+std::optional<std::pair<std::reference_wrapper<Transform>, std::reference_wrapper<Rigidbody>>>
+CollisionSystem::get_active_transform_and_rigidbody(game_object_id_t game_object_id) {
+ RefVector<Transform> transforms = this->component_manager.get_components_by_id<Transform>(game_object_id);
+ if (transforms.empty()) return std::nullopt;
+
+ RefVector<Rigidbody> rigidbodies = this->component_manager.get_components_by_id<Rigidbody>(game_object_id);
+ if (rigidbodies.empty()) return std::nullopt;
+
+ Transform& transform = transforms.front().get();
+ if (!transform.active) return std::nullopt;
+
+ Rigidbody& rigidbody = rigidbodies.front().get();
+ if (!rigidbody.active) return std::nullopt;
+
+ // Return the active components
+ return std::make_pair(std::ref(transform), std::ref(rigidbody));
+}
+
+CollisionSystem::CollisionInternalType CollisionSystem::check_collider_type(const collider_variant& collider1,const collider_variant& collider2){
+ 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::check_collision(const CollisionInternal& first_info,const CollisionInternal& second_info, CollisionInternalType type){
+ 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 check_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 check_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 check_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 check_box_circle_collision(box_collider,circle_collider,first_info.transform,second_info.transform,second_info.rigidbody,second_info.rigidbody);
+ }
+ }
+ return false;
+}
+
+
+bool CollisionSystem::check_box_box_collision(const BoxCollider& box1, const BoxCollider& box2, const Transform& transform1, const Transform& transform2, const Rigidbody& rigidbody1, const Rigidbody& rigidbody2)
+{
+ // Get current positions of colliders
+ vec2 final_position1 = current_position(box1.offset,transform1,rigidbody1);
+ vec2 final_position2 = current_position(box2.offset,transform2,rigidbody2);
+
+ // Calculate half-extents (half width and half height)
+ float half_width1 = box1.width / 2.0;
+ float half_height1 = box1.height / 2.0;
+ float half_width2 = box2.width / 2.0;
+ float half_height2 = box2.height / 2.0;
+
+ // Check if the boxes overlap along the X and Y axes
+ return (final_position1.x + half_width1 > final_position2.x - half_width2 && // not left
+ final_position1.x - half_width1 < final_position2.x + half_width2 && // not right
+ final_position1.y + half_height1 > final_position2.y - half_height2 && // not above
+ final_position1.y - half_height1 < final_position2.y + half_height2); // not below
+}
+
+bool CollisionSystem::check_box_circle_collision(const BoxCollider& box1, const CircleCollider& circle2, const Transform& transform1, const Transform& transform2, const Rigidbody& rigidbody1, const Rigidbody& rigidbody2) {
+ // Get current positions of colliders
+ vec2 final_position1 = current_position(box1.offset, transform1, rigidbody1);
+ vec2 final_position2 = current_position(circle2.offset, transform2, rigidbody2);
+
+ // Calculate box half-extents
+ float half_width = box1.width / 2.0;
+ float half_height = box1.height / 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::check_circle_circle_collision(const CircleCollider& circle1, const CircleCollider& circle2, const Transform& transform1, const Transform& transform2, const Rigidbody& rigidbody1, const Rigidbody& rigidbody2) {
+ // Get current positions of colliders
+ vec2 final_position1 = current_position(circle1.offset,transform1,rigidbody1);
+ vec2 final_position2 = 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::current_position(vec2 collider_offset, const Transform& transform, const Rigidbody& rigidbody) {
+ // 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..ea8c1e1 100644
--- a/src/crepe/system/CollisionSystem.h
+++ b/src/crepe/system/CollisionSystem.h
@@ -1,13 +1,246 @@
#pragma once
+#include <vector>
+#include <variant>
+#include <optional>
+
+#include "api/Rigidbody.h"
+#include "api/Transform.h"
+#include "api/BoxCollider.h"
+#include "api/CircleCollider.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 stores the collider type, its associated transform, and its rigidbody.
+ */
+ struct CollisionInternal {
+ //! Store either BoxCollider or CircleCollider
+ 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& first_collider;
+ Transform& first_transform;
+ Rigidbody& first_rigidbody;
+ Collider& second_collider;
+ Transform& second_transform;
+ Rigidbody& second_rigidbody;
+ //! 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 check_collider_type(const collider_variant& collider1,const collider_variant& collider2);
+
+ /**
+ * \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 current_position(vec2 collider_offset, const Transform& transform, const Rigidbody& rigidbody);
+
+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 first BoxCollider.
+ */
+ vec2 box_box_resolution(const BoxCollider& box_collider1,const BoxCollider& box_collider2,vec2 position1,vec2 position2);
+
+ /**
+ * \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>> check_collisions(std::vector<collider_variant> & colliders);
+
+ /**
+ * \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 check_collision(const CollisionInternal& first_info,const CollisionInternal& second_info, CollisionInternalType type);
+
+ /**
+ * \brief Retrieves the active Transform and Rigidbody components for a given game object.
+ *
+ * This function looks up the Transform and Rigidbody components associated with the specified
+ * game object ID. It checks if both components are present and active. If both are found
+ * to be active, they are returned wrapped in reference wrappers; otherwise, an empty
+ * optional is returned.
+ *
+ * \param game_object_id The ID of the game object for which to retrieve the components.
+ *
+ * \return A std::optional containing a pair of reference wrappers to the active Transform
+ * and Rigidbody components, or std::nullopt if either component is not found
+ * or not active.
+ */
+ std::optional<std::pair<std::reference_wrapper<Transform>, std::reference_wrapper<Rigidbody>>> get_active_transform_and_rigidbody(game_object_id_t game_object_id);
+
+
+ /**
+ * \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 check_box_box_collision(const BoxCollider& box1, const BoxCollider& box2, const Transform& transform1, const Transform& transform2, const Rigidbody& rigidbody1, const Rigidbody& rigidbody2);
+
+ /**
+ * \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 check_box_circle_collision(const BoxCollider& box1, const CircleCollider& circle2, const Transform& transform1, const Transform& transform2, const Rigidbody& rigidbody1, const Rigidbody& rigidbody2);
+
+ /**
+ * \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 check_circle_circle_collision(const CircleCollider& circle1, const CircleCollider& circle2, const Transform& transform1, const Transform& transform2, const Rigidbody& rigidbody1, const Rigidbody& rigidbody2);
};
} // namespace crepe
diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt
index 560e2bc..3ec5e43 100644
--- a/src/example/CMakeLists.txt
+++ b/src/example/CMakeLists.txt
@@ -20,4 +20,5 @@ add_example(asset_manager)
add_example(savemgr)
add_example(rendering_particle)
add_example(gameloop)
+add_example(game)
diff --git a/src/example/game.cpp b/src/example/game.cpp
new file mode 100644
index 0000000..e851526
--- /dev/null
+++ b/src/example/game.cpp
@@ -0,0 +1,92 @@
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Transform.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/BoxCollider.h>
+#include <crepe/api/Camera.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Color.h>
+#include <crepe/api/Sprite.h>
+#include <crepe/api/Texture.h>
+#include <crepe/api/Vector2.h>
+#include <crepe/api/Event.h>
+#include <crepe/api/EventManager.h>
+#include <crepe/api/LoopManager.h>
+
+using namespace crepe;
+
+using namespace std;
+
+class MyScript : public Script {
+ bool oncollision(const CollisionEvent& test) {
+ Log::logf("Box {} script on_collision()", test.info.first.collider.game_object_id);
+ return true;
+ }
+ void init() {
+ subscribe<CollisionEvent>([this](const CollisionEvent& ev) -> bool {
+ return this->oncollision(ev);
+ });
+ }
+ void update() {
+ // Retrieve component from the same GameObject this script is on
+ }
+};
+
+
+class ConcreteScene1 : public Scene {
+public:
+ using Scene::Scene;
+
+ void load_scene() {
+ ComponentManager & mgr = this->component_manager;
+ Color color(0, 0, 0, 0);
+
+ float screen_size_width = 640;
+ float screen_size_height = 480;
+ float world_collider = 1000;
+ //define playable world
+ GameObject world = mgr.new_object("Name", "Tag", vec2{screen_size_width/2, screen_size_height/2}, 0, 1);
+ world.add_component<Rigidbody>(Rigidbody::Data{
+ .mass = 0,
+ .gravity_scale = 0,
+ .body_type = Rigidbody::BodyType::STATIC,
+ .constraints = {0, 0, 0},
+ .use_gravity = false,
+ .bounce = false,
+ .offset = {0,0}
+ });
+ world.add_component<BoxCollider>(vec2{0, 0-(screen_size_height/2+world_collider/2)}, world_collider, world_collider);; // Top
+ world.add_component<BoxCollider>(vec2{0, screen_size_height/2+world_collider/2}, world_collider, world_collider); // Bottom
+ world.add_component<BoxCollider>(vec2{0-(screen_size_width/2+world_collider/2), 0}, world_collider, world_collider); // Left
+ world.add_component<BoxCollider>(vec2{screen_size_width/2+world_collider/2, 0}, world_collider, world_collider); // right
+
+
+ GameObject game_object1 = mgr.new_object("Name", "Tag", vec2{screen_size_width/2, screen_size_height/2}, 0, 1);
+ game_object1.add_component<Rigidbody>(Rigidbody::Data{
+ .mass = 1,
+ .gravity_scale = 0.01,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = {1,1},
+ .constraints = {0, 0, 0},
+ .use_gravity = true,
+ .bounce = true,
+ .elastisity = 1,
+ .offset = {0,0},
+ });
+ game_object1.add_component<BoxCollider>(vec2{0, 0}, 20, 20);
+ game_object1.add_component<BehaviorScript>().set_script<MyScript>();
+ game_object1.add_component<Sprite>(
+ make_shared<Texture>("/home/jaro/crepe/asset/texture/green_square.png"), color,
+ FlipSettings{true, true});
+ game_object1.add_component<Camera>(Color::WHITE);
+ }
+
+ string get_name() const { return "scene1"; }
+};
+
+int main(int argc, char * argv[]) {
+
+ LoopManager gameloop;
+ gameloop.add_scene<ConcreteScene1>();
+ gameloop.start();
+ return 0;
+}
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index d310f6a..616e238 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -1,15 +1,17 @@
target_sources(test_main PUBLIC
+ CollisionTest.cpp
main.cpp
- PhysicsTest.cpp
- ScriptTest.cpp
- ParticleTest.cpp
- AssetTest.cpp
- OptionalRefTest.cpp
- RenderSystemTest.cpp
- EventTest.cpp
- ECSTest.cpp
- SceneManagerTest.cpp
- ValueBrokerTest.cpp
- DBTest.cpp
- Vector2Test.cpp
+ # PhysicsTest.cpp
+ # ScriptTest.cpp
+ # ParticleTest.cpp
+ # AssetTest.cpp
+ # OptionalRefTest.cpp
+ # RenderSystemTest.cpp
+ # EventTest.cpp
+ # ECSTest.cpp
+ # SceneManagerTest.cpp
+ # ValueBrokerTest.cpp
+ # DBTest.cpp
+ # Vector2Test.cpp
+ # Profiling.cpp
)
diff --git a/src/test/CollisionTest.cpp b/src/test/CollisionTest.cpp
new file mode 100644
index 0000000..8daf77f
--- /dev/null
+++ b/src/test/CollisionTest.cpp
@@ -0,0 +1,347 @@
+#include <cmath>
+#include <cstddef>
+#include <gtest/gtest.h>
+
+#define private public
+#define protected public
+
+#include <crepe/ComponentManager.h>
+#include <crepe/api/Event.h>
+#include <crepe/api/EventManager.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Transform.h>
+#include <crepe/system/CollisionSystem.h>
+#include <crepe/system/ScriptSystem.h>
+#include <crepe/types.h>
+#include <crepe/util/Log.h>
+
+using namespace std;
+using namespace std::chrono_literals;
+using namespace crepe;
+using namespace testing;
+
+class CollisionHandler : public Script {
+public:
+ int box_id;
+ function<void(const CollisionEvent& ev)> test_fn = [](const CollisionEvent & ev) { };
+
+ CollisionHandler(int box_id) {
+ this->box_id = box_id;
+ }
+
+ bool on_collision(const CollisionEvent& ev) {
+ //Log::logf("Box {} script on_collision()", box_id);
+ test_fn(ev);
+ return true;
+ }
+
+ void init() {
+ subscribe<CollisionEvent>([this](const CollisionEvent& ev) -> bool {
+ return this->on_collision(ev);
+ });
+ }
+ void update() {
+ // Retrieve component from the same GameObject this script is on
+ }
+};
+
+class CollisionTest : public Test {
+public:
+ ComponentManager mgr;
+ CollisionSystem collision_sys{mgr};
+ ScriptSystem script_sys{mgr};
+
+ GameObject world = mgr.new_object("world","",{50,50});
+ GameObject game_object1 = mgr.new_object("object1", "", { 50, 50});
+ GameObject game_object2 = mgr.new_object("object2", "", { 50, 30});
+
+ CollisionHandler * script_object1_ref = nullptr;
+ CollisionHandler * script_object2_ref = nullptr;
+
+ void SetUp() override {
+ world.add_component<Rigidbody>(Rigidbody::Data{
+ // TODO: remove unrelated properties:
+ .body_type = Rigidbody::BodyType::STATIC,
+ .bounce = false,
+ .offset = {0,0},
+ });
+ // Create a box with an inner size of 10x10 units
+ world.add_component<BoxCollider>(vec2{0, -100}, 100, 100); // Top
+ world.add_component<BoxCollider>(vec2{0, 100}, 100, 100); // Bottom
+ world.add_component<BoxCollider>(vec2{-100, 0}, 100, 100); // Left
+ world.add_component<BoxCollider>(vec2{100, 0}, 100, 100); // right
+
+ game_object1.add_component<Rigidbody>(Rigidbody::Data{
+ .mass = 1,
+ .gravity_scale = 0.01,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = {0,0},
+ .constraints = {0, 0, 0},
+ .use_gravity = true,
+ .bounce = true,
+ .elastisity = 1,
+ .offset = {0,0},
+ });
+ game_object1.add_component<BoxCollider>(vec2{0, 0}, 10, 10);
+ BehaviorScript & script_object1 = game_object1.add_component<BehaviorScript>().set_script<CollisionHandler>(1);
+ script_object1_ref = static_cast<CollisionHandler*>(script_object1.script.get());
+ ASSERT_NE(script_object1_ref, nullptr);
+
+ game_object2.add_component<Rigidbody>(Rigidbody::Data{
+ .mass = 1,
+ .gravity_scale = 0.01,
+ .body_type = Rigidbody::BodyType::DYNAMIC,
+ .linear_velocity = {0,0},
+ .constraints = {0, 0, 0},
+ .use_gravity = true,
+ .bounce = true,
+ .elastisity = 1,
+ .offset = {0,0},
+ });
+ game_object2.add_component<BoxCollider>(vec2{0, 0}, 10, 10);
+ BehaviorScript & script_object2 = game_object2.add_component<BehaviorScript>().set_script<CollisionHandler>(2);
+ script_object2_ref = static_cast<CollisionHandler*>(script_object2.script.get());
+ ASSERT_NE(script_object2_ref, nullptr);
+
+ // Ensure Script::init() is called on all BehaviorScript instances
+ script_sys.update();
+ }
+};
+
+TEST_F(CollisionTest, collision_example) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 1);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 2);
+ };
+ EXPECT_FALSE(collision_happend);
+ collision_sys.update();
+ EXPECT_FALSE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_both_no_velocity) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, 10);
+ EXPECT_EQ(ev.info.resolution.y, 10);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, 10);
+ EXPECT_EQ(ev.info.resolution.y, 10);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH);
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50,30};
+ collision_sys.update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_x_direction_no_velocity) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, -5);
+ EXPECT_EQ(ev.info.resolution.y, 0);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, 5);
+ EXPECT_EQ(ev.info.resolution.y, 0);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION);
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {45,30};
+ collision_sys.update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_y_direction_no_velocity) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, 0);
+ EXPECT_EQ(ev.info.resolution.y, -5);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, 0);
+ EXPECT_EQ(ev.info.resolution.y, 5);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION);
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50,25};
+ collision_sys.update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_both) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, 10);
+ EXPECT_EQ(ev.info.resolution.y, 10);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, 10);
+ EXPECT_EQ(ev.info.resolution.y, 10);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH);
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50,30};
+ Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get();
+ rg1.data.linear_velocity = {10,10};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.linear_velocity = {10,10};
+ collision_sys.update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_x_direction) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, -5);
+ EXPECT_EQ(ev.info.resolution.y, -5);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, 5);
+ EXPECT_EQ(ev.info.resolution.y, 5);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION);
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {45,30};
+ Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get();
+ rg1.data.linear_velocity = {10,10};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.linear_velocity = {10,10};
+ collision_sys.update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_dynamic_y_direction) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, -5);
+ EXPECT_EQ(ev.info.resolution.y, -5);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 2);
+ EXPECT_EQ(ev.info.resolution.x, 5);
+ EXPECT_EQ(ev.info.resolution.y, 5);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION);
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50,25};
+ Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get();
+ rg1.data.linear_velocity = {10,10};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.linear_velocity = {10,10};
+ collision_sys.update();
+ EXPECT_TRUE(collision_happend);
+}
+
+
+TEST_F(CollisionTest, collision_box_box_static_both) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, 10);
+ EXPECT_EQ(ev.info.resolution.y, 10);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ // is static should not be called
+ FAIL();
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50,30};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC;
+ collision_sys.update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_static_x_direction) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, -5);
+ EXPECT_EQ(ev.info.resolution.y, -5);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::X_DIRECTION);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ // is static should not be called
+ FAIL();
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {45,30};
+ Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get();
+ rg1.data.linear_velocity = {10,10};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC;
+ collision_sys.update();
+ EXPECT_TRUE(collision_happend);
+}
+
+TEST_F(CollisionTest, collision_box_box_static_y_direction) {
+ bool collision_happend = false;
+ script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ collision_happend = true;
+ EXPECT_EQ(ev.info.first_collider.game_object_id, 1);
+ EXPECT_EQ(ev.info.resolution.x, -5);
+ EXPECT_EQ(ev.info.resolution.y, -5);
+ EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::Y_DIRECTION);
+ };
+ script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) {
+ // is static should not be called
+ FAIL();
+ };
+ EXPECT_FALSE(collision_happend);
+ Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get();
+ tf.position = {50,25};
+ Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get();
+ rg1.data.linear_velocity = {10,10};
+ Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get();
+ rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC;
+ collision_sys.update();
+ EXPECT_TRUE(collision_happend);
+}
diff --git a/src/test/Profiling.cpp b/src/test/Profiling.cpp
new file mode 100644
index 0000000..2549c57
--- /dev/null
+++ b/src/test/Profiling.cpp
@@ -0,0 +1,230 @@
+#include "system/ParticleSystem.h"
+#include "system/PhysicsSystem.h"
+#include "system/RenderSystem.h"
+#include <cmath>
+#include <chrono>
+#include <gtest/gtest.h>
+
+#define private public
+#define protected public
+
+#include <crepe/ComponentManager.h>
+#include <crepe/api/Event.h>
+#include <crepe/api/EventManager.h>
+#include <crepe/api/GameObject.h>
+#include <crepe/api/Rigidbody.h>
+#include <crepe/api/Script.h>
+#include <crepe/api/Transform.h>
+#include <crepe/api/ParticleEmitter.h>
+#include <crepe/system/CollisionSystem.h>
+#include <crepe/system/ScriptSystem.h>
+#include <crepe/types.h>
+#include <crepe/util/Log.h>
+
+using namespace std;
+using namespace std::chrono_literals;
+using namespace crepe;
+using namespace testing;
+
+
+/*
+List of test cases with component settings/details
+1. Minimal test creates gameobject without additonal components
+2. Minimal 'Complex' gameobject. Has dynamic body without bounce and no collision handler
+3. Minimal 'Complex' gameobject. Same as test 2 but with particle emitter
+*/
+
+class TestScript : public Script {
+ bool oncollision(const CollisionEvent& test) {
+ Log::logf("Box {} script on_collision()", test.info.first_collider.game_object_id);
+ return true;
+ }
+ void init() {
+ subscribe<CollisionEvent>([this](const CollisionEvent& ev) -> bool {
+ return this->oncollision(ev);
+ });
+ }
+ void update() {
+ // Retrieve component from the same GameObject this script is on
+ }
+};
+
+class Profiling : public Test {
+public:
+ // Config for test
+ // Minimum amount to let test pass
+ const int min_gameobject_count = 100;
+ // Maximum amount to stop test
+ const int max_gameobject_count = 200;
+ // Amount of times a test runs to calculate average
+ const int average = 5;
+ // Maximum duration to stop test
+ const std::chrono::microseconds duration = 16000us;
+
+
+ ComponentManager mgr;
+ // Add system used for profling tests
+ CollisionSystem collision_sys{mgr};
+ PhysicsSystem physics_sys{mgr};
+ ParticleSystem particle_sys{mgr};
+ RenderSystem render_sys{mgr};
+ ScriptSystem script_sys{mgr};
+
+ // Test data
+ std::map<std::string, std::chrono::microseconds> timings;
+ int game_object_count = 0;
+ std::chrono::microseconds total_time = 0us;
+
+
+ void SetUp() override {
+
+ GameObject do_not_use = mgr.new_object("DO_NOT_USE","",{0,0});
+ do_not_use.add_component<Camera>(Color::WHITE);
+ // initialize systems here:
+ //calls init
+ script_sys.update();
+ //creates window
+ render_sys.update();
+ }
+
+ // Helper function to time an update call and store its duration
+ template <typename Func>
+ std::chrono::microseconds time_function(const std::string& name, Func&& func) {
+ auto start = std::chrono::steady_clock::now();
+ func();
+ auto end = std::chrono::steady_clock::now();
+ std::chrono::microseconds duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+ timings[name] += duration;
+ return duration;
+ }
+
+ // Run and profile all systems, return the total time in milliseconds
+ std::chrono::microseconds run_all_systems() {
+ std::chrono::microseconds total_microseconds = 0us;
+ total_microseconds += time_function("PhysicsSystem", [&]() { physics_sys.update(); });
+ total_microseconds += time_function("CollisionSystem", [&]() { collision_sys.update(); });
+ total_microseconds += time_function("ParticleSystem", [&]() { particle_sys.update(); });
+ total_microseconds += time_function("RenderSystem", [&]() { render_sys.update(); });
+ return total_microseconds;
+ }
+
+ // Print timings of all functions
+ void log_timings() const {
+ std::stringstream ss;
+ ss << "\nFunction timings:\n";
+ for (const auto& [name, duration] : timings) {
+ ss << name << " took " << duration.count() / 1000.0 / average << " ms (" << duration.count() / average << " µs).\n";
+ }
+ ss << "Total time: " << this->total_time.count() / 1000.0 / average << " ms (" << this->total_time.count() / average << " µs)\n";
+ ss << "Amount of gameobjects: " << game_object_count << "\n";
+ GTEST_LOG_(INFO) << ss.str();
+ }
+
+ void clear_timings() {
+ for (auto& [key, value] : timings) {
+ value = std::chrono::microseconds(0);
+ }
+ }
+};
+
+TEST_F(Profiling, Profiling_1) {
+ while (this->total_time/this->average < this->duration) {
+
+
+ {
+ //define gameobject used for testing
+ GameObject gameobject = mgr.new_object("gameobject","",{0,0});
+ }
+
+ this->game_object_count++;
+
+ this->total_time = 0us;
+ clear_timings();
+
+ for (int amount = 0; amount < this->average; amount++) {
+ this->total_time += run_all_systems();
+ }
+
+ if(this->game_object_count >= this->max_gameobject_count) break;
+ }
+ log_timings();
+ EXPECT_GE(this->game_object_count, this->min_gameobject_count);
+}
+
+TEST_F(Profiling, Profiling_2) {
+ while (this->total_time/this->average < this->duration) {
+
+ {
+ //define gameobject used for testing
+ GameObject gameobject = mgr.new_object("gameobject","",{static_cast<float>(game_object_count*2),0});
+ gameobject.add_component<Rigidbody>(Rigidbody::Data{
+ .body_type = Rigidbody::BodyType::STATIC,
+ .use_gravity = false,
+ });
+ gameobject.add_component<BoxCollider>(vec2{0, 0}, 1, 1);
+ gameobject.add_component<BehaviorScript>().set_script<TestScript>();
+ Color color(0, 0, 0, 0);
+ gameobject.add_component<Sprite>(
+ make_shared<Texture>("asset/texture/green_square.png"), color,
+ FlipSettings{true, true});
+ }
+
+ this->game_object_count++;
+
+ this->total_time = 0us;
+ clear_timings();
+ for (int amount = 0; amount < this->average; amount++) {
+ this->total_time += run_all_systems();
+ }
+
+ if(this->game_object_count >= this->max_gameobject_count) break;
+ }
+ log_timings();
+ EXPECT_GE(this->game_object_count, this->min_gameobject_count);
+}
+
+TEST_F(Profiling, Profiling_3) {
+ while (this->total_time/this->average < this->duration) {
+
+ {
+ //define gameobject used for testing
+ GameObject gameobject = mgr.new_object("gameobject","",{static_cast<float>(game_object_count*2),0});
+ gameobject.add_component<Rigidbody>(Rigidbody::Data{
+ .body_type = Rigidbody::BodyType::STATIC,
+ .use_gravity = false,
+ });
+ gameobject.add_component<BoxCollider>(vec2{0, 0}, 1, 1);
+ gameobject.add_component<BehaviorScript>().set_script<TestScript>();
+ Color color(0, 0, 0, 0);
+ gameobject.add_component<Sprite>(
+ make_shared<Texture>("asset/texture/green_square.png"), color,
+ FlipSettings{true, true});
+ Sprite & test_sprite = gameobject.add_component<Sprite>(
+ make_shared<Texture>("asset/texture/img.png"), color, FlipSettings{false, false});
+ auto & test = gameobject.add_component<ParticleEmitter>(ParticleEmitter::Data{
+ .max_particles = 10,
+ .emission_rate = 100,
+ .end_lifespan = 100000,
+ .boundary{
+ .width = 1000,
+ .height = 1000,
+ .offset = vec2{0, 0},
+ .reset_on_exit = false,
+ },
+ .sprite = test_sprite,
+ });
+ }
+ render_sys.update();
+ this->game_object_count++;
+
+ this->total_time = 0us;
+ clear_timings();
+ for (int amount = 0; amount < this->average; amount++) {
+ this->total_time += run_all_systems();
+ }
+
+ if(this->game_object_count >= this->max_gameobject_count) break;
+ }
+ log_timings();
+ EXPECT_GE(this->game_object_count, this->min_gameobject_count);
+}