aboutsummaryrefslogtreecommitdiff
path: root/src/crepe/system/CollisionSystem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/crepe/system/CollisionSystem.cpp')
-rw-r--r--src/crepe/system/CollisionSystem.cpp454
1 files changed, 453 insertions, 1 deletions
diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp
index c74ca1d..2d5ce9d 100644
--- a/src/crepe/system/CollisionSystem.cpp
+++ b/src/crepe/system/CollisionSystem.cpp
@@ -1,5 +1,457 @@
+#include <cmath>
+#include <algorithm>
+#include <cstddef>
+#include <functional>
+#include <utility>
+#include <variant>
+#include <optional>
+
+#include "api/Event.h"
+#include "api/EventManager.h"
+#include "api/BoxCollider.h"
+#include "api/CircleCollider.h"
+#include "api/Metadata.h"
+#include "api/Vector2.h"
+#include "api/Rigidbody.h"
+#include "api/Transform.h"
+
+#include "ComponentManager.h"
#include "CollisionSystem.h"
+#include "Collider.h"
+#include "types.h"
+#include "util/OptionalRef.h"
using namespace crepe;
-void CollisionSystem::update() {}
+void CollisionSystem::update() {
+ std::vector<CollisionInternal> all_colliders;
+ game_object_id_t id = 0;
+ RefVector<Rigidbody> rigidbodies = this->component_manager.get_components_by_type<Rigidbody>();
+ // Collisions can only happen on object with a rigidbody
+ for(Rigidbody& rigidbody : rigidbodies) {
+ if (!rigidbody.active) continue;
+ id = rigidbody.game_object_id;
+ Transform& transform = this->component_manager.get_components_by_id<Transform>(id).front().get();
+ // Check if the boxcollider is active and has the same id as the rigidbody.
+ RefVector<BoxCollider> boxcolliders = this->component_manager.get_components_by_type<BoxCollider>();
+ for (BoxCollider& boxcollider : boxcolliders) {
+ if(boxcollider.game_object_id != id) continue;
+ if(!boxcollider.active) continue;
+ all_colliders.push_back(
+ {
+ .id = id,
+ .collider = collider_variant{boxcollider},
+ .transform = transform,
+ .rigidbody = rigidbody
+ }
+ );
+ }
+ // Check if the circlecollider is active and has the same id as the rigidbody.
+ RefVector<CircleCollider> circlecolliders = this->component_manager.get_components_by_type<CircleCollider>();
+ for (CircleCollider& circlecollider : circlecolliders) {
+ if(circlecollider.game_object_id != id) continue;
+ if(!circlecollider.active) continue;
+ all_colliders.push_back(
+ {
+ .id = id,
+ .collider = collider_variant{circlecollider},
+ .transform = transform,
+ .rigidbody = rigidbody
+ }
+ );
+ }
+ }
+
+ // Check between all colliders if there is a collision
+ std::vector<std::pair<CollisionInternal,CollisionInternal>> collided = this->gather_collisions(all_colliders);
+
+ // For both objects call the collision handler
+ for (auto& collision_pair : collided) {
+ this->collision_handler_request(collision_pair.first,collision_pair.second);
+ this->collision_handler_request(collision_pair.second,collision_pair.first);
+ }
+}
+
+void CollisionSystem::collision_handler_request(CollisionInternal& this_data,CollisionInternal& other_data){
+
+ CollisionInternalType type = this->get_collider_type(this_data.collider,other_data.collider);
+ std::pair<vec2,CollisionSystem::Direction> resolution_data = this->collision_handler(this_data,other_data,type);
+
+ OptionalRef<Metadata> this_metadata = this->component_manager.get_components_by_id<Metadata>(this_data.id).front().get();
+ OptionalRef<Metadata> other_metadata = this->component_manager.get_components_by_id<Metadata>(other_data.id).front().get();
+ OptionalRef<Collider> this_collider;
+ OptionalRef<Collider> other_collider;
+ switch (type) {
+ case CollisionInternalType::BOX_BOX:{
+ this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider);
+ other_collider = std::get<std::reference_wrapper<BoxCollider>>(other_data.collider);
+ break;
+ }
+ case CollisionInternalType::BOX_CIRCLE:{
+ this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider);
+ other_collider = std::get<std::reference_wrapper<CircleCollider>>(other_data.collider);
+ break;
+ }
+ case CollisionInternalType::CIRCLE_BOX:{
+ this_collider = std::get<std::reference_wrapper<CircleCollider>>(this_data.collider);
+ other_collider = std::get<std::reference_wrapper<BoxCollider>>(other_data.collider);
+ break;
+ }
+ case CollisionInternalType::CIRCLE_CIRCLE:{
+ this_collider = std::get<std::reference_wrapper<CircleCollider>>(this_data.collider);
+ other_collider = std::get<std::reference_wrapper<CircleCollider>>(other_data.collider);
+ break;
+ }
+ }
+
+ // collision info
+ crepe::CollisionSystem::CollisionInfo collision_info{
+ .this_collider = this_collider,
+ .this_transform = this_data.transform,
+ .this_rigidbody = this_data.rigidbody,
+ .this_metadata = this_metadata,
+ .other_collider = other_collider,
+ .other_transform = other_data.transform,
+ .other_rigidbody = other_data.rigidbody,
+ .other_metadata = other_metadata,
+ .resolution = resolution_data.first,
+ .resolution_direction = resolution_data.second,
+ };
+
+ // Determine if static needs to be called
+ this->determine_collision_handler(collision_info);
+}
+
+
+std::pair<vec2,CollisionSystem::Direction> CollisionSystem::collision_handler(CollisionInternal& data1,CollisionInternal& data2,CollisionInternalType type) {
+ vec2 resolution;
+ switch (type) {
+ case CollisionInternalType::BOX_BOX: {
+ const BoxCollider & collider1 = std::get<std::reference_wrapper<BoxCollider>>(data1.collider);
+ const BoxCollider & collider2 = std::get<std::reference_wrapper<BoxCollider>>(data2.collider);
+ vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, data1.rigidbody);
+ vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, data2.rigidbody);
+ resolution = this->get_box_box_resolution(collider1,collider2,collider_pos1,collider_pos2);
+ break;
+ }
+ case CollisionInternalType::BOX_CIRCLE: {
+ const BoxCollider & collider1 = std::get<std::reference_wrapper<BoxCollider>>(data1.collider);
+ const CircleCollider & collider2 = std::get<std::reference_wrapper<CircleCollider>>(data2.collider);
+ vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, data1.rigidbody);
+ vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, data2.rigidbody);
+ resolution = this->get_circle_box_resolution(collider2,collider1,collider_pos2,collider_pos1);
+ break;
+ }
+ case CollisionInternalType::CIRCLE_CIRCLE: {
+ const CircleCollider & collider1 = std::get<std::reference_wrapper<CircleCollider>>(data1.collider);
+ const CircleCollider & collider2 = std::get<std::reference_wrapper<CircleCollider>>(data2.collider);
+ vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, data1.rigidbody);
+ vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, data2.rigidbody);
+ resolution = this->get_circle_circle_resolution(collider1,collider2,collider_pos1,collider_pos2);
+ break;
+ }
+ case CollisionInternalType::CIRCLE_BOX: {
+ const CircleCollider & collider1 = std::get<std::reference_wrapper<CircleCollider>>(data1.collider);
+ const BoxCollider & collider2 = std::get<std::reference_wrapper<BoxCollider>>(data2.collider);
+ vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, data1.rigidbody);
+ vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, data2.rigidbody);
+ resolution = this->get_circle_box_resolution(collider1,collider2,collider_pos1,collider_pos2);
+ break;
+ }
+ }
+
+ Direction resolution_direction = Direction::NONE;
+ if(resolution.x != 0 && resolution.y > 0) {
+ resolution_direction = Direction::BOTH;
+ } else if (resolution.x != 0) {
+ resolution_direction = Direction::X_DIRECTION;
+ if(data1.rigidbody.data.linear_velocity.y != 0)
+ resolution.y = data1.rigidbody.data.linear_velocity.y * (resolution.x/data1.rigidbody.data.linear_velocity.x);
+ } else if (resolution.y != 0) {
+ resolution_direction = Direction::Y_DIRECTION;
+ if(data1.rigidbody.data.linear_velocity.x != 0)
+ resolution.x = data1.rigidbody.data.linear_velocity.x * (resolution.y/data1.rigidbody.data.linear_velocity.y);
+ }
+
+ return std::make_pair(resolution,resolution_direction);
+}
+
+vec2 CollisionSystem::get_box_box_resolution(const BoxCollider& box_collider1,const BoxCollider& box_collider2,const vec2& final_position1,const vec2& final_position2) const
+{
+ vec2 resolution; // Default resolution vector
+ vec2 delta = final_position2 - final_position1;
+
+ // Compute half-dimensions of the boxes
+ float half_width1 = box_collider1.dimensions.x / 2.0;
+ float half_height1 = box_collider1.dimensions.y / 2.0;
+ float half_width2 = box_collider2.dimensions.x / 2.0;
+ float half_height2 = box_collider2.dimensions.y / 2.0;
+
+ // Calculate overlaps along X and Y axes
+ float overlap_x = (half_width1 + half_width2) - std::abs(delta.x);
+ float overlap_y = (half_height1 + half_height2) - std::abs(delta.y);
+
+ // Check if there is a collision should always be true
+ if (overlap_x > 0 && overlap_y > 0) {
+ // Determine the direction of resolution
+ if (overlap_x < overlap_y) {
+ // Resolve along the X-axis (smallest overlap)
+ resolution.x = (delta.x > 0) ? -overlap_x : overlap_x;
+ } else if (overlap_y < overlap_x) {
+ // Resolve along the Y-axis (smallest overlap)
+ resolution.y = (delta.y > 0) ? -overlap_y : overlap_y;
+ } else {
+ // Equal overlap, resolve both directions with preference
+ resolution.x = (delta.x > 0) ? -overlap_x : overlap_x;
+ resolution.y = (delta.y > 0) ? -overlap_y : overlap_y;
+ }
+ }
+
+ return resolution;
+}
+
+vec2 CollisionSystem::get_circle_circle_resolution(const CircleCollider& circle_collider1, const CircleCollider& circle_collider2, const vec2& final_position1, const vec2& final_position2) const
+{
+ vec2 delta = final_position2 - final_position1;
+
+ // Compute the distance between the two circle centers
+ float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y);
+
+ // Compute the combined radii of the two circles
+ float combined_radius = circle_collider1.radius + circle_collider2.radius;
+
+ // Compute the penetration depth
+ float penetration_depth = combined_radius - distance;
+
+ // Normalize the delta vector to get the collision direction
+ vec2 collision_normal = delta / distance;
+
+ // Compute the resolution vector
+ vec2 resolution = collision_normal * penetration_depth;
+
+ return resolution;
+}
+
+vec2 CollisionSystem::get_circle_box_resolution(const CircleCollider& circle_collider, const BoxCollider& box_collider, const vec2& circle_position, const vec2& box_position) const
+{
+ vec2 delta = circle_position - box_position;
+
+ // Compute half-dimensions of the box
+ float half_width = box_collider.dimensions.x / 2.0f;
+ float half_height = box_collider.dimensions.y / 2.0f;
+
+ // Clamp circle center to the nearest point on the box
+ vec2 closest_point;
+ closest_point.x = std::clamp(delta.x, -half_width, half_width);
+ closest_point.y = std::clamp(delta.y, -half_height, half_height);
+
+ // Find the vector from the circle center to the closest point
+ vec2 closest_delta = delta - closest_point;
+
+ // Normalize the delta to get the collision direction
+ float distance = std::sqrt(closest_delta.x * closest_delta.x + closest_delta.y * closest_delta.y);
+ vec2 collision_normal = closest_delta / distance;
+
+ // Compute penetration depth
+ float penetration_depth = circle_collider.radius - distance;
+
+ // Compute the resolution vector
+ vec2 resolution = collision_normal * penetration_depth;
+
+ return resolution;
+}
+
+
+void CollisionSystem::determine_collision_handler(CollisionInfo& info){
+ // Check rigidbody type for static
+ if(info.this_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) return;
+ // If second body is static perform the static collision handler in this system
+ if(info.other_rigidbody.data.body_type == Rigidbody::BodyType::STATIC){
+ static_collision_handler(info);
+ };
+ // Call collision event for user
+ CollisionEvent data(info);
+ EventManager::get_instance().trigger_event<CollisionEvent>(data, info.this_collider.game_object_id);
+}
+
+void CollisionSystem::static_collision_handler(CollisionInfo& info){
+ // Move object back using calculate move back value
+ info.this_transform.position += info.resolution;
+
+ // If bounce is enabled mirror velocity
+ if(info.this_rigidbody.data.elastisity_coefficient > 0) {
+ if(info.resolution_direction == Direction::BOTH)
+ {
+ info.this_rigidbody.data.linear_velocity.y = -info.this_rigidbody.data.linear_velocity.y * info.this_rigidbody.data.elastisity_coefficient;
+ info.this_rigidbody.data.linear_velocity.x = -info.this_rigidbody.data.linear_velocity.x * info.this_rigidbody.data.elastisity_coefficient;
+ }
+ else if(info.resolution_direction == Direction::Y_DIRECTION) {
+ info.this_rigidbody.data.linear_velocity.y = -info.this_rigidbody.data.linear_velocity.y * info.this_rigidbody.data.elastisity_coefficient;
+ }
+ else if(info.resolution_direction == Direction::X_DIRECTION){
+ info.this_rigidbody.data.linear_velocity.x = -info.this_rigidbody.data.linear_velocity.x * info.this_rigidbody.data.elastisity_coefficient;
+ }
+ }
+ // Stop movement if bounce is disabled
+ else {
+ info.this_rigidbody.data.linear_velocity = {0,0};
+ }
+}
+
+std::vector<std::pair<CollisionSystem::CollisionInternal,CollisionSystem::CollisionInternal>> CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) {
+
+
+ // TODO:
+ // If no colliders skip
+ // Check if colliders has rigidbody if not skip
+
+ // TODO:
+ // If amount is higer than lets say 16 for now use quadtree otwerwise skip
+ // Quadtree code
+ // Quadtree is placed over the input vector
+
+ // Return data of collided colliders which are variants
+ std::vector<std::pair<CollisionInternal,CollisionInternal>> collisions_ret;
+ //using visit to visit the variant to access the active and id.
+ for (size_t i = 0; i < colliders.size(); ++i) {
+ for (size_t j = i + 1; j < colliders.size(); ++j) {
+ if(colliders[i].id == colliders[j].id) continue;
+ 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;
+}
+
+CollisionSystem::CollisionInternalType CollisionSystem::get_collider_type(const collider_variant& collider1,const collider_variant& collider2) const{
+ if(std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider1)){
+ if(std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2))
+ {
+ return CollisionInternalType::CIRCLE_CIRCLE;
+ }
+ else {
+ return CollisionInternalType::CIRCLE_BOX;
+ }
+ }
+ else {
+ if(std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2))
+ {
+ return CollisionInternalType::BOX_CIRCLE;
+ }
+ else {
+ return CollisionInternalType::BOX_BOX;
+ }
+ }
+}
+
+bool CollisionSystem::get_collision(const CollisionInternal& first_info,const CollisionInternal& second_info, CollisionInternalType type) const{
+ switch (type) {
+ case CollisionInternalType::BOX_BOX: {
+ const BoxCollider & box_collider1 = std::get<std::reference_wrapper<BoxCollider>>(first_info.collider);
+ const BoxCollider & box_collider2 = std::get<std::reference_wrapper<BoxCollider>>(second_info.collider);
+ return this->get_box_box_collision(box_collider1,box_collider2,first_info.transform,second_info.transform,second_info.rigidbody,second_info.rigidbody);
+ }
+ case CollisionInternalType::BOX_CIRCLE: {
+ const BoxCollider & box_collider = std::get<std::reference_wrapper<BoxCollider>>(first_info.collider);
+ const CircleCollider & circle_collider = std::get<std::reference_wrapper<CircleCollider>>(second_info.collider);
+ return this->get_box_circle_collision(box_collider,circle_collider,first_info.transform,second_info.transform,second_info.rigidbody,second_info.rigidbody);
+ }
+ case CollisionInternalType::CIRCLE_CIRCLE: {
+ const CircleCollider & circle_collider1 = std::get<std::reference_wrapper<CircleCollider>>(first_info.collider);
+ const CircleCollider & circle_collider2 = std::get<std::reference_wrapper<CircleCollider>>(second_info.collider);
+ return this->get_circle_circle_collision(circle_collider1,circle_collider2,first_info.transform,second_info.transform,second_info.rigidbody,second_info.rigidbody);
+ }
+ case CollisionInternalType::CIRCLE_BOX: {
+ const CircleCollider & circle_collider = std::get<std::reference_wrapper<CircleCollider>>(first_info.collider);
+ const BoxCollider & box_collider = std::get<std::reference_wrapper<BoxCollider>>(second_info.collider);
+ return this->get_box_circle_collision(box_collider,circle_collider,first_info.transform,second_info.transform,second_info.rigidbody,second_info.rigidbody);
+ }
+ }
+ return false;
+}
+
+
+bool CollisionSystem::get_box_box_collision(const BoxCollider& box1, const BoxCollider& box2, const Transform& transform1, const Transform& transform2, const Rigidbody& rigidbody1, const Rigidbody& rigidbody2) const
+{
+ // Get current positions of colliders
+ vec2 final_position1 = this->get_current_position(box1.offset,transform1,rigidbody1);
+ vec2 final_position2 = this->get_current_position(box2.offset,transform2,rigidbody2);
+
+ // Calculate half-extents (half width and half height)
+ float half_width1 = box1.dimensions.x / 2.0;
+ float half_height1 = box1.dimensions.y / 2.0;
+ float half_width2 = box2.dimensions.x / 2.0;
+ float half_height2 = box2.dimensions.y / 2.0;
+
+ // Check if the boxes overlap along the X and Y axes
+ return (final_position1.x + half_width1 > final_position2.x - half_width2 &&
+ final_position1.x - half_width1 < final_position2.x + half_width2 &&
+ final_position1.y + half_height1 > final_position2.y - half_height2 &&
+ final_position1.y - half_height1 < final_position2.y + half_height2);
+}
+
+bool CollisionSystem::get_box_circle_collision(const BoxCollider& box1, const CircleCollider& circle2, const Transform& transform1, const Transform& transform2, const Rigidbody& rigidbody1, const Rigidbody& rigidbody2) const {
+ // Get current positions of colliders
+ vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1);
+ vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2);
+
+ // Calculate box half-extents
+ float half_width = box1.dimensions.x / 2.0;
+ float half_height = box1.dimensions.y / 2.0;
+
+ // Find the closest point on the box to the circle's center
+ float closest_x = std::max(final_position1.x - half_width, std::min(final_position2.x, final_position1.x + half_width));
+ float closest_y = std::max(final_position1.y - half_height, std::min(final_position2.y, final_position1.y + half_height));
+
+ // Calculate the distance squared between the circle's center and the closest point on the box
+ float distance_x = final_position2.x - closest_x;
+ float distance_y = final_position2.y - closest_y;
+ float distance_squared = distance_x * distance_x + distance_y * distance_y;
+
+ // Compare distance squared with the square of the circle's radius
+ return distance_squared <= circle2.radius * circle2.radius;
+}
+
+bool CollisionSystem::get_circle_circle_collision(const CircleCollider& circle1, const CircleCollider& circle2, const Transform& transform1, const Transform& transform2, const Rigidbody& rigidbody1, const Rigidbody& rigidbody2) const {
+ // Get current positions of colliders
+ vec2 final_position1 = this->get_current_position(circle1.offset,transform1,rigidbody1);
+ vec2 final_position2 = this->get_current_position(circle2.offset,transform2,rigidbody2);
+
+ float distance_x = final_position1.x - final_position2.x;
+ float distance_y = final_position1.y - final_position2.y;
+ float distance_squared = distance_x * distance_x + distance_y * distance_y;
+
+ // Calculate the sum of the radii
+ float radius_sum = circle1.radius + circle2.radius;
+
+ // Check if the distance between the centers is less than or equal to the sum of the radii
+ return distance_squared <= radius_sum * radius_sum;
+}
+
+vec2 CollisionSystem::get_current_position(const vec2& collider_offset, const Transform& transform, const Rigidbody& rigidbody) const {
+ // Get the rotation in radians
+ float radians1 = transform.rotation * (M_PI / 180.0);
+
+ // Calculate total offset with scale
+ vec2 total_offset = (rigidbody.data.offset + collider_offset) * transform.scale;
+
+ // Rotate
+ float rotated_total_offset_x1 = total_offset.x * cos(radians1) - total_offset.y * sin(radians1);
+ float rotated_total_offset_y1 = total_offset.x * sin(radians1) + total_offset.y * cos(radians1);
+
+ // Final positions considering scaling and rotation
+ return(transform.position + vec2(rotated_total_offset_x1, rotated_total_offset_y1));
+
+}