diff options
Diffstat (limited to 'src/crepe/system')
24 files changed, 971 insertions, 742 deletions
diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp index 680dbb8..94445c7 100644 --- a/src/crepe/system/AISystem.cpp +++ b/src/crepe/system/AISystem.cpp @@ -10,7 +10,7 @@ using namespace crepe; using namespace std::chrono; -void AISystem::update() { +void AISystem::fixed_update() { const Mediator & mediator = this->mediator; ComponentManager & mgr = mediator.component_manager; LoopTimerManager & loop_timer = mediator.loop_timer; @@ -28,7 +28,8 @@ void AISystem::update() { = mgr.get_components_by_id<Rigidbody>(ai.game_object_id); if (rigidbodies.empty()) { throw std::runtime_error( - "AI component must be attached to a GameObject with a Rigidbody component"); + "AI component must be attached to a GameObject with a Rigidbody component" + ); } Rigidbody & rigidbody = rigidbodies.front().get(); if (!rigidbody.active) { @@ -110,8 +111,8 @@ bool AISystem::accumulate_force(const AI & ai, vec2 & running_total, vec2 & forc return true; } -vec2 AISystem::seek(const AI & ai, const Rigidbody & rigidbody, - const Transform & transform) const { +vec2 AISystem::seek(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) + const { // Calculate the desired velocity vec2 desired_velocity = ai.seek_target - transform.position; desired_velocity.normalize(); @@ -120,12 +121,12 @@ vec2 AISystem::seek(const AI & ai, const Rigidbody & rigidbody, return desired_velocity - rigidbody.data.linear_velocity; } -vec2 AISystem::flee(const AI & ai, const Rigidbody & rigidbody, - const Transform & transform) const { +vec2 AISystem::flee(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) + const { // Calculate the desired velocity if the entity is within the panic distance vec2 desired_velocity = transform.position - ai.flee_target; if (desired_velocity.length_squared() > ai.square_flee_panic_distance) { - return vec2{0, 0}; + return vec2 {0, 0}; } desired_velocity.normalize(); desired_velocity *= rigidbody.data.max_linear_velocity; @@ -133,8 +134,8 @@ vec2 AISystem::flee(const AI & ai, const Rigidbody & rigidbody, return desired_velocity - rigidbody.data.linear_velocity; } -vec2 AISystem::arrive(const AI & ai, const Rigidbody & rigidbody, - const Transform & transform) const { +vec2 AISystem::arrive(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) + const { // Calculate the desired velocity (taking into account the deceleration rate) vec2 to_target = ai.arrive_target - transform.position; float distance = to_target.length(); @@ -150,12 +151,12 @@ vec2 AISystem::arrive(const AI & ai, const Rigidbody & rigidbody, return desired_velocity - rigidbody.data.linear_velocity; } - return vec2{0, 0}; + return vec2 {0, 0}; } vec2 AISystem::path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform) { if (ai.path.empty()) { - return vec2{0, 0}; + return vec2 {0, 0}; } // Get the target node diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h index d5f8a8e..04807cf 100644 --- a/src/crepe/system/AISystem.h +++ b/src/crepe/system/AISystem.h @@ -20,7 +20,7 @@ public: using System::System; //! Update the AI system - void update() override; + void fixed_update() override; private: /** diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 107b25d..e5ab2fa 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -1,5 +1,3 @@ - - #include "../api/Animator.h" #include "../manager/ComponentManager.h" #include "../manager/LoopTimerManager.h" @@ -10,7 +8,7 @@ using namespace crepe; using namespace std::chrono; -void AnimatorSystem::update() { +void AnimatorSystem::frame_update() { ComponentManager & mgr = this->mediator.component_manager; LoopTimerManager & timer = this->mediator.loop_timer; RefVector<Animator> animations = mgr.get_components_by_type<Animator>(); diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h index 7d3f565..092e131 100644 --- a/src/crepe/system/AnimatorSystem.h +++ b/src/crepe/system/AnimatorSystem.h @@ -22,7 +22,7 @@ public: * Animator components, moving the animations forward and managing their behavior (e.g., * looping). */ - void update() override; + void frame_update() override; }; } // namespace crepe diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp index b1aa0f8..d4e8b9f 100644 --- a/src/crepe/system/AudioSystem.cpp +++ b/src/crepe/system/AudioSystem.cpp @@ -7,7 +7,7 @@ using namespace crepe; using namespace std; -void AudioSystem::update() { +void AudioSystem::fixed_update() { ComponentManager & component_manager = this->mediator.component_manager; ResourceManager & resource_manager = this->mediator.resource_manager; RefVector<AudioSource> components diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h index 2ddc443..56fc98c 100644 --- a/src/crepe/system/AudioSystem.h +++ b/src/crepe/system/AudioSystem.h @@ -11,7 +11,7 @@ namespace crepe { class AudioSystem : public System { public: using System::System; - void update() override; + void fixed_update() override; private: /** diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index 0e2db76..52369d0 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -8,6 +8,8 @@ target_sources(crepe PUBLIC AudioSystem.cpp AnimatorSystem.cpp InputSystem.cpp + EventSystem.cpp + ReplaySystem.cpp AISystem.cpp ) @@ -20,5 +22,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES AudioSystem.h AnimatorSystem.h InputSystem.h + EventSystem.h + ReplaySystem.h AISystem.h ) diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp index af8adce..571ac70 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -1,6 +1,7 @@ #include <algorithm> #include <cmath> #include <cstddef> +#include <emmintrin.h> #include <functional> #include <optional> #include <utility> @@ -15,15 +16,25 @@ #include "api/Rigidbody.h" #include "api/Transform.h" #include "api/Vector2.h" +#include "util/AbsolutePosition.h" +#include "util/OptionalRef.h" -#include "Collider.h" #include "CollisionSystem.h" #include "types.h" -#include "util/OptionalRef.h" using namespace crepe; +using enum Rigidbody::BodyType; + +CollisionSystem::CollisionInfo CollisionSystem::CollisionInfo::operator-() const { + return { + .self = this->other, + .other = this->self, + .resolution = -this->resolution, + .resolution_direction = this->resolution_direction, + }; +} -void CollisionSystem::update() { +void CollisionSystem::fixed_update() { std::vector<CollisionInternal> all_colliders; game_object_id_t id = 0; ComponentManager & mgr = this->mediator.component_manager; @@ -33,15 +44,17 @@ void CollisionSystem::update() { if (!rigidbody.active) continue; id = rigidbody.game_object_id; Transform & transform = mgr.get_components_by_id<Transform>(id).front().get(); + Metadata & metadata = mgr.get_components_by_id<Metadata>(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}); + all_colliders.push_back( + {.id = id, + .collider = collider_variant {boxcollider}, + .info = {transform, rigidbody, metadata}} + ); } // Check if the circlecollider is active and has the same id as the rigidbody. RefVector<CircleCollider> circlecolliders @@ -49,312 +62,461 @@ void CollisionSystem::update() { 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}); + all_colliders.push_back( + {.id = id, + .collider = collider_variant {circlecollider}, + .info = {transform, rigidbody, metadata}} + ); } } - // Check between all colliders if there is a collision + // Check between all colliders if there is a collision (collision handling) std::vector<std::pair<CollisionInternal, CollisionInternal>> collided = this->gather_collisions(all_colliders); - // For both objects call the collision handler + // For the object convert the info and call the collision handler if needed 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); + // Convert internal struct to external struct + CollisionInfo info + = this->get_collision_info(collision_pair.first, collision_pair.second); + // Determine if and/or what collison handler is needed. + this->determine_collision_handler(info); } } -void CollisionSystem::collision_handler_request(CollisionInternal & this_data, - CollisionInternal & other_data) { +// Below is for collision detection +std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>> +CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) { - 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; + // 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 (!should_collide(colliders[i], colliders[j])) continue; + CollisionInternalType type + = get_collider_type(colliders[i].collider, colliders[j].collider); + if (!detect_collision(colliders[i], colliders[j], type)) continue; + //fet + collisions_ret.emplace_back(colliders[i], colliders[j]); } } + return collisions_ret; +} - // 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, - }; +bool CollisionSystem::should_collide( + const CollisionInternal & self, const CollisionInternal & other +) const { + + const Rigidbody::Data & self_rigidbody = self.info.rigidbody.data; + const Rigidbody::Data & other_rigidbody = other.info.rigidbody.data; + const Metadata & self_metadata = self.info.metadata; + const Metadata & other_metadata = other.info.metadata; + + // Check collision layers + if (self_rigidbody.collision_layers.contains(other_rigidbody.collision_layer)) return true; + if (other_rigidbody.collision_layers.contains(self_rigidbody.collision_layer)) return true; + + // Check names + if (self_rigidbody.collision_names.contains(other_metadata.name)) return true; + if (other_rigidbody.collision_names.contains(self_metadata.name)) return true; + + // Check tags + if (self_rigidbody.collision_tags.contains(other_metadata.tag)) return true; + if (other_rigidbody.collision_tags.contains(self_metadata.tag)) return true; - // Determine if static needs to be called - this->determine_collision_handler(collision_info); + return false; } -std::pair<vec2, CollisionSystem::Direction> -CollisionSystem::collision_handler(CollisionInternal & data1, CollisionInternal & data2, - CollisionInternalType type) { +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::detect_collision( + CollisionInternal & self, CollisionInternal & other, const 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); + // Box-Box collision detection + const BoxColliderInternal BOX1 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const BoxColliderInternal BOX2 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from box-box collision detection + resolution = this->get_box_box_detection(BOX1, BOX2); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; 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); + // Box-Circle collision detection + const BoxColliderInternal BOX1 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const CircleColliderInternal CIRCLE2 + = {.collider + = std::get<std::reference_wrapper<CircleCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from box-circle collision detection + resolution = this->get_box_circle_detection(BOX1, CIRCLE2); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; + // Invert the resolution vector for proper collision response + resolution = -resolution; 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); + // Circle-Circle collision detection + const CircleColliderInternal CIRCLE1 + = {.collider = std::get<std::reference_wrapper<CircleCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const CircleColliderInternal CIRCLE2 + = {.collider + = std::get<std::reference_wrapper<CircleCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from circle-circle collision detection + resolution = this->get_circle_circle_detection(CIRCLE1, CIRCLE2); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; 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); + // Circle-Box collision detection + const CircleColliderInternal CIRCLE1 + = {.collider = std::get<std::reference_wrapper<CircleCollider>>(self.collider), + .transform = self.info.transform, + .rigidbody = self.info.rigidbody}; + const BoxColliderInternal BOX2 + = {.collider = std::get<std::reference_wrapper<BoxCollider>>(other.collider), + .transform = other.info.transform, + .rigidbody = other.info.rigidbody}; + // Get resolution vector from box-circle collision detection (order swapped) + resolution = this->get_box_circle_detection(BOX2, CIRCLE1); + // If no collision (NaN values), return false + if (resolution.is_nan()) return false; break; } + case CollisionInternalType::NONE: + // No collision detection needed if the type is NONE + return false; + break; } + // Store the calculated resolution vector for the 'self' collider + self.resolution = resolution; + // Calculate the resolution direction based on the rigidbody data + self.resolution_direction + = this->resolution_correction(self.resolution, self.info.rigidbody.data); + // For the 'other' collider, the resolution is the opposite direction of 'self' + other.resolution = -self.resolution; + other.resolution_direction = self.resolution_direction; + + // Return true if a collision was detected and resolution was calculated + return true; +} - 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; - //checks if the other velocity has a value and if this object moved - if (data1.rigidbody.data.linear_velocity.x != 0 - && 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; - //checks if the other velocity has a value and if this object moved - if (data1.rigidbody.data.linear_velocity.x != 0 - && data1.rigidbody.data.linear_velocity.y != 0) - resolution.x = -data1.rigidbody.data.linear_velocity.x - * (resolution.y / data1.rigidbody.data.linear_velocity.y); - } +vec2 CollisionSystem::get_box_box_detection( + const BoxColliderInternal & box1, const BoxColliderInternal & box2 +) const { + vec2 resolution {NAN, NAN}; + // Get current positions of colliders + vec2 pos1 = AbsolutePosition::get_position(box1.transform, box1.collider.offset); + vec2 pos2 = AbsolutePosition::get_position(box2.transform, box2.collider.offset); - return std::make_pair(resolution, resolution_direction); -} + // Scale dimensions + vec2 scaled_box1 = box1.collider.dimensions * box1.transform.scale; + vec2 scaled_box2 = box2.collider.dimensions * box2.transform.scale; + vec2 delta = pos2 - pos1; -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; + // Calculate half-extents (half width and half height) + float half_width1 = scaled_box1.x / 2.0; + float half_height1 = scaled_box1.y / 2.0; + float half_width2 = scaled_box2.x / 2.0; + float half_height2 = scaled_box2.y / 2.0; + + if (pos1.x + half_width1 > pos2.x - half_width2 + && pos1.x - half_width1 < pos2.x + half_width2 + && pos1.y + half_height1 > pos2.y - half_height2 + && pos1.y - half_height1 < pos2.y + half_height2) { + resolution = {0, 0}; + float overlap_x = (half_width1 + half_width2) - std::abs(delta.x); + float overlap_y = (half_height1 + half_height2) - std::abs(delta.y); + 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; +vec2 CollisionSystem::get_box_circle_detection( + const BoxColliderInternal & box, const CircleColliderInternal & circle +) const { + /// Get current positions of colliders + vec2 box_pos = AbsolutePosition::get_position(box.transform, box.collider.offset); + vec2 circle_pos = AbsolutePosition::get_position(circle.transform, circle.collider.offset); - // Compute the distance between the two circle centers - float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); + // Scale dimensions + vec2 scaled_box = box.collider.dimensions * box.transform.scale; + float scaled_circle_radius = circle.collider.radius * circle.transform.scale; - // Compute the combined radii of the two circles - float combined_radius = circle_collider1.radius + circle_collider2.radius; + // Calculate box half-extents + float half_width = scaled_box.x / 2.0f; + float half_height = scaled_box.y / 2.0f; - // Compute the penetration depth - float penetration_depth = combined_radius - distance; + // Find the closest point on the box to the circle's center + float closest_x + = std::max(box_pos.x - half_width, std::min(circle_pos.x, box_pos.x + half_width)); + float closest_y + = std::max(box_pos.y - half_height, std::min(circle_pos.y, box_pos.y + half_height)); - // Normalize the delta vector to get the collision direction - vec2 collision_normal = delta / distance; + float distance_x = circle_pos.x - closest_x; + float distance_y = circle_pos.y - closest_y; + float distance_squared = distance_x * distance_x + distance_y * distance_y; + if (distance_squared < scaled_circle_radius * scaled_circle_radius) { + vec2 delta = circle_pos - box_pos; - // Compute the resolution vector - vec2 resolution = -collision_normal * penetration_depth; + // 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); - return resolution; + // Find the vector from the circle center to the closest point + vec2 closest_delta = delta - closest_point; + + 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 = scaled_circle_radius - distance; + + // Compute the resolution vector + return vec2 {collision_normal * penetration_depth}; + } + // No collision + return vec2 {NAN, NAN}; } -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; +vec2 CollisionSystem::get_circle_circle_detection( + const CircleColliderInternal & circle1, const CircleColliderInternal & circle2 +) const { + // Get current positions of colliders + vec2 final_position1 + = AbsolutePosition::get_position(circle1.transform, circle1.collider.offset); + vec2 final_position2 + = AbsolutePosition::get_position(circle2.transform, circle2.collider.offset); - // Compute half-dimensions of the box - float half_width = box_collider.dimensions.x / 2.0f; - float half_height = box_collider.dimensions.y / 2.0f; + // Scale dimensions + float scaled_circle1 = circle1.collider.radius * circle1.transform.scale; + float scaled_circle2 = circle2.collider.radius * circle2.transform.scale; - // 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); + 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 = scaled_circle1 + scaled_circle2; - // Find the vector from the circle center to the closest point - vec2 closest_delta = delta - closest_point; + // Check for collision (distance squared must be less than the square of the radius sum) + if (distance_squared < radius_sum * radius_sum) { + vec2 delta = final_position2 - final_position1; - // 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 the distance between the two circle centers + float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); - // Compute penetration depth - float penetration_depth = circle_collider.radius - distance; + // Compute the combined radii of the two circles + float combined_radius = scaled_circle1 + scaled_circle2; - // Compute the resolution vector - vec2 resolution = collision_normal * penetration_depth; + // Compute the penetration depth + float penetration_depth = combined_radius - distance; - return resolution; + // 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; + } + // No collision + return vec2 {NAN, NAN}; + ; +} + +CollisionSystem::Direction +CollisionSystem::resolution_correction(vec2 & resolution, const Rigidbody::Data & rigidbody) { + + // Calculate the other value to move back correctly + // If only X or Y has a value determine what is should be to move back. + Direction resolution_direction = Direction::NONE; + // If both are not zero a perfect corner has been hit + if (resolution.x != 0 && resolution.y != 0) { + resolution_direction = Direction::BOTH; + // If x is not zero a horizontal action was latest action. + } else if (resolution.x != 0) { + resolution_direction = Direction::X_DIRECTION; + // If both are 0 resolution y should not be changed (y_velocity can be 0 by kinematic object movement) + if (rigidbody.linear_velocity.x != 0 && rigidbody.linear_velocity.y != 0) + resolution.y + = -rigidbody.linear_velocity.y * (resolution.x / rigidbody.linear_velocity.x); + } else if (resolution.y != 0) { + resolution_direction = Direction::Y_DIRECTION; + // If both are 0 resolution x should not be changed (x_velocity can be 0 by kinematic object movement) + if (rigidbody.linear_velocity.x != 0 && rigidbody.linear_velocity.y != 0) + resolution.x + = -rigidbody.linear_velocity.x * (resolution.y / rigidbody.linear_velocity.y); + } + + return resolution_direction; } -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); +CollisionSystem::CollisionInfo CollisionSystem::get_collision_info( + const CollisionInternal & in_self, const CollisionInternal & in_other +) const { + + crepe::CollisionSystem::ColliderInfo self { + .transform = in_self.info.transform, + .rigidbody = in_self.info.rigidbody, + .metadata = in_self.info.metadata, }; - // 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); + + crepe::CollisionSystem::ColliderInfo other { + .transform = in_other.info.transform, + .rigidbody = in_other.info.rigidbody, + .metadata = in_other.info.metadata, + }; + + struct CollisionInfo collision_info { + .self = self, .other = other, .resolution = in_self.resolution, + .resolution_direction = in_self.resolution_direction, + }; + return collision_info; } -void CollisionSystem::static_collision_handler(CollisionInfo & info) { +void CollisionSystem::determine_collision_handler(const CollisionInfo & info) { + Rigidbody::BodyType self_type = info.self.rigidbody.data.body_type; + Rigidbody::BodyType other_type = info.other.rigidbody.data.body_type; + bool self_kinematic = info.self.rigidbody.data.kinematic_collision; + bool other_kinematic = info.other.rigidbody.data.kinematic_collision; + // Inverted collision info + CollisionInfo inverted = -info; + // If both objects are static skip handle call collision script + if (self_type == STATIC && other_type == STATIC) return; + + // First body is not dynamic + if (self_type != DYNAMIC) { + bool static_collision = self_type == STATIC && other_type == DYNAMIC; + bool kinematic_collision + = self_type == KINEMATIC && other_type == DYNAMIC && self_kinematic; + + // Handle collision + if (static_collision || kinematic_collision) this->static_collision_handler(inverted); + // Call scripts + this->call_collision_events(inverted); + return; + } + + // Second body is not dynamic + if (other_type != DYNAMIC) { + bool static_collision = other_type == STATIC; + bool kinematic_collision = other_type == KINEMATIC && other_kinematic; + // Handle collision + if (static_collision || kinematic_collision) this->static_collision_handler(info); + // Call scripts + this->call_collision_events(info); + return; + } + + // Dynamic + // Handle collision + this->dynamic_collision_handler(info); + // Call scripts + this->call_collision_events(info); +} + +void CollisionSystem::static_collision_handler(const CollisionInfo & info) { + + vec2 & transform_pos = info.self.transform.position; + float elasticity = info.self.rigidbody.data.elasticity_coefficient; + vec2 & rigidbody_vel = info.self.rigidbody.data.linear_velocity; + // Move object back using calculate move back value - info.this_transform.position += info.resolution; + transform_pos += info.resolution; switch (info.resolution_direction) { case Direction::BOTH: //bounce - if (info.this_rigidbody.data.elastisity_coefficient > 0) { - info.this_rigidbody.data.linear_velocity - = -info.this_rigidbody.data.linear_velocity - * info.this_rigidbody.data.elastisity_coefficient; + if (elasticity > 0) { + rigidbody_vel = -rigidbody_vel * elasticity; } //stop movement else { - info.this_rigidbody.data.linear_velocity = {0, 0}; + rigidbody_vel = {0, 0}; } break; case Direction::Y_DIRECTION: // Bounce - if (info.this_rigidbody.data.elastisity_coefficient > 0) { - info.this_rigidbody.data.linear_velocity.y - = -info.this_rigidbody.data.linear_velocity.y - * info.this_rigidbody.data.elastisity_coefficient; + if (elasticity > 0) { + rigidbody_vel.y = -rigidbody_vel.y * elasticity; } // Stop movement else { - info.this_rigidbody.data.linear_velocity.y = 0; - info.this_transform.position.x -= info.resolution.x; + rigidbody_vel.y = 0; + transform_pos.x -= info.resolution.x; } break; case Direction::X_DIRECTION: // Bounce - if (info.this_rigidbody.data.elastisity_coefficient > 0) { - info.this_rigidbody.data.linear_velocity.x - = -info.this_rigidbody.data.linear_velocity.x - * info.this_rigidbody.data.elastisity_coefficient; + if (elasticity > 0) { + rigidbody_vel.x = -rigidbody_vel.x * elasticity; } // Stop movement else { - info.this_rigidbody.data.linear_velocity.x = 0; - info.this_transform.position.y -= info.resolution.y; + rigidbody_vel.x = 0; + transform_pos.y -= info.resolution.y; } break; case Direction::NONE: @@ -363,213 +525,80 @@ void CollisionSystem::static_collision_handler(CollisionInfo & info) { } } -std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>> -CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) { +void CollisionSystem::dynamic_collision_handler(const CollisionInfo & info) { - // TODO: - // If no colliders skip - // Check if colliders has rigidbody if not skip + vec2 & self_transform_pos = info.self.transform.position; + vec2 & other_transform_pos = info.other.transform.position; + float self_elasticity = info.self.rigidbody.data.elasticity_coefficient; + float other_elasticity = info.other.rigidbody.data.elasticity_coefficient; + vec2 & self_rigidbody_vel = info.self.rigidbody.data.linear_velocity; + vec2 & other_rigidbody_vel = info.other.rigidbody.data.linear_velocity; - // TODO: - // If amount is higer than lets say 16 for now use quadtree otwerwise skip - // Quadtree code - // Quadtree is placed over the input vector + self_transform_pos += info.resolution / 2; + other_transform_pos += -(info.resolution / 2); - // 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) { + switch (info.resolution_direction) { + case Direction::BOTH: + if (self_elasticity > 0) { + self_rigidbody_vel = -self_rigidbody_vel * self_elasticity; + } else { + self_rigidbody_vel = {0, 0}; + } - // Check if any number is equal in the layers - for (int num : layers1) { - if (layers2.contains(num)) { - // Common layer found - return true; + if (other_elasticity > 0) { + other_rigidbody_vel = -other_rigidbody_vel * other_elasticity; + } else { + other_rigidbody_vel = {0, 0}; + } break; - } - } - // No common layer found - return false; -} + case Direction::Y_DIRECTION: + if (self_elasticity > 0) { + self_rigidbody_vel.y = -self_rigidbody_vel.y * self_elasticity; + } + // Stop movement + else { + self_rigidbody_vel.y = 0; + self_transform_pos.x -= info.resolution.x; + } -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; - } - } -} + if (other_elasticity > 0) { + other_rigidbody_vel.y = -other_rigidbody_vel.y * other_elasticity; + } + // Stop movement + else { + other_rigidbody_vel.y = 0; + other_transform_pos.x -= info.resolution.x; + } + break; + case Direction::X_DIRECTION: + if (self_elasticity > 0) { + self_rigidbody_vel.x = -self_rigidbody_vel.x * self_elasticity; + } + // Stop movement + else { + self_rigidbody_vel.x = 0; + self_transform_pos.y -= info.resolution.y; + } -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); - } + if (other_elasticity > 0) { + other_rigidbody_vel.x = -other_rigidbody_vel.x * other_elasticity; + } + // Stop movement + else { + other_rigidbody_vel.x = 0; + other_transform_pos.y -= info.resolution.y; + } + break; + case Direction::NONE: + // Not possible + break; } - 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)); +void CollisionSystem::call_collision_events(const CollisionInfo & info) { + CollisionEvent data(info); + CollisionEvent data_inverted(-info); + EventManager & emgr = this->mediator.event_manager; + emgr.trigger_event<CollisionEvent>(data, info.self.transform.game_object_id); + emgr.trigger_event<CollisionEvent>(data_inverted, info.other.transform.game_object_id); } diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h index 5b136c6..ff2d35f 100644 --- a/src/crepe/system/CollisionSystem.h +++ b/src/crepe/system/CollisionSystem.h @@ -23,33 +23,6 @@ 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. @@ -59,33 +32,77 @@ private: //! Movement in the Y direction. Y_DIRECTION, //! Movement in both X and Y directions. - BOTH + BOTH, }; public: + //! Structure representing components of the collider + struct ColliderInfo { + Transform & transform; + Rigidbody & rigidbody; + Metadata & metadata; + }; + /** * \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; + ColliderInfo self; + ColliderInfo other; //! The resolution vector for the collision. vec2 resolution; //! The direction of movement for resolving the collision. Direction resolution_direction = Direction::NONE; + CollisionInfo operator-() const; }; +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, + NONE, + }; + + /** + * \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 handling 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; + ColliderInfo info; + vec2 resolution; + Direction resolution_direction = Direction::NONE; + }; + + //! Structure of a collider with additional components + template <typename ColliderType> + struct ColliderInternal { + ColliderType & collider; + Transform & transform; + Rigidbody & rigidbody; + }; + //! Predefined BoxColliderInternal. (System is only made for this type) + using BoxColliderInternal = ColliderInternal<BoxCollider>; + //! Predefined CircleColliderInternal. (System is only made for this type) + using CircleColliderInternal = ColliderInternal<CircleCollider>; + public: //! Updates the collision system by checking for collisions between colliders and handling them. - void update() override; + void fixed_update() override; private: /** @@ -97,117 +114,94 @@ private: * \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; + CollisionInternalType get_collider_type( + const collider_variant & collider1, const collider_variant & collider2 + ) const; private: /** - * \brief Handles collision resolution between two colliders. + * \brief Converts internal collision data into user-accessible collision information. * - * Processes collision data and adjusts objects to resolve collisions and/or calls the user oncollision script function. + * This function processes collision data from two colliding entities and packages it + * into a structured format that is accessible for further use, + * such as resolving collisions and triggering user-defined collision scripts. * * \param data1 Collision data for the first collider. * \param data2 Collision data for the second collider. */ - void collision_handler_request(CollisionInternal & data1, CollisionInternal & data2); + CollisionInfo + get_collision_info(const CollisionInternal & data1, const CollisionInternal & data2) const; /** - * \brief Resolves collision between two colliders and calculates the movement required. + * \brief Corrects the collision resolution vector and determines its direction. * - * Determines the displacement and direction needed to separate colliders based on their types. + * This function adjusts the provided resolution vector based on the + * rigidbody's linear velocity to ensure consistent collision correction. If the resolution + * vector has only one non-zero component (either x or y), the missing component is computed + * based on the rigidbody's velocity. If both components are non-zero, it indicates a corner + * collision. The function also identifies the direction of the resolution and returns it. * - * \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. + * \param resolution resolution vector that needs to be corrected + * \param rigidbody rigidbody data used to correct resolution + * \return A Direction indicating the resolution direction */ - std::pair<vec2, Direction> collision_handler(CollisionInternal & data1, - CollisionInternal & data2, - CollisionInternalType type); + Direction resolution_correction(vec2 & resolution, const Rigidbody::Data & rigidbody); /** - * \brief Calculates the resolution vector for two BoxColliders. + * \brief Determines the appropriate collision handler for a given collision event. * - * Computes the displacement required to separate two overlapping BoxColliders. + * This function identifies the correct collision resolution process based on the body types + * of the colliders involved in the collision. It delegates + * collision handling to specific handlers and calls collision event scripts + * as needed. * - * \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. + * \param info Collision information containing data about both colliders. */ - vec2 get_box_box_resolution(const BoxCollider & box_collider1, - const BoxCollider & box_collider2, const vec2 & position1, - const vec2 & position2) const; + void determine_collision_handler(const CollisionInfo & info); /** - * \brief Calculates the resolution vector for two CircleCollider. + * \brief Calls both collision script * - * Computes the displacement required to separate two overlapping CircleCollider. + * Calls both collision script to let user add additonal handling or handle full collision. * - * \param circle_collider1 The first CircleCollider. - * \param circle_collider2 The second CircleCollider. - * \param final_position1 The position of the first CircleCollider. - * \param final_position2 The position of the second CircleCollider. - * \return The resolution vector for the collision. + * \param info Collision information containing data about both colliders. */ - vec2 get_circle_circle_resolution(const CircleCollider & circle_collider1, - const CircleCollider & circle_collider2, - const vec2 & final_position1, - const vec2 & final_position2) const; + void call_collision_events(const CollisionInfo & info); /** - * \brief Calculates the resolution vector for two CircleCollider. - * - * Computes the displacement required to separate two overlapping CircleCollider. + * \brief Handles collisions involving static objects. * - * \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. - * \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. + * This function resolves collisions between static and dynamic objects by adjusting + * the position of the static object and modifying the velocity of the dynamic object + * if elasticity is enabled. The position of the static object is corrected + * based on the collision resolution, and the dynamic object's velocity is adjusted + * accordingly to reflect the collision response. * - * Decides the correct resolution process based on the dynamic or static nature of the colliders involved. + * The handling includes stopping movement, applying bouncing based on the elasticity + * coefficient, and adjusting the position of the dynamic object if needed. * * \param info Collision information containing data about both colliders. */ - void determine_collision_handler(CollisionInfo & info); + void static_collision_handler(const CollisionInfo & info); /** - * \brief Handles collisions involving static objects. + * \brief Handles collisions involving dynamic objects. * - * Resolves collisions by adjusting positions and modifying velocities if bounce is enabled. + * Resolves collisions between two dynamic objects by adjusting their positions and modifying + * their velocities based on the collision resolution. If elasticity is enabled, + * the velocity of both objects is reversed and scaled by the respective elasticity coefficient. + * The positions of the objects are adjusted based on the collision resolution. * * \param info Collision information containing data about both colliders. */ - void static_collision_handler(CollisionInfo & info); + void dynamic_collision_handler(const CollisionInfo & info); private: /** * \brief Checks for collisions between colliders. * - * Identifies collisions and generates pairs of colliding objects for further processing. + * This function checks all active colliders and identifies pairs of colliding objects. + * For each identified collision, the appropriate collision data is returned as pairs for further processing. * * \param colliders A collection of all active colliders. * \return A list of collision pairs with their associated data. @@ -216,86 +210,84 @@ private: gather_collisions(std::vector<CollisionInternal> & colliders); /** - * \brief Checks if two collision layers have at least one common layer. + * \brief Checks if the settings allow collision * - * 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. + * This function checks if there is any collison layer where each object is located in. + * After checking the layers it checks the names and at last the tags. + * if in all three sets nothing is found collision can not happen. * - * \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. + * \param this_rigidbody Rigidbody of first object + * \param other_rigidbody Rigidbody of second collider + * \param this_metadata Rigidbody of first object + * \param other_metadata Rigidbody of second object + * \return Returns true if there is at least one comparison found. */ - - bool have_common_layer(const std::set<int> & layers1, const std::set<int> & layers2); + bool should_collide( + const CollisionInternal & self, + const CollisionInternal & other + ) const; //done /** * \brief Checks for collision between two colliders. * - * Calls the appropriate collision detection function based on the collider types. + * This function determines whether two colliders are colliding based on their types. + * It calls the appropriate collision detection function based on the collider pair type and stores the collision resolution data. + * If a collision is detected, it returns true, otherwise false. * * \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; + bool detect_collision( + CollisionInternal & first_info, CollisionInternal & second_info, + const CollisionInternalType & type + ); /** * \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. + * This function checks whether two `BoxCollider` are colliding based on their positions and scaled dimensions. + * If a collision is detected, it calculates the overlap along the X and Y axes and returns the resolution vector. + * If no collision is detected, it returns a vector with NaN values. + + * \param box1 Information about the first BoxCollider. + * \param box2 Information about the second BoxCollider. + * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}. */ - bool get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, - const Transform & transform1, const Transform & transform2, - const Rigidbody & rigidbody1, - const Rigidbody & rigidbody2) const; + vec2 get_box_box_detection( + const BoxColliderInternal & box1, const BoxColliderInternal & box2 + ) 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. + * This function detects if a collision occurs between a rectangular box and a circular collider. + * If a collision is detected, the function calculates the resolution vector to resolve the collision. + * If no collision is detected, it returns a vector with NaN values + * + * \param box1 Information about the BoxCollider. + * \param circle2 Information about the circleCollider. + * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}. */ - bool get_box_circle_collision(const BoxCollider & box1, const CircleCollider & circle2, - const Transform & transform1, const Transform & transform2, - const Rigidbody & rigidbody1, - const Rigidbody & rigidbody2) const; + vec2 get_box_circle_detection( + const BoxColliderInternal & box1, const CircleColliderInternal & circle2 + ) 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. + * This function detects if a collision occurs between two circular colliders. + * If a collision is detected, it calculates the resolution vector to resolve the collision. + * If no collision is detected, it returns a vector with NaN values. * - * \return status of collision + * \param circle1 Information about the first circleCollider. + * \param circle2 Information about the second circleCollider. + * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}. */ - bool get_circle_circle_collision(const CircleCollider & circle1, - const CircleCollider & circle2, - const Transform & transform1, - const Transform & transform2, - const Rigidbody & rigidbody1, - const Rigidbody & rigidbody2) const; + vec2 get_circle_circle_detection( + const CircleColliderInternal & circle1, const CircleColliderInternal & circle2 + ) const; }; /** diff --git a/src/crepe/system/EventSystem.cpp b/src/crepe/system/EventSystem.cpp new file mode 100644 index 0000000..7e168ab --- /dev/null +++ b/src/crepe/system/EventSystem.cpp @@ -0,0 +1,9 @@ +#include "EventSystem.h" +#include "../manager/EventManager.h" + +using namespace crepe; + +void EventSystem::fixed_update() { + EventManager & ev = this->mediator.event_manager; + ev.dispatch_events(); +} diff --git a/src/crepe/system/EventSystem.h b/src/crepe/system/EventSystem.h new file mode 100644 index 0000000..0ae48d2 --- /dev/null +++ b/src/crepe/system/EventSystem.h @@ -0,0 +1,21 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +/** + * \brief EventManager dispatch helper system + */ +class EventSystem : public System { +public: + using System::System; + + /** + * \brief Dispatch queued events + * \see EventManager::dispatch_events + */ + void fixed_update() override; +}; + +} // namespace crepe diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp index fca540f..b4a0633 100644 --- a/src/crepe/system/InputSystem.cpp +++ b/src/crepe/system/InputSystem.cpp @@ -8,13 +8,11 @@ using namespace crepe; -void InputSystem::update() { +void InputSystem::fixed_update() { ComponentManager & mgr = this->mediator.component_manager; - SDLContext & context = this->mediator.sdl_context; std::vector<EventData> event_list = context.get_events(); - RefVector<Button> buttons = mgr.get_components_by_type<Button>(); - RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); + const RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); OptionalRef<Camera> curr_cam_ref; // Find the active camera @@ -26,9 +24,8 @@ void InputSystem::update() { if (!curr_cam_ref) return; Camera & current_cam = curr_cam_ref; - RefVector<Transform> transform_vec - = mgr.get_components_by_id<Transform>(current_cam.game_object_id); - Transform & cam_transform = transform_vec.front().get(); + const Transform & cam_transform + = mgr.get_components_by_id<Transform>(current_cam.game_object_id).front(); vec2 camera_origin = cam_transform.position + current_cam.data.postion_offset - (current_cam.viewport_size / 2); @@ -47,12 +44,13 @@ void InputSystem::update() { } } -void InputSystem::handle_mouse_event(const EventData & event, const vec2 & camera_origin, - const Camera & current_cam) { +void InputSystem::handle_mouse_event( + const EventData & event, const vec2 & camera_origin, const Camera & current_cam +) { EventManager & event_mgr = this->mediator.event_manager; vec2 adjusted_mouse; adjusted_mouse.x = event.data.mouse_data.mouse_position.x + camera_origin.x; - adjusted_mouse.x = event.data.mouse_data.mouse_position.y + camera_origin.y; + adjusted_mouse.y = event.data.mouse_data.mouse_position.y + camera_origin.y; // Check if the mouse is within the viewport if ((adjusted_mouse.x < camera_origin.x || adjusted_mouse.x > camera_origin.x + current_cam.viewport_size.x @@ -85,7 +83,9 @@ void InputSystem::handle_mouse_event(const EventData & event, const vec2 & camer .mouse_pos = adjusted_mouse, .button = event.data.mouse_data.mouse_button, }); - this->handle_click(event.data.mouse_data.mouse_button, adjusted_mouse); + this->handle_click( + event.data.mouse_data.mouse_button, adjusted_mouse, current_cam + ); } break; } @@ -95,7 +95,7 @@ void InputSystem::handle_mouse_event(const EventData & event, const vec2 & camer .mouse_pos = adjusted_mouse, .mouse_delta = event.data.mouse_data.rel_mouse_move, }); - this->handle_move(event, adjusted_mouse); + this->handle_move(event, adjusted_mouse, current_cam); break; case EventType::MOUSE_WHEEL: @@ -117,7 +117,8 @@ void InputSystem::handle_non_mouse_event(const EventData & event) { case EventType::KEY_DOWN: event_mgr.queue_event<KeyPressEvent>( - {.repeat = event.data.key_data.key_repeat, .key = event.data.key_data.key}); + {.repeat = event.data.key_data.key_repeat, .key = event.data.key_data.key} + ); break; case EventType::KEY_UP: event_mgr.queue_event<KeyReleaseEvent>({.key = event.data.key_data.key}); @@ -130,11 +131,13 @@ void InputSystem::handle_non_mouse_event(const EventData & event) { break; case EventType::WINDOW_RESIZE: event_mgr.queue_event<WindowResizeEvent>( - WindowResizeEvent{.dimensions = event.data.window_data.resize_dimension}); + WindowResizeEvent {.dimensions = event.data.window_data.resize_dimension} + ); break; case EventType::WINDOW_MOVE: event_mgr.queue_event<WindowMoveEvent>( - {.delta_move = event.data.window_data.move_delta}); + {.delta_move = event.data.window_data.move_delta} + ); break; case EventType::WINDOW_MINIMIZE: event_mgr.queue_event<WindowMinimizeEvent>({}); @@ -153,63 +156,70 @@ void InputSystem::handle_non_mouse_event(const EventData & event) { } } -void InputSystem::handle_move(const EventData & event_data, const vec2 & mouse_pos) { +void InputSystem::handle_move( + const EventData & event_data, const vec2 & mouse_pos, const Camera & current_cam +) { ComponentManager & mgr = this->mediator.component_manager; - - RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + EventManager & event_mgr = this->mediator.event_manager; + const RefVector<Button> buttons = mgr.get_components_by_type<Button>(); for (Button & button : buttons) { if (!button.active) continue; - RefVector<Transform> transform_vec - = mgr.get_components_by_id<Transform>(button.game_object_id); - Transform & transform(transform_vec.front().get()); + const Transform & transform + = mgr.get_components_by_id<Transform>(button.game_object_id).front(); + const Transform & cam_transform + = mgr.get_components_by_id<Transform>(current_cam.game_object_id).front(); + const Metadata & metadata + = mgr.get_components_by_id<Metadata>(button.game_object_id).front(); bool was_hovering = button.hover; - if (this->is_mouse_inside_button(mouse_pos, button, transform)) { + + if (this->is_mouse_inside_button(mouse_pos, button, transform, cam_transform)) { button.hover = true; - if (!button.on_mouse_enter) continue; if (!was_hovering) { - button.on_mouse_enter(); + event_mgr.trigger_event<ButtonEnterEvent>(metadata, metadata.game_object_id); } } else { button.hover = false; - // Trigger the on_exit callback if the hover state just changed to false - if (!button.on_mouse_exit) continue; if (was_hovering) { - button.on_mouse_exit(); + event_mgr.trigger_event<ButtonExitEvent>(metadata, metadata.game_object_id); } } } } -void InputSystem::handle_click(const MouseButton & mouse_button, const vec2 & mouse_pos) { +void InputSystem::handle_click( + const MouseButton & mouse_button, const vec2 & mouse_pos, const Camera & current_cam +) { ComponentManager & mgr = this->mediator.component_manager; - - RefVector<Button> buttons = mgr.get_components_by_type<Button>(); - + EventManager & event_mgr = this->mediator.event_manager; + const RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + const Transform & cam_transform + = mgr.get_components_by_id<Transform>(current_cam.game_object_id).front(); for (Button & button : buttons) { if (!button.active) continue; - if (!button.on_click) continue; - RefVector<Transform> transform_vec - = mgr.get_components_by_id<Transform>(button.game_object_id); - Transform & transform = transform_vec.front().get(); - - if (this->is_mouse_inside_button(mouse_pos, button, transform)) { - - button.on_click(); + const Metadata & metadata + = mgr.get_components_by_id<Metadata>(button.game_object_id).front(); + const Transform & transform + = mgr.get_components_by_id<Transform>(button.game_object_id).front(); + if (this->is_mouse_inside_button(mouse_pos, button, transform, cam_transform)) { + event_mgr.trigger_event<ButtonPressEvent>(metadata, metadata.game_object_id); } } } -bool InputSystem::is_mouse_inside_button(const vec2 & mouse_pos, const Button & button, - const Transform & transform) { - int actual_x = transform.position.x + button.offset.x; - int actual_y = transform.position.y + button.offset.y; - - int half_width = button.dimensions.x / 2; - int half_height = button.dimensions.y / 2; +bool InputSystem::is_mouse_inside_button( + const vec2 & mouse_pos, const Button & button, const Transform & transform, + const Transform & cam_transform +) { + vec2 actual_pos = transform.position + button.offset; + if (!button.world_space) { + actual_pos += cam_transform.position; + } + vec2 half_dimensions = button.dimensions / 2; - // Check if the mouse is within the button's boundaries - return mouse_pos.x >= actual_x - half_width && mouse_pos.x <= actual_x + half_width - && mouse_pos.y >= actual_y - half_height && mouse_pos.y <= actual_y + half_height; + return mouse_pos.x >= actual_pos.x - half_dimensions.x + && mouse_pos.x <= actual_pos.x + half_dimensions.x + && mouse_pos.y >= actual_pos.y - half_dimensions.y + && mouse_pos.y <= actual_pos.y + half_dimensions.y; } diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h index eefd9fe..37311cc 100644 --- a/src/crepe/system/InputSystem.h +++ b/src/crepe/system/InputSystem.h @@ -3,6 +3,8 @@ #include "../api/Config.h" #include "../facade/EventData.h" +#include "../api/Event.h" +#include "../api/Metadata.h" #include "../types.h" #include "../util/OptionalRef.h" @@ -13,6 +15,37 @@ namespace crepe { class Camera; class Button; class Transform; +//! Event triggered when a button is clicked +class ButtonPressEvent : public Event { +public: + //! Metadata of the button. + const Metadata & metadata; + /** + * \param metadata Metadata of the button pressed + */ + ButtonPressEvent(const Metadata & metadata) : metadata(metadata) {}; +}; +//! Event triggered when the mouse enters a button +class ButtonEnterEvent : public Event { +public: + //! Metadata of the button. + const Metadata & metadata; + /** + * \param metadata Metadata of the button pressed + */ + ButtonEnterEvent(const Metadata & metadata) : metadata(metadata) {}; +}; +//! Event triggered when the mouse leaves a button +class ButtonExitEvent : public Event { +public: + //! Metadata of the button. + const Metadata & metadata; + /** + * \param metadata Metadata of the button pressed + */ + ButtonExitEvent(const Metadata & metadata) : metadata(metadata) {}; +}; + /** * \brief Handles the processing of input events created by SDLContext * @@ -28,7 +61,7 @@ public: * \brief Updates the system, processing all input events. * This method processes all events and triggers corresponding actions. */ - void update() override; + void fixed_update() override; private: //! Stores the last position of the mouse when the button was pressed. @@ -46,8 +79,9 @@ private: * This method processes mouse events, adjusts the mouse position to world coordinates, * and triggers the appropriate mouse-specific event handling logic. */ - void handle_mouse_event(const EventData & event, const vec2 & camera_origin, - const Camera & current_cam); + void handle_mouse_event( + const EventData & event, const vec2 & camera_origin, const Camera & current_cam + ); /** * \brief Handles non-mouse-related events. * \param event The event data for the non-mouse event. @@ -61,20 +95,26 @@ private: * \param mouse_button The mouse button involved in the click. * \param world_mouse_x The X coordinate of the mouse in world space. * \param world_mouse_y The Y coordinate of the mouse in world space. + * \param current_cam The current active camera. * * This method processes the mouse click event and triggers the corresponding button action. */ - void handle_click(const MouseButton & mouse_button, const vec2 & mouse_pos); + void handle_click( + const MouseButton & mouse_button, const vec2 & mouse_pos, const Camera & current_cam + ); /** * \brief Handles the mouse movement event. * \param event_data The event data containing information about the mouse movement. * \param world_mouse_x The X coordinate of the mouse in world space. * \param world_mouse_y The Y coordinate of the mouse in world space. + * \param current_cam The current active camera. * * This method processes the mouse movement event and updates the button hover state. */ - void handle_move(const EventData & event_data, const vec2 & mouse_pos); + void handle_move( + const EventData & event_data, const vec2 & mouse_pos, const Camera & current_cam + ); /** * \brief Checks if the mouse position is inside the bounds of the button. @@ -82,10 +122,13 @@ private: * \param world_mouse_y The Y coordinate of the mouse in world space. * \param button The button to check. * \param transform The transform component of the button. + * \param cam_transform the transform of the current active camera * \return True if the mouse is inside the button, false otherwise. */ - bool is_mouse_inside_button(const vec2 & mouse_pos, const Button & button, - const Transform & transform); + bool is_mouse_inside_button( + const vec2 & mouse_pos, const Button & button, const Transform & transform, + const Transform & cam_transform + ); /** * \brief Handles the button press event, calling the on_click callback if necessary. diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 35a1d41..f026390 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -7,12 +7,13 @@ #include "../api/Transform.h" #include "../manager/ComponentManager.h" #include "../manager/LoopTimerManager.h" +#include "util/AbsolutePosition.h" #include "ParticleSystem.h" using namespace crepe; -void ParticleSystem::update() { +void ParticleSystem::fixed_update() { // Get all emitters const Mediator & mediator = this->mediator; LoopTimerManager & loop_timer = mediator.loop_timer; @@ -48,9 +49,11 @@ void ParticleSystem::update() { void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) { constexpr float DEG_TO_RAD = M_PI / 180.0; - vec2 initial_position = emitter.data.offset + transform.position; - float random_angle - = this->generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); + vec2 initial_position = AbsolutePosition::get_position(transform, emitter.data.offset); + float random_angle = this->generate_random_angle( + emitter.data.min_angle + transform.rotation, + emitter.data.max_angle + transform.rotation + ); float random_speed = this->generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); @@ -61,8 +64,9 @@ void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & for (Particle & particle : emitter.particles) { if (!particle.active) { - particle.reset(emitter.data.end_lifespan, initial_position, velocity, - random_angle); + particle.reset( + emitter.data.end_lifespan, initial_position, velocity, random_angle + ); break; } } @@ -80,8 +84,9 @@ void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & t for (Particle & particle : emitter.particles) { const vec2 & position = particle.position; - bool within_bounds = (position.x >= left && position.x <= right && position.y >= top - && position.y <= bottom); + bool within_bounds + = (position.x >= left && position.x <= right && position.y >= top + && position.y <= bottom); //if not within bounds do a reset or stop velocity if (!within_bounds) { if (emitter.data.boundary.reset_on_exit) { diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index 154521d..4296ff3 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -20,7 +20,7 @@ public: * \brief Updates all particle emitters by emitting particles, updating particle states, and * checking bounds. */ - void update() override; + void fixed_update() override; private: /** diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index 3b3b8ab..62f8132 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -12,8 +12,7 @@ using namespace crepe; -void PhysicsSystem::update() { - +void PhysicsSystem::fixed_update() { const Mediator & mediator = this->mediator; ComponentManager & mgr = mediator.component_manager; LoopTimerManager & loop_timer = mediator.loop_timer; diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h index 26152a5..5ed624f 100644 --- a/src/crepe/system/PhysicsSystem.h +++ b/src/crepe/system/PhysicsSystem.h @@ -18,7 +18,7 @@ public: * * It calculates new velocties and changes the postion in the transform. */ - void update() override; + void fixed_update() override; }; } // namespace crepe diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index 62d42ec..30bb422 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -16,6 +16,9 @@ #include "../facade/Texture.h" #include "../manager/ComponentManager.h" #include "../manager/ResourceManager.h" +#include "api/Text.h" +#include "facade/Font.h" +#include "util/AbsolutePosition.h" #include "RenderSystem.h" #include "types.h" @@ -67,14 +70,37 @@ RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) const { return sorted_objs; } -void RenderSystem::update() { +void RenderSystem::frame_update() { this->clear_screen(); this->render(); + this->render_text(); this->present_screen(); } -bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { +void RenderSystem::render_text() { + SDLContext & ctx = this->mediator.sdl_context; + ComponentManager & mgr = this->mediator.component_manager; + ResourceManager & resource_manager = this->mediator.resource_manager; + + RefVector<Text> texts = mgr.get_components_by_type<Text>(); + + for (Text & text : texts) { + if (!text.active) continue; + if (!text.font.has_value()) + text.font.emplace(ctx.get_font_from_name(text.font_family)); + + const Font & font = resource_manager.get<Font>(text.font.value()); + const auto & transform + = mgr.get_components_by_id<Transform>(text.game_object_id).front().get(); + ctx.draw_text(SDLContext::RenderText { + .text = text, + .font = font, + .transform = transform, + }); + } +} +bool RenderSystem::render_particle(const Sprite & sprite, const Transform & transform) { ComponentManager & mgr = this->mediator.component_manager; SDLContext & ctx = this->mediator.sdl_context; ResourceManager & resource_manager = this->mediator.resource_manager; @@ -92,29 +118,30 @@ bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) for (const Particle & p : em.particles) { if (!p.active) continue; + if (p.time_in_life < em.data.begin_lifespan) continue; - ctx.draw(SDLContext::RenderContext{ + ctx.draw(SDLContext::RenderContext { .sprite = sprite, .texture = res, .pos = p.position, - .angle = p.angle, - .scale = scale, + .angle = p.angle + transform.rotation, + .scale = transform.scale, }); } } return rendering_particles; } -void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) { +void RenderSystem::render_normal(const Sprite & sprite, const Transform & transform) { SDLContext & ctx = this->mediator.sdl_context; ResourceManager & resource_manager = this->mediator.resource_manager; const Texture & res = resource_manager.get<Texture>(sprite.source); - - ctx.draw(SDLContext::RenderContext{ + vec2 pos = AbsolutePosition::get_position(transform, sprite.data.position_offset); + ctx.draw(SDLContext::RenderContext { .sprite = sprite, .texture = res, - .pos = tm.position, - .angle = tm.rotation, - .scale = tm.scale, + .pos = pos, + .angle = transform.rotation, + .scale = transform.scale, }); } @@ -126,35 +153,16 @@ void RenderSystem::render() { ResourceManager & resource_manager = this->mediator.resource_manager; RefVector<Sprite> sorted_sprites = this->sort(sprites); RefVector<Text> text_components = mgr.get_components_by_type<Text>(); - for (Text & text : text_components) { - const Transform & transform - = mgr.get_components_by_id<Transform>(text.game_object_id).front().get(); - this->render_text(text, transform); - } + for (const Sprite & sprite : sorted_sprites) { if (!sprite.active) continue; const Transform & transform = mgr.get_components_by_id<Transform>(sprite.game_object_id).front().get(); - bool rendered_particles = this->render_particle(sprite, transform.scale); + bool rendered_particles = this->render_particle(sprite, transform); if (rendered_particles) continue; this->render_normal(sprite, transform); } } -void RenderSystem::render_text(Text & text, const Transform & tm) { - SDLContext & ctx = this->mediator.sdl_context; - - if (!text.font.has_value()) { - text.font.emplace(ctx.get_font_from_name(text.font_family)); - } - - ResourceManager & resource_manager = this->mediator.resource_manager; - - if (!text.font.has_value()) { - return; - } - const Asset & font_asset = text.font.value(); - const Font & res = resource_manager.get<Font>(font_asset); -} diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index 56a0553..627a743 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -24,7 +24,7 @@ public: * \brief Updates the RenderSystem for the current frame. * This method is called to perform all rendering operations for the current game frame. */ - void update() override; + void frame_update() override; private: //! Clears the screen in preparation for rendering. @@ -36,34 +36,28 @@ private: //! Updates the active camera used for rendering. void update_camera(); - //! Renders the whole screen + //! Renders all the sprites and particles void render(); + //! Renders all Text components + void render_text(); + +private: /** * \brief Renders all the particles on the screen from a given sprite. * * \param sprite renders the particles with given texture - * \param tm the Transform component for scale. This is not a const reference because each - * particle has a position and rotation that needs to overwrite the transform position and - * rotation without overwriting the current transform. and because the transform - * constructor is now protected i cannot make tmp inside + * \param transform the component that holds the position, rotation, and scale. * \return true if particles have been rendered */ - bool render_particle(const Sprite & sprite, const double & scale); - /** - * \brief Renders all Text components - * - * \param text The text component to be rendered. - * \param tm the Transform component that holds the position,rotation and scale - */ - void render_text(Text & text, const Transform & tm); + bool render_particle(const Sprite & sprite, const Transform & transform); /** * \brief renders a sprite with a Transform component on the screen * * \param sprite the sprite component that holds all the data - * \param tm the Transform component that holds the position,rotation and scale + * \param transform the Transform component that holds the position,rotation and scale */ - void render_normal(const Sprite & sprite, const Transform & tm); + void render_normal(const Sprite & sprite, const Transform & transform); /** * \brief sort a vector sprite objects with @@ -72,12 +66,6 @@ private: * \return returns a sorted reference vector */ RefVector<Sprite> sort(RefVector<Sprite> & objs) const; - - /** - * \todo Add text rendering using SDL_ttf for text components. - * \todo Implement a text component and a button component. - * \todo Consider adding text input functionality. - */ }; } // namespace crepe diff --git a/src/crepe/system/ReplaySystem.cpp b/src/crepe/system/ReplaySystem.cpp new file mode 100644 index 0000000..efc3be4 --- /dev/null +++ b/src/crepe/system/ReplaySystem.cpp @@ -0,0 +1,54 @@ +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "EventSystem.h" +#include "RenderSystem.h" +#include "ReplaySystem.h" + +using namespace crepe; +using namespace std; + +void ReplaySystem::fixed_update() { + ReplayManager & replay = this->mediator.replay_manager; + ReplayManager::State state = replay.get_state(); + ReplayManager::State last_state = this->last_state; + this->last_state = state; + + switch (state) { + case ReplayManager::IDLE: + break; + case ReplayManager::RECORDING: { + replay.frame_record(); + break; + } + case ReplayManager::PLAYING: { + if (last_state != ReplayManager::PLAYING) this->playback_begin(); + bool last = replay.frame_step(); + if (last) this->playback_end(); + break; + } + } +} + +void ReplaySystem::playback_begin() { + SystemManager & systems = this->mediator.system_manager; + ComponentManager & components = this->mediator.component_manager; + + this->playback = { + .components = components.save(), + .systems = systems.save(), + }; + + systems.disable_all(); + systems.get_system<RenderSystem>().active = true; + systems.get_system<ReplaySystem>().active = true; + systems.get_system<EventSystem>().active = true; +} + +void ReplaySystem::playback_end() { + SystemManager & systems = this->mediator.system_manager; + ComponentManager & components = this->mediator.component_manager; + + components.restore(this->playback.components); + systems.restore(this->playback.systems); +} diff --git a/src/crepe/system/ReplaySystem.h b/src/crepe/system/ReplaySystem.h new file mode 100644 index 0000000..bbc8d76 --- /dev/null +++ b/src/crepe/system/ReplaySystem.h @@ -0,0 +1,44 @@ +#pragma once + +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "System.h" + +namespace crepe { + +/** + * \brief ReplayManager helper system + * + * This system records and replays recordings using ReplayManager. + */ +class ReplaySystem : public System { +public: + using System::System; + + void fixed_update() override; + +private: + //! Last ReplayManager state + ReplayManager::State last_state = ReplayManager::IDLE; + + /** + * \brief Playback snapshot + * + * When starting playback, the component state is saved and most systems are disabled. This + * struct stores the engine state before ReplayManager::play is called. + */ + struct Snapshot { + ComponentManager::Snapshot components; + SystemManager::Snapshot systems; + }; + //! Before playback snapshot + Snapshot playback; + + //! Snapshot state and disable systems during playback + void playback_begin(); + //! Restore state from before \c playback_begin() + void playback_end(); +}; + +} // namespace crepe diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp index d6b2ca1..ed0c7cc 100644 --- a/src/crepe/system/ScriptSystem.cpp +++ b/src/crepe/system/ScriptSystem.cpp @@ -7,9 +7,21 @@ using namespace std; using namespace crepe; -void ScriptSystem::update() { - dbg_trace(); +void ScriptSystem::fixed_update() { + LoopTimerManager & timer = this->mediator.loop_timer; + duration_t delta_time = timer.get_scaled_fixed_delta_time(); + this->update(&Script::fixed_update, delta_time); +} + +void ScriptSystem::frame_update() { + LoopTimerManager & timer = this->mediator.loop_timer; + duration_t delta_time = timer.get_delta_time(); + this->update(&Script::frame_update, delta_time); +} +void ScriptSystem::update( + void (Script::*update_function)(duration_t), const duration_t & delta_time +) { ComponentManager & mgr = this->mediator.component_manager; RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>(); @@ -23,6 +35,7 @@ void ScriptSystem::update() { script->init(); script->initialized = true; } - script->update(); + + (*script.*update_function)(delta_time); } } diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h index 3db1b1e..257b615 100644 --- a/src/crepe/system/ScriptSystem.h +++ b/src/crepe/system/ScriptSystem.h @@ -1,5 +1,7 @@ #pragma once +#include "../manager/LoopTimerManager.h" + #include "System.h" namespace crepe { @@ -9,20 +11,27 @@ class Script; /** * \brief Script system * - * The script system is responsible for all \c BehaviorScript components, and - * calls the methods on classes derived from \c Script. + * The script system is responsible for all \c BehaviorScript components, and calls the methods + * on classes derived from \c Script. */ class ScriptSystem : public System { public: using System::System; + +public: + //! Call Script::fixed_update() on all active \c BehaviorScript instances + void fixed_update() override; + //! Call Script::frame_update() on all active \c BehaviorScript instances + void frame_update() override; + +private: /** - * \brief Call Script::update() on all active \c BehaviorScript instances + * \brief Call Script `*_update` member function on all active \c BehaviorScript instances * - * This routine updates all scripts sequentially using the Script::update() - * method. It also calls Script::init() if this has not been done before on - * the \c BehaviorScript instance. + * \note This routine also calls Script::init() if this has not been done before on the \c + * BehaviorScript instance. */ - void update() override; + void update(void (Script::*update_function)(duration_t), const duration_t & delta_time); }; } // namespace crepe diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h index 063dfbf..e2ce7eb 100644 --- a/src/crepe/system/System.h +++ b/src/crepe/system/System.h @@ -14,10 +14,12 @@ class ComponentManager; */ class System { public: - /** - * \brief Process all components this system is responsible for. - */ - virtual void update() = 0; + //! Code that runs in the fixed loop + virtual void fixed_update() {}; + //! Code that runs in the frame loop + virtual void frame_update() {}; + //! Indicates that the update functions of this system should be run + bool active = true; public: System(const Mediator & m); |