diff options
| author | max-001 <maxsmits21@kpnmail.nl> | 2024-12-06 09:24:20 +0100 | 
|---|---|---|
| committer | max-001 <maxsmits21@kpnmail.nl> | 2024-12-06 09:24:20 +0100 | 
| commit | 5835e478ff8c9417b5b12c8fe2e21cc0311991be (patch) | |
| tree | b89c3924637dbb5c1bcb761d501f13f2ee6a8750 | |
| parent | 9cd9e3674d8b1c326c81b7896b9254408fb19972 (diff) | |
| parent | 453aeafda1503aeafa54b8f6e293936c1a3db5ea (diff) | |
Merge branch 'jaro/collision-system' of github.com:lonkaars/crepe into max/AI
| -rwxr-xr-x | asset/texture/circle.png | bin | 0 -> 509 bytes | |||
| -rwxr-xr-x | asset/texture/square.png | bin | 0 -> 162 bytes | |||
| -rw-r--r-- | src/crepe/Collider.cpp | 2 | ||||
| -rw-r--r-- | src/crepe/Collider.h | 13 | ||||
| -rw-r--r-- | src/crepe/api/BoxCollider.cpp | 10 | ||||
| -rw-r--r-- | src/crepe/api/BoxCollider.h | 22 | ||||
| -rw-r--r-- | src/crepe/api/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | src/crepe/api/CircleCollider.cpp | 8 | ||||
| -rw-r--r-- | src/crepe/api/CircleCollider.h | 16 | ||||
| -rw-r--r-- | src/crepe/api/Event.h | 4 | ||||
| -rw-r--r-- | src/crepe/api/LoopManager.cpp | 15 | ||||
| -rw-r--r-- | src/crepe/api/Rigidbody.cpp | 4 | ||||
| -rw-r--r-- | src/crepe/api/Rigidbody.h | 125 | ||||
| -rw-r--r-- | src/crepe/api/Script.h | 1 | ||||
| -rw-r--r-- | src/crepe/system/CollisionSystem.cpp | 553 | ||||
| -rw-r--r-- | src/crepe/system/CollisionSystem.h | 299 | ||||
| -rw-r--r-- | src/crepe/system/PhysicsSystem.cpp | 13 | ||||
| -rw-r--r-- | src/example/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/example/game.cpp | 263 | ||||
| -rw-r--r-- | src/test/CMakeLists.txt | 32 | ||||
| -rw-r--r-- | src/test/CollisionTest.cpp | 390 | ||||
| -rw-r--r-- | src/test/PhysicsTest.cpp | 10 | ||||
| -rw-r--r-- | src/test/Profiling.cpp | 230 | 
23 files changed, 1946 insertions, 69 deletions
| diff --git a/asset/texture/circle.png b/asset/texture/circle.pngBinary files differ new file mode 100755 index 0000000..0a92ac7 --- /dev/null +++ b/asset/texture/circle.png diff --git a/asset/texture/square.png b/asset/texture/square.pngBinary files differ new file mode 100755 index 0000000..d07ec98 --- /dev/null +++ b/asset/texture/square.png diff --git a/src/crepe/Collider.cpp b/src/crepe/Collider.cpp index bbec488..77e11c8 100644 --- a/src/crepe/Collider.cpp +++ b/src/crepe/Collider.cpp @@ -2,4 +2,4 @@  using namespace crepe; -Collider::Collider(game_object_id_t id) : Component(id) {} +Collider::Collider(game_object_id_t id, const vec2 & offset) : Component(id), offset(offset) {} diff --git a/src/crepe/Collider.h b/src/crepe/Collider.h index 827f83d..a08a68e 100644 --- a/src/crepe/Collider.h +++ b/src/crepe/Collider.h @@ -1,14 +1,23 @@  #pragma once  #include "Component.h" +#include "types.h"  namespace crepe {  class Collider : public Component {  public: -	Collider(game_object_id_t id); +	Collider(game_object_id_t id, const vec2 & offset); -	int size; +public: +	/** +	* \brief Offset of the collider relative to the rigidbody position. +	* +	* The `offset` defines the positional shift applied to the collider relative to the position of the rigidbody it is attached to. +	* This allows the collider to be placed at a different position than the rigidbody. +	*  +	*/ +	vec2 offset;  };  } // namespace crepe diff --git a/src/crepe/api/BoxCollider.cpp b/src/crepe/api/BoxCollider.cpp new file mode 100644 index 0000000..c097a24 --- /dev/null +++ b/src/crepe/api/BoxCollider.cpp @@ -0,0 +1,10 @@ +#include "BoxCollider.h" + +#include "../Collider.h" + +using namespace crepe; + +BoxCollider::BoxCollider(game_object_id_t game_object_id, const vec2 & offset, +						 const vec2 & dimensions) +	: Collider(game_object_id, offset), +	  dimensions(dimensions) {} diff --git a/src/crepe/api/BoxCollider.h b/src/crepe/api/BoxCollider.h new file mode 100644 index 0000000..89e43d8 --- /dev/null +++ b/src/crepe/api/BoxCollider.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../Collider.h" +#include "Vector2.h" +#include "types.h" + +namespace crepe { + +/** + * \brief A class representing a box-shaped collider. + *  + * This class is used for collision detection with other colliders (e.g., CircleCollider). + */ +class BoxCollider : public Collider { +public: +	BoxCollider(game_object_id_t game_object_id, const vec2 & offset, const vec2 & dimensions); + +	//! Width and height of the box collider +	vec2 dimensions; +}; + +} // namespace crepe diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 0355b72..9f61ef5 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -13,6 +13,8 @@ target_sources(crepe PUBLIC  	Metadata.cpp  	Camera.cpp  	Animator.cpp +	BoxCollider.cpp +	CircleCollider.cpp  	IKeyListener.cpp  	IMouseListener.cpp  	LoopManager.cpp @@ -45,6 +47,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Metadata.h  	Camera.h  	Animator.h +	BoxCollider.h +	CircleCollider.h  	EventHandler.h  	EventHandler.hpp  	Event.h diff --git a/src/crepe/api/CircleCollider.cpp b/src/crepe/api/CircleCollider.cpp new file mode 100644 index 0000000..a4271e9 --- /dev/null +++ b/src/crepe/api/CircleCollider.cpp @@ -0,0 +1,8 @@ +#include "CircleCollider.h" + +using namespace crepe; + +CircleCollider::CircleCollider(game_object_id_t game_object_id, const vec2 & offset, +							   float radius) +	: Collider(game_object_id, offset), +	  radius(radius) {} diff --git a/src/crepe/api/CircleCollider.h b/src/crepe/api/CircleCollider.h index e77a592..ebd1cb2 100644 --- a/src/crepe/api/CircleCollider.h +++ b/src/crepe/api/CircleCollider.h @@ -1,14 +1,22 @@  #pragma once + +#include "Vector2.h" +  #include "../Collider.h"  namespace crepe { +/** + * \brief A class representing a circle-shaped collider. + *  + * This class is used for collision detection with other colliders (e.g., BoxCollider). + */  class CircleCollider : public Collider {  public: -	CircleCollider(game_object_id_t game_object_id, int radius) -		: Collider(game_object_id), -		  radius(radius) {} -	int radius; +	CircleCollider(game_object_id_t game_object_id, const vec2 & offset, float radius); + +	//! Radius of the circle collider. +	float radius;  };  } // namespace crepe diff --git a/src/crepe/api/Event.h b/src/crepe/api/Event.h index 6298118..f2f3daf 100644 --- a/src/crepe/api/Event.h +++ b/src/crepe/api/Event.h @@ -112,10 +112,6 @@ public:  	//! scroll amount in y axis (from and away from the person).  	float scroll_delta = 0;  }; -/** - * \brief Event triggered during a collision between objects. - */ -class CollisionEvent : public Event {};  /**   * \brief Event triggered when text is submitted, e.g., from a text input. diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index e4b1609..2f8a0a7 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -7,6 +7,7 @@  #include "../system/PhysicsSystem.h"  #include "../system/RenderSystem.h"  #include "../system/ScriptSystem.h" +#include "manager/EventManager.h"  #include "LoopManager.h" @@ -35,7 +36,13 @@ void LoopManager::start() {  }  void LoopManager::set_running(bool running) { this->game_running = running; } -void LoopManager::fixed_update() {} +void LoopManager::fixed_update() { +	EventManager & ev = this->mediator.event_manager; +	ev.dispatch_events(); +	this->get_system<ScriptSystem>().update(); +	this->get_system<PhysicsSystem>().update(); +	this->get_system<CollisionSystem>().update(); +}  void LoopManager::loop() {  	LoopTimer & timer = this->loop_timer; @@ -43,13 +50,13 @@ void LoopManager::loop() {  	while (game_running) {  		timer.update(); - +		  		while (timer.get_lag() >= timer.get_fixed_delta_time()) {  			this->process_input();  			this->fixed_update();  			timer.advance_fixed_update();  		} - +		  		this->update();  		this->render(); @@ -64,8 +71,8 @@ void LoopManager::loop() {  void LoopManager::setup() {  	LoopTimer & timer = this->loop_timer; -  	this->game_running = true; +	this->scene_manager.load_next_scene();  	timer.start();  	timer.set_fps(200);  	this->scene_manager.load_next_scene(); diff --git a/src/crepe/api/Rigidbody.cpp b/src/crepe/api/Rigidbody.cpp index 576ca45..8213afb 100644 --- a/src/crepe/api/Rigidbody.cpp +++ b/src/crepe/api/Rigidbody.cpp @@ -10,6 +10,4 @@ void crepe::Rigidbody::add_force_linear(const vec2 & force) {  	this->data.linear_velocity += force;  } -void crepe::Rigidbody::add_force_angular(double force) { -	this->data.angular_velocity += force; -} +void crepe::Rigidbody::add_force_angular(float force) { this->data.angular_velocity += force; } diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h index 3b0588f..b0a24f7 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -1,9 +1,13 @@  #pragma once +#include <cmath> +#include <set> +  #include "../Component.h"  #include "types.h" +  namespace crepe {  /** @@ -34,11 +38,11 @@ public:  	 * the systems will not move the object.  	 */  	struct PhysicsConstraints { -		//! X constraint +		//! Prevent movement along X axis  		bool x = false; -		//! Y constraint +		//! Prevent movement along Y axis  		bool y = false; -		//! rotation constraint +		//! Prevent rotation  		bool rotation = false;  	}; @@ -50,29 +54,93 @@ public:  	 */  	struct Data {  		//! objects mass -		double mass = 0.0; -		//! gravtiy scale -		double gravity_scale = 0.0; -		//! Changes if physics apply +		float mass = 0.0; +		/** +		* \brief Gravity scale factor. +		* +		* The `gravity_scale` controls how much gravity affects the object. It is a multiplier applied to the default +		* gravity force, allowing for fine-grained control over how the object responds to gravity. +		*  +		*/ +		float gravity_scale = 0; + +		//! Defines the type of the physics body, which determines how the physics system interacts with the object.  		BodyType body_type = BodyType::DYNAMIC; -		//! linear velocity of object + +		/** +		* \name Linear (positional) motion +		* +		* These variables define the linear motion (movement along the position) of an object. +		* The linear velocity is applied to the object's position in each update of the PhysicsSystem. +		* The motion is affected by the object's linear velocity, its maximum velocity, and a coefficient +		* that can scale the velocity over time. +		* +		* \{ +		*/ +		//! Linear velocity of the object (speed and direction).  		vec2 linear_velocity; -		//! maximum linear velocity of object -		vec2 max_linear_velocity; -		//! linear damping of object -		vec2 linear_damping; -		//! angular velocity of object -		double angular_velocity = 0.0; -		//! max angular velocity of object -		double max_angular_velocity = 0.0; -		//! angular damping of object -		double angular_damping = 0.0; -		//! movements constraints of object +		//! Maximum linear velocity of the object. This limits the object's speed. +		vec2 max_linear_velocity = {INFINITY, INFINITY}; +		//! Linear velocity coefficient. This scales the object's velocity for adjustment or damping. +		vec2 linear_velocity_coefficient = {1, 1}; +		//! \} + +		/** +		* \name Angular (rotational) motion +		* +		* These variables define the angular motion (rotation) of an object. +		* The angular velocity determines how quickly the object rotates, while the maximum angular velocity +		* sets a limit on the rotation speed. The angular velocity coefficient applies damping or scaling +		* to the angular velocity, which can be used to simulate friction or other effects that slow down rotation. +		* +		* \{ +		*/ +		//! Angular velocity of the object, representing the rate of rotation (in degrees). +		float angular_velocity = 0; +		//! Maximum angular velocity of the object. This limits the maximum rate of rotation. +		float max_angular_velocity = INFINITY; +		//! Angular velocity coefficient. This scales the object's angular velocity, typically used for damping. +		float angular_velocity_coefficient = 1; +		//! \} + +		/** +		* \brief Movement constraints for an object. +		* +		* The `PhysicsConstraints` struct defines the constraints that restrict an object's movement +		* in certain directions or prevent rotation. These constraints effect only the physics system +		* to prevent the object from moving or rotating in specified ways. +		*  +		*/  		PhysicsConstraints constraints; -		//! if gravity applies -		bool use_gravity = true; -		//! if object bounces -		bool bounce = false; + +		/** +		* \brief Elasticity factor of the material (bounce factor). +		* +		* The `elasticity_coefficient` controls how much of the object's velocity is retained after a collision. +		* It represents the material's ability to bounce or recover velocity upon impact. The coefficient is a value +		* above 0.0. +		* +		*/ +		float elastisity_coefficient = 0.0; + +		/** +		* \brief Offset of all colliders relative to the object's transform position. +		* +		* The `offset` defines a positional shift applied to all colliders associated with the object, relative to the object's +		* transform position. This allows for the colliders to be placed at a different position than the object's actual +		* position, without modifying the object's transform itself. +		*  +		*/ +		vec2 offset; + +		/** +		 * \brief Defines the collision layers of a GameObject. +		 * +		 * The `collision_layers` vector specifies the layers that the GameObject will collide with. +		 * Each element in the vector represents a layer ID, and the GameObject will only detect  +		 * collisions with other GameObjects that belong to these layers. +		 */ +		std::set<int> collision_layers;  	};  public: @@ -96,7 +164,16 @@ public:  	 *   	 * \param force Vector2 that is added to the angular force.  	 */ -	void add_force_angular(double force); +	void add_force_angular(float force); + +protected: +	/** +	* Ensures there is at most one Rigidbody component per entity. +	* \return Always returns 1, indicating this constraint. +	*/ +	virtual int get_instances_max() const { return 1; } +	//! ComponentManager instantiates all components +	friend class ComponentManager;  };  } // namespace crepe diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index 1b339b0..1474a09 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -3,6 +3,7 @@  #include <vector>  #include "../manager/EventManager.h" +#include "system/CollisionSystem.h"  #include "../manager/Mediator.h"  #include "../types.h"  #include "../util/OptionalRef.h" diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp index c74ca1d..f75d0ad 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -1,5 +1,556 @@ +#include <algorithm> +#include <cmath> +#include <cstddef> +#include <functional> +#include <optional> +#include <utility> +#include <variant> + +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" + +#include "Collider.h"  #include "CollisionSystem.h" +#include "types.h" +#include "util/OptionalRef.h"  using namespace crepe; -void CollisionSystem::update() {} +void CollisionSystem::update() { +	std::vector<CollisionInternal> all_colliders; +	game_object_id_t id = 0; +	ComponentManager & mgr = this->mediator.component_manager; +	RefVector<Rigidbody> rigidbodies +		= mgr.get_components_by_type<Rigidbody>(); +	// Collisions can only happen on object with a rigidbody +	for (Rigidbody & rigidbody : rigidbodies) { +		if (!rigidbody.active) continue; +		id = rigidbody.game_object_id; +		Transform & transform +			= mgr.get_components_by_id<Transform>(id).front().get(); +		// Check if the boxcollider is active and has the same id as the rigidbody. +		RefVector<BoxCollider> boxcolliders +			= mgr.get_components_by_type<BoxCollider>(); +		for (BoxCollider & boxcollider : boxcolliders) { +			if (boxcollider.game_object_id != id) continue; +			if (!boxcollider.active) continue; +			all_colliders.push_back({.id = id, +									 .collider = collider_variant{boxcollider}, +									 .transform = transform, +									 .rigidbody = rigidbody}); +		} +		// Check if the circlecollider is active and has the same id as the rigidbody. +		RefVector<CircleCollider> circlecolliders +			= mgr.get_components_by_type<CircleCollider>(); +		for (CircleCollider & circlecollider : circlecolliders) { +			if (circlecollider.game_object_id != id) continue; +			if (!circlecollider.active) continue; +			all_colliders.push_back({.id = id, +									 .collider = collider_variant{circlecollider}, +									 .transform = transform, +									 .rigidbody = rigidbody}); +		} +	} + +	// Check between all colliders if there is a collision +	std::vector<std::pair<CollisionInternal, CollisionInternal>> collided +		= this->gather_collisions(all_colliders); + +	// For both objects call the collision handler +	for (auto & collision_pair : collided) { +		this->collision_handler_request(collision_pair.first, collision_pair.second); +		this->collision_handler_request(collision_pair.second, collision_pair.first); +	} +} + +void CollisionSystem::collision_handler_request(CollisionInternal & this_data, +												CollisionInternal & other_data) { + +	CollisionInternalType type +		= this->get_collider_type(this_data.collider, other_data.collider); +	std::pair<vec2, CollisionSystem::Direction> resolution_data +		= this->collision_handler(this_data, other_data, type); +	ComponentManager & mgr = this->mediator.component_manager; +	OptionalRef<Metadata> this_metadata +		= mgr.get_components_by_id<Metadata>(this_data.id).front().get(); +	OptionalRef<Metadata> other_metadata +		= mgr.get_components_by_id<Metadata>(other_data.id).front().get(); +	OptionalRef<Collider> this_collider; +	OptionalRef<Collider> other_collider; +	switch (type) { +		case CollisionInternalType::BOX_BOX: { +			this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); +			other_collider +				= std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); +			break; +		} +		case CollisionInternalType::BOX_CIRCLE: { +			this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); +			other_collider +				= std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); +			break; +		} +		case CollisionInternalType::CIRCLE_BOX: { +			this_collider +				= std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); +			other_collider +				= std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); +			break; +		} +		case CollisionInternalType::CIRCLE_CIRCLE: { +			this_collider +				= std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); +			other_collider +				= std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); +			break; +		} +	} + +	// collision info +	crepe::CollisionSystem::CollisionInfo collision_info{ +		.this_collider = this_collider, +		.this_transform = this_data.transform, +		.this_rigidbody = this_data.rigidbody, +		.this_metadata = this_metadata, +		.other_collider = other_collider, +		.other_transform = other_data.transform, +		.other_rigidbody = other_data.rigidbody, +		.other_metadata = other_metadata, +		.resolution = resolution_data.first, +		.resolution_direction = resolution_data.second, +	}; + +	// Determine if static needs to be called +	this->determine_collision_handler(collision_info); +} + +std::pair<vec2, CollisionSystem::Direction> +CollisionSystem::collision_handler(CollisionInternal & data1, CollisionInternal & data2, +								   CollisionInternalType type) { +	vec2 resolution; +	switch (type) { +		case CollisionInternalType::BOX_BOX: { +			const BoxCollider & collider1 +				= std::get<std::reference_wrapper<BoxCollider>>(data1.collider); +			const BoxCollider & collider2 +				= std::get<std::reference_wrapper<BoxCollider>>(data2.collider); +			vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, +															data1.rigidbody); +			vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, +															data2.rigidbody); +			resolution = this->get_box_box_resolution(collider1, collider2, collider_pos1, +													  collider_pos2); +			break; +		} +		case CollisionInternalType::BOX_CIRCLE: { +			const BoxCollider & collider1 +				= std::get<std::reference_wrapper<BoxCollider>>(data1.collider); +			const CircleCollider & collider2 +				= std::get<std::reference_wrapper<CircleCollider>>(data2.collider); +			vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, +															data1.rigidbody); +			vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, +															data2.rigidbody); +			resolution = this->get_circle_box_resolution(collider2, collider1, collider_pos2, +														 collider_pos1,true); +			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,false); +			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,bool inverse) 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; +	if(inverse) collision_normal = -collision_normal; +	// 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 & emgr = this->mediator.event_manager; +	emgr.trigger_event<CollisionEvent>( +		data, info.this_collider.game_object_id); +} + +void CollisionSystem::static_collision_handler(CollisionInfo & info) { +	// Move object back using calculate move back value +	info.this_transform.position += info.resolution; + +	// If bounce is enabled mirror velocity +	if (info.this_rigidbody.data.elastisity_coefficient > 0) { +		if (info.resolution_direction == Direction::BOTH) { +			info.this_rigidbody.data.linear_velocity.y +				= -info.this_rigidbody.data.linear_velocity.y +				  * info.this_rigidbody.data.elastisity_coefficient; +			info.this_rigidbody.data.linear_velocity.x +				= -info.this_rigidbody.data.linear_velocity.x +				  * info.this_rigidbody.data.elastisity_coefficient; +		} else if (info.resolution_direction == Direction::Y_DIRECTION) { +			info.this_rigidbody.data.linear_velocity.y +				= -info.this_rigidbody.data.linear_velocity.y +				  * info.this_rigidbody.data.elastisity_coefficient; +		} else if (info.resolution_direction == Direction::X_DIRECTION) { +			info.this_rigidbody.data.linear_velocity.x +				= -info.this_rigidbody.data.linear_velocity.x +				  * info.this_rigidbody.data.elastisity_coefficient; +		} +	} +	// Stop movement if bounce is disabled +	else { +		info.this_rigidbody.data.linear_velocity = {0, 0}; +	} +} + +std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>> +CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) { + +	// TODO: +	// If no colliders skip +	// Check if colliders has rigidbody if not skip + +	// TODO: +	// If amount is higer than lets say 16 for now use quadtree otwerwise skip +	// Quadtree code +	// Quadtree is placed over the input vector + +	// Return data of collided colliders which are variants +	std::vector<std::pair<CollisionInternal, CollisionInternal>> collisions_ret; +	//using visit to visit the variant to access the active and id. +	for (size_t i = 0; i < colliders.size(); ++i) { +		for (size_t j = i + 1; j < colliders.size(); ++j) { +			if (colliders[i].id == colliders[j].id) continue; +			if (!have_common_layer(colliders[i].rigidbody.data.collision_layers, +								   colliders[j].rigidbody.data.collision_layers)) +				continue; +			CollisionInternalType type +				= get_collider_type(colliders[i].collider, colliders[j].collider); +			if (!get_collision( +					{ +						.collider = colliders[i].collider, +						.transform = colliders[i].transform, +						.rigidbody = colliders[i].rigidbody, +					}, +					{ +						.collider = colliders[j].collider, +						.transform = colliders[j].transform, +						.rigidbody = colliders[j].rigidbody, +					}, +					type)) +				continue; +			collisions_ret.emplace_back(colliders[i], colliders[j]); +		} +	} + +	return collisions_ret; +} + +bool CollisionSystem::have_common_layer(const std::set<int> & layers1, +										const std::set<int> & layers2) { +	 +	// Check if any number is equal in the layers +	for (int num : layers1) { +			if (layers2.contains(num)) { +					// Common layer found +					return true; +					break; +			} +	} +	// No common layer found +	return false; +} + +CollisionSystem::CollisionInternalType +CollisionSystem::get_collider_type(const collider_variant & collider1, +								   const collider_variant & collider2) const { +	if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider1)) { +		if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { +			return CollisionInternalType::CIRCLE_CIRCLE; +		} else { +			return CollisionInternalType::CIRCLE_BOX; +		} +	} else { +		if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { +			return CollisionInternalType::BOX_CIRCLE; +		} else { +			return CollisionInternalType::BOX_BOX; +		} +	} +} + +bool CollisionSystem::get_collision(const CollisionInternal & first_info, +									const CollisionInternal & second_info, +									CollisionInternalType type) const { +	switch (type) { +		case CollisionInternalType::BOX_BOX: { +			const BoxCollider & box_collider1 +				= std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); +			const BoxCollider & box_collider2 +				= std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); +			return this->get_box_box_collision(box_collider1, box_collider2, +											   first_info.transform, second_info.transform, +											   second_info.rigidbody, second_info.rigidbody); +		} +		case CollisionInternalType::BOX_CIRCLE: { +			const BoxCollider & box_collider +				= std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); +			const CircleCollider & circle_collider +				= std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); +			return this->get_box_circle_collision( +				box_collider, circle_collider, first_info.transform, second_info.transform, +				second_info.rigidbody, second_info.rigidbody); +		} +		case CollisionInternalType::CIRCLE_CIRCLE: { +			const CircleCollider & circle_collider1 +				= std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); +			const CircleCollider & circle_collider2 +				= std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); +			return this->get_circle_circle_collision( +				circle_collider1, circle_collider2, first_info.transform, +				second_info.transform, second_info.rigidbody, second_info.rigidbody); +		} +		case CollisionInternalType::CIRCLE_BOX: { +			const CircleCollider & circle_collider +				= std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); +			const BoxCollider & box_collider +				= std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); +			return this->get_box_circle_collision( +				box_collider, circle_collider, first_info.transform, second_info.transform, +				second_info.rigidbody, second_info.rigidbody); +		} +	} +	return false; +} + +bool CollisionSystem::get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, +											const Transform & transform1, +											const Transform & transform2, +											const Rigidbody & rigidbody1, +											const Rigidbody & rigidbody2) const { +	// Get current positions of colliders +	vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); +	vec2 final_position2 = this->get_current_position(box2.offset, transform2, rigidbody2); + +	// Calculate half-extents (half width and half height) +	float half_width1 = box1.dimensions.x / 2.0; +	float half_height1 = box1.dimensions.y / 2.0; +	float half_width2 = box2.dimensions.x / 2.0; +	float half_height2 = box2.dimensions.y / 2.0; + +	// Check if the boxes overlap along the X and Y axes +	return (final_position1.x + half_width1 > final_position2.x - half_width2 +			&& final_position1.x - half_width1 < final_position2.x + half_width2 +			&& final_position1.y + half_height1 > final_position2.y - half_height2 +			&& final_position1.y - half_height1 < final_position2.y + half_height2); +} + +bool CollisionSystem::get_box_circle_collision(const BoxCollider & box1, +											   const CircleCollider & circle2, +											   const Transform & transform1, +											   const Transform & transform2, +											   const Rigidbody & rigidbody1, +											   const Rigidbody & rigidbody2) const { +	// Get current positions of colliders +	vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); +	vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2); + +	// Calculate box half-extents +	float half_width = box1.dimensions.x / 2.0; +	float half_height = box1.dimensions.y / 2.0; + +	// Find the closest point on the box to the circle's center +	float closest_x = std::max(final_position1.x - half_width, +							   std::min(final_position2.x, final_position1.x + half_width)); +	float closest_y = std::max(final_position1.y - half_height, +							   std::min(final_position2.y, final_position1.y + half_height)); + +	// Calculate the distance squared between the circle's center and the closest point on the box +	float distance_x = final_position2.x - closest_x; +	float distance_y = final_position2.y - closest_y; +	float distance_squared = distance_x * distance_x + distance_y * distance_y; + +	// Compare distance squared with the square of the circle's radius +	return distance_squared <= circle2.radius * circle2.radius-1; +} + +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 - 1; +} + +vec2 CollisionSystem::get_current_position(const vec2 & collider_offset, +										   const Transform & transform, +										   const Rigidbody & rigidbody) const { +	// Get the rotation in radians +	float radians1 = transform.rotation * (M_PI / 180.0); + +	// Calculate total offset with scale +	vec2 total_offset = (rigidbody.data.offset + collider_offset) * transform.scale; + +	// Rotate +	float rotated_total_offset_x1 +		= total_offset.x * cos(radians1) - total_offset.y * sin(radians1); +	float rotated_total_offset_y1 +		= total_offset.x * sin(radians1) + total_offset.y * cos(radians1); + +	// Final positions considering scaling and rotation +	return (transform.position + vec2(rotated_total_offset_x1, rotated_total_offset_y1)); +} diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h index c1a70d8..b978dbb 100644 --- a/src/crepe/system/CollisionSystem.h +++ b/src/crepe/system/CollisionSystem.h @@ -1,13 +1,312 @@  #pragma once +#include <optional> +#include <variant> +#include <vector> + +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" +#include "api/Event.h" + +#include "Collider.h"  #include "System.h"  namespace crepe { +//! A system responsible for detecting and handling collisions between colliders.  class CollisionSystem : public System {  public:  	using System::System; + +private: +	//! A variant type that can hold either a BoxCollider or a CircleCollider. +	using collider_variant = std::variant<std::reference_wrapper<BoxCollider>, +										  std::reference_wrapper<CircleCollider>>; + +	//! Enum representing the types of collider pairs for collision detection. +	enum class CollisionInternalType { +		BOX_BOX, +		CIRCLE_CIRCLE, +		BOX_CIRCLE, +		CIRCLE_BOX, +	}; + +	/** +		* \brief A structure to store the collision data of a single collider. +		*  +		* This structure all components and id that are for needed within this system when calculating or handeling collisions. +		* The transform and rigidbody are mostly needed for location and rotation. +		* In rigidbody additional info is written about what the body of the object is, +		* and how it should respond on a collision. +		*/ +	struct CollisionInternal { +		game_object_id_t id = 0; +		collider_variant collider; +		Transform & transform; +		Rigidbody & rigidbody; +	}; + +	//! Enum representing movement directions during collision resolution. +	enum class Direction { +		//! No movement required. +		NONE, +		//! Movement in the X direction. +		X_DIRECTION, +		//! Movement in the Y direction. +		Y_DIRECTION, +		//! Movement in both X and Y directions. +		BOTH +	}; + +public: +	/** +		* \brief Structure representing detailed collision information between two colliders. +		*  +		* Includes information about the colliding objects and the resolution data for handling the collision. +		*/ +	struct CollisionInfo { +		Collider & this_collider; +		Transform & this_transform; +		Rigidbody & this_rigidbody; +		Metadata & this_metadata; +		Collider & other_collider; +		Transform & other_transform; +		Rigidbody & other_rigidbody; +		Metadata & other_metadata; +		//! The resolution vector for the collision. +		vec2 resolution; +		//! The direction of movement for resolving the collision. +		Direction resolution_direction = Direction::NONE; +	}; + +public: +	//! Updates the collision system by checking for collisions between colliders and handling them.  	void update() override; + +private: +	/** +		* \brief Determines the type of collider pair from two colliders. +		*  +		* Uses std::holds_alternative to identify the types of the provided colliders. +		*  +		* \param collider1 First collider variant (BoxCollider or CircleCollider). +		* \param collider2 Second collider variant (BoxCollider or CircleCollider). +		* \return The combined type of the two colliders. +		*/ +	CollisionInternalType get_collider_type(const collider_variant & collider1, +											const collider_variant & collider2) const; + +	/** +		* \brief Calculates the current position of a collider. +		*  +		* Combines the Collider offset, Transform position, and Rigidbody offset to compute the position of the collider. +		*  +		* \param collider_offset The offset of the collider. +		* \param transform The Transform of the associated game object. +		* \param rigidbody The Rigidbody of the associated game object. +		* \return The calculated position of the collider. +		*/ +	vec2 get_current_position(const vec2 & collider_offset, const Transform & transform, +							  const Rigidbody & rigidbody) const; + +private: +	/** +		* \brief Handles collision resolution between two colliders. +		*  +		* Processes collision data and adjusts objects to resolve collisions and/or calls the user oncollision script function. +		*  +		* \param data1 Collision data for the first collider. +		* \param data2 Collision data for the second collider. +		*/ +	void collision_handler_request(CollisionInternal & data1, CollisionInternal & data2); + +	/** +		* \brief Resolves collision between two colliders and calculates the movement required. +		*  +		* Determines the displacement and direction needed to separate colliders based on their types. +		*  +		* \param data1 Collision data for the first collider. +		* \param data2 Collision data for the second collider. +		* \param type The type of collider pair. +		* \return A pair containing the resolution vector and direction for the first collider. +		*/ +	std::pair<vec2, Direction> collision_handler(CollisionInternal & data1, +												 CollisionInternal & data2, +												 CollisionInternalType type); + +	/** +		* \brief Calculates the resolution vector for two BoxColliders. +		*  +		* Computes the displacement required to separate two overlapping BoxColliders. +		*  +		* \param box_collider1 The first BoxCollider. +		* \param box_collider2 The second BoxCollider. +		* \param position1 The position of the first BoxCollider. +		* \param position2 The position of the second BoxCollider. +		* \return The resolution vector for the collision. +		*/ +	vec2 get_box_box_resolution(const BoxCollider & box_collider1, +								const BoxCollider & box_collider2, const vec2 & position1, +								const vec2 & position2) const; + +	/** +		* \brief Calculates the resolution vector for two CircleCollider. +		*  +		* Computes the displacement required to separate two overlapping CircleCollider. +		*  +		* \param circle_collider1 The first CircleCollider. +		* \param circle_collider2 The second CircleCollider. +		* \param position1 The position of the first CircleCollider. +		* \param position2 The position of the second CircleCollider. +		* \return The resolution vector for the collision. +		*/ +	vec2 get_circle_circle_resolution(const CircleCollider & circle_collider1, +									  const CircleCollider & circle_collider2, +									  const vec2 & final_position1, +									  const vec2 & final_position2) const; + +	/** +		* \brief Calculates the resolution vector for two CircleCollider. +		*  +		* Computes the displacement required to separate two overlapping CircleCollider. +		*  +		* \param circle_collider The first CircleCollider. +		* \param box_collider The second CircleCollider. +		* \param circle_position The position of the CircleCollider. +		* \param box_position The position of the BoxCollider. +		* \param inverse Inverted true if box circle collision, false if circle box collision (inverts the direction). +		* \return The resolution vector for the collision. +		*/ +	vec2 get_circle_box_resolution(const CircleCollider & circle_collider, +								   const BoxCollider & box_collider, +								   const vec2 & circle_position, +								   const vec2 & box_position,bool inverse) const; + +	/** +		* \brief Determines the appropriate collision handler for a collision. +		*  +		* Decides the correct resolution process based on the dynamic or static nature of the colliders involved. +		*  +		* \param info Collision information containing data about both colliders. +		*/ +	void determine_collision_handler(CollisionInfo & info); + +	/** +		* \brief Handles collisions involving static objects. +		*  +		* Resolves collisions by adjusting positions and modifying velocities if bounce is enabled. +		*  +		* \param info Collision information containing data about both colliders. +		*/ +	void static_collision_handler(CollisionInfo & info); + +private: +	/** +		* \brief Checks for collisions between colliders. +		*  +		* Identifies collisions and generates pairs of colliding objects for further processing. +		*  +		* \param colliders A collection of all active colliders. +		* \return A list of collision pairs with their associated data. +		*/ +	std::vector<std::pair<CollisionInternal, CollisionInternal>> +	gather_collisions(std::vector<CollisionInternal> & colliders); + +	/** +	 * \brief Checks if two collision layers have at least one common layer. +	 *  +	 * This function checks if there is any overlapping layer between the two input  +	 * collision layer vectors. It compares each layer from the first vector to see  +	 * if it exists in the second vector. If at least one common layer is found,  +	 * the function returns true, indicating that the two colliders share a common  +	 * collision layer. +	 *  +	 * \param layers1 A vector of collision layers for the first collider. +	 * \param layers2 A vector of collision layers for the second collider. +	 * \return Returns true if there is at least one common layer, false otherwise. +	 */ + +	bool have_common_layer(const std::set<int> & layers1, const std::set<int> & layers2); + +	/** +		* \brief Checks for collision between two colliders. +		*  +		* Calls the appropriate collision detection function based on the collider types. +		*  +		* \param first_info Collision data for the first collider. +		* \param second_info Collision data for the second collider. +		* \param type The type of collider pair. +		* \return True if a collision is detected, otherwise false. +		*/ +	bool get_collision(const CollisionInternal & first_info, +					   const CollisionInternal & second_info, +					   CollisionInternalType type) const; + +	/** +		* \brief Detects collisions between two BoxColliders. +		*  +		* \param box1 The first BoxCollider. +		* \param box2 The second BoxCollider. +		* \param transform1 Transform of the first object. +		* \param transform2 Transform of the second object. +		* \param rigidbody1 Rigidbody of the first object. +		* \param rigidbody2 Rigidbody of the second object. +		* \return True if a collision is detected, otherwise false. +		*/ +	bool get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, +							   const Transform & transform1, const Transform & transform2, +							   const Rigidbody & rigidbody1, +							   const Rigidbody & rigidbody2) const; + +	/** +	 * \brief Check collision for box on circle collider +	 * +	 * \param box1 The BoxCollider +	 * \param circle2 The CircleCollider +	 * \param transform1 Transform of the first object. +	 * \param transform2 Transform of the second object. +	 * \param rigidbody1 Rigidbody of the first object. +	 * \param rigidbody2 Rigidbody of the second object. +	 * \return True if a collision is detected, otherwise false. +	 */ +	bool get_box_circle_collision(const BoxCollider & box1, const CircleCollider & circle2, +								  const Transform & transform1, const Transform & transform2, +								  const Rigidbody & rigidbody1, +								  const Rigidbody & rigidbody2) const; + +	/** +	 * \brief Check collision for circle on circle collider +	 * +	 * \param circle1 First CircleCollider +	 * \param circle2 Second CircleCollider +	 * \param transform1 Transform of the first object. +	 * \param transform2 Transform of the second object. +	 * \param rigidbody1 Rigidbody of the first object. +	 * \param rigidbody2 Rigidbody of the second object. +	 * \return True if a collision is detected, otherwise false. +	 * +	 * \return status of collision +	 */ +	bool get_circle_circle_collision(const CircleCollider & circle1, +									 const CircleCollider & circle2, +									 const Transform & transform1, +									 const Transform & transform2, +									 const Rigidbody & rigidbody1, +									 const Rigidbody & rigidbody2) const; +}; + +/** + * \brief Event triggered during a collision between objects. + */ +class CollisionEvent : public Event { +public: +	crepe::CollisionSystem::CollisionInfo info; +	CollisionEvent(const crepe::CollisionSystem::CollisionInfo & collisionInfo) +		: info(collisionInfo) {}  };  } // namespace crepe diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index bebcf3d..ebf4439 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -25,17 +25,20 @@ void PhysicsSystem::update() {  					if (transform.game_object_id == rigidbody.game_object_id) {  						// Add gravity -						if (rigidbody.data.use_gravity) { +						if (rigidbody.data.gravity_scale > 0) {  							rigidbody.data.linear_velocity.y  								+= (rigidbody.data.mass * rigidbody.data.gravity_scale  									* gravity);  						}  						// Add damping -						if (rigidbody.data.angular_damping != 0) { -							rigidbody.data.angular_velocity *= rigidbody.data.angular_damping; +						if (rigidbody.data.angular_velocity_coefficient > 0) { +							rigidbody.data.angular_velocity +								*= rigidbody.data.angular_velocity_coefficient;  						} -						if (rigidbody.data.linear_damping != vec2{0, 0}) { -							rigidbody.data.linear_velocity *= rigidbody.data.linear_damping; +						if (rigidbody.data.linear_velocity_coefficient.x > 0 +							&& rigidbody.data.linear_velocity_coefficient.y > 0) { +							rigidbody.data.linear_velocity +								*= rigidbody.data.linear_velocity_coefficient;  						}  						// Max velocity check diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 0158d67..149c412 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -19,5 +19,6 @@ endfunction()  add_example(asset_manager)  add_example(savemgr)  add_example(rendering_particle) +add_example(game)  add_example(button)  add_example(AITest) diff --git a/src/example/game.cpp b/src/example/game.cpp new file mode 100644 index 0000000..be756bd --- /dev/null +++ b/src/example/game.cpp @@ -0,0 +1,263 @@ +#include "api/CircleCollider.h" +#include "manager/ComponentManager.h" +#include "api/Scene.h" +#include "manager/Mediator.h" +#include <crepe/api/BoxCollider.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/Event.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/LoopManager.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> +#include <crepe/api/Transform.h> +#include <crepe/api/Vector2.h> + +using namespace crepe; + +using namespace std; + +class MyScript1 : public Script { +	bool flip = false; +	bool oncollision(const CollisionEvent & test) { +		Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); +		return true; +	} +	bool keypressed(const KeyPressEvent & test) { +		Log::logf("Box script keypressed()"); +		switch (test.key) { +			case Keycode::A: +			{ +				Transform & tf = this->get_component<Transform>(); +				tf.position.x -= 1; +				break; +			} +			case Keycode::W: +			{ +				Transform & tf = this->get_component<Transform>(); +				tf.position.y -= 1; +				break; +			} +			case Keycode::S: +			{ +				Transform & tf = this->get_component<Transform>(); +				tf.position.y += 1; +				break; +			} +			case Keycode::D: +			{ +				Transform & tf = this->get_component<Transform>(); +				tf.position.x += 1; +				break; +			} +			case Keycode::E: +			{ +				if(flip){ +					flip = false; +					this->get_component<BoxCollider>().active = true; +					this->get_components<Sprite>()[0].get().active = true; +					this->get_component<CircleCollider>().active = false; +					this->get_components<Sprite>()[1].get().active = false; +				} +				else { +					flip = true; +					this->get_component<BoxCollider>().active = false; +					this->get_components<Sprite>()[0].get().active = false; +					this->get_component<CircleCollider>().active = true; +					this->get_components<Sprite>()[1].get().active = true; +				} +				 +				 +				//add collider switch +				break; +			} +			default: +			break; +		} +		return false; +	}  + +	void init() { +		Log::logf("init"); +		subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); +		subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool { return this->keypressed(ev); }); +	} +	void update() { +		// Retrieve component from the same GameObject this script is on +	} +	 +}; + +class MyScript2 : public Script { +	bool flip = false; +	bool oncollision(const CollisionEvent & test) { +		Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); +		return true; +	} +	bool keypressed(const KeyPressEvent & test) { +		Log::logf("Box script keypressed()"); +		switch (test.key) { +			case Keycode::LEFT: +			{ +				Transform & tf = this->get_component<Transform>(); +				tf.position.x -= 1; +				break; +			} +			case Keycode::UP: +			{ +				Transform & tf = this->get_component<Transform>(); +				tf.position.y -= 1; +				break; +			} +			case Keycode::DOWN: +			{ +				Transform & tf = this->get_component<Transform>(); +				tf.position.y += 1; +				break; +			} +			case Keycode::RIGHT: +			{ +				Transform & tf = this->get_component<Transform>(); +				tf.position.x += 1; +				break; +			} +			case Keycode::PAUSE: +			{ +				if(flip){ +					flip = false; +					this->get_component<BoxCollider>().active = true; +					this->get_components<Sprite>()[0].get().active = true; +					this->get_component<CircleCollider>().active = false; +					this->get_components<Sprite>()[1].get().active = false; +				} +				else { +					flip = true; +					this->get_component<BoxCollider>().active = false; +					this->get_components<Sprite>()[0].get().active = false; +					this->get_component<CircleCollider>().active = true; +					this->get_components<Sprite>()[1].get().active = true; +				} +				 +				 +				//add collider switch +				break; +			} +			default: +			break; +		} +		return false; +	}  + +	void init() { +		Log::logf("init"); +		subscribe<CollisionEvent>([this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); +		subscribe<KeyPressEvent>([this](const KeyPressEvent & ev) -> bool { return this->keypressed(ev); }); +	} +	void update() { +		// Retrieve component from the same GameObject this script is on +	} +	 +}; + +class ConcreteScene1 : public Scene { +public: +	using Scene::Scene; +	 +	void load_scene() { +		 +		Mediator & m = this->mediator; +		ComponentManager & mgr = m.component_manager; +		Color color(0, 0, 0, 255); + +		float screen_size_width = 320; +		float screen_size_height = 240; +		float world_collider = 1000; +		//define playable world +		GameObject world = mgr.new_object( +			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1); +		world.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 0, +			.gravity_scale = 0, +			.body_type = Rigidbody::BodyType::STATIC, +			.offset = {0, 0}, +			.collision_layers = {0}, +		}); +		world.add_component<BoxCollider>( +			vec2{0, 0 - (screen_size_height / 2 + world_collider / 2)}, +			vec2{world_collider, world_collider}); +		; // Top +		world.add_component<BoxCollider>(vec2{0, screen_size_height / 2 + world_collider / 2}, +										 vec2{world_collider, world_collider}); // Bottom +		world.add_component<BoxCollider>( +			vec2{0 - (screen_size_width / 2 + world_collider / 2), 0}, +			vec2{world_collider, world_collider}); // Left +		world.add_component<BoxCollider>(vec2{screen_size_width / 2 + world_collider / 2, 0}, +										 vec2{world_collider, world_collider}); // right +		world.add_component<Camera>(Color::WHITE, ivec2{static_cast<int>(screen_size_width), static_cast<int>(screen_size_height)}, vec2{screen_size_width, screen_size_height}, 1.0f); + +		GameObject game_object1 = mgr.new_object( +			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1); +		game_object1.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 1, +			.gravity_scale = 0, +			.body_type = Rigidbody::BodyType::DYNAMIC, +			.linear_velocity = {0, 0}, +			.constraints = {0, 0, 0}, +			.elastisity_coefficient = 1, +			.offset = {0, 0}, +			.collision_layers = {0}, +		}); +		// add box with boxcollider +		game_object1.add_component<BoxCollider>(vec2{0, 0}, vec2{20, 20}); +		game_object1.add_component<BehaviorScript>().set_script<MyScript1>(); +		auto img1 = Texture("asset/texture/square.png"); +		game_object1.add_component<Sprite>(img1, color, Sprite::FlipSettings{false, false}, 1, +										   1, 20); + +		//add circle with cirlcecollider deactiveated +		game_object1.add_component<CircleCollider>(vec2{0, 0}, 10).active = false; +		auto img2 = Texture("asset/texture/circle.png"); +		game_object1.add_component<Sprite>(img2, color, Sprite::FlipSettings{false, false}, 1, +										   1, 20).active = false; + + + +		GameObject game_object2 = mgr.new_object( +			"Name", "Tag", vec2{screen_size_width / 2, screen_size_height / 2}, 0, 1); +		game_object2.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 1, +			.gravity_scale = 0, +			.body_type = Rigidbody::BodyType::STATIC, +			.linear_velocity = {0, 0}, +			.constraints = {0, 0, 0}, +			.elastisity_coefficient = 1, +			.offset = {0, 0}, +			.collision_layers = {0}, +		}); +		// add box with boxcollider +		game_object2.add_component<BoxCollider>(vec2{0, 0}, vec2{20, 20}); +		game_object2.add_component<BehaviorScript>().set_script<MyScript2>(); +		auto img3 = Texture("asset/texture/square.png"); +		game_object2.add_component<Sprite>(img3, color, Sprite::FlipSettings{false, false}, 1, +										   1, 20); + +		//add circle with cirlcecollider deactiveated +		game_object2.add_component<CircleCollider>(vec2{0, 0}, 10).active = false; +		auto img4 = Texture("asset/texture/circle.png"); +		game_object2.add_component<Sprite>(img4, color, Sprite::FlipSettings{false, false}, 1, +										   1, 20).active = false; + +	} + +	string get_name() const { return "scene1"; } +}; + +int main(int argc, char * argv[]) { + +	LoopManager gameloop; +	gameloop.add_scene<ConcreteScene1>(); +	gameloop.start(); +	return 0; +} diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index e19d7de..a863598 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,18 +1,20 @@  target_sources(test_main PUBLIC  	main.cpp -	PhysicsTest.cpp -	ScriptTest.cpp -	ParticleTest.cpp -	AssetTest.cpp -	OptionalRefTest.cpp -	RenderSystemTest.cpp -	EventTest.cpp -	ECSTest.cpp -	SceneManagerTest.cpp -	ValueBrokerTest.cpp -	DBTest.cpp -	Vector2Test.cpp -	InputTest.cpp -	ScriptEventTest.cpp -	ScriptSceneTest.cpp +	CollisionTest.cpp +	# PhysicsTest.cpp +	# ScriptTest.cpp +	# ParticleTest.cpp +	# AssetTest.cpp +	# OptionalRefTest.cpp +	# RenderSystemTest.cpp +	# EventTest.cpp +	# ECSTest.cpp +	# SceneManagerTest.cpp +	# ValueBrokerTest.cpp +	# DBTest.cpp +	# Vector2Test.cpp +	# InputTest.cpp +	# ScriptEventTest.cpp +	# ScriptSceneTest.cpp +	# Profiling.cpp  ) diff --git a/src/test/CollisionTest.cpp b/src/test/CollisionTest.cpp new file mode 100644 index 0000000..a683b1f --- /dev/null +++ b/src/test/CollisionTest.cpp @@ -0,0 +1,390 @@ +#include "api/BoxCollider.h" +#include "manager/Mediator.h" +#include <cmath> +#include <cstddef> +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/manager/ComponentManager.h> +#include <crepe/manager/Mediator.h> +#include <crepe/api/Event.h> +#include <crepe/manager/EventManager.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Transform.h> +#include <crepe/system/CollisionSystem.h> +#include <crepe/system/ScriptSystem.h> +#include <crepe/types.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class CollisionHandler : public Script { +public: +	int box_id; +	function<void(const CollisionEvent & ev)> test_fn = [](const CollisionEvent & ev) {}; + +	CollisionHandler(int box_id) { this->box_id = box_id; } + +	bool on_collision(const CollisionEvent & ev) { +		//Log::logf("Box {} script on_collision()", box_id); +		test_fn(ev); +		return true; +	} + +	void init() { +		subscribe<CollisionEvent>( +			[this](const CollisionEvent & ev) -> bool { return this->on_collision(ev); }); +	} +	void update() { +		// Retrieve component from the same GameObject this script is on +	} +}; + +class CollisionTest : public Test { +public: +	Mediator m; +	ComponentManager mgr{m}; +	CollisionSystem collision_sys{m}; +	ScriptSystem script_sys{m}; + +	GameObject world = mgr.new_object("world", "", {50, 50}); +	GameObject game_object1 = mgr.new_object("object1", "", {50, 50}); +	GameObject game_object2 = mgr.new_object("object2", "", {50, 30}); + +	CollisionHandler * script_object1_ref = nullptr; +	CollisionHandler * script_object2_ref = nullptr; + +	void SetUp() override { +		world.add_component<Rigidbody>(Rigidbody::Data{ +			// TODO: remove unrelated properties: +			.body_type = Rigidbody::BodyType::STATIC, +			.offset = {0, 0}, +		}); +		// Create a box with an inner size of 10x10 units +		world.add_component<BoxCollider>(vec2{0, -100}, vec2{100, 100}); // Top +		world.add_component<BoxCollider>(vec2{0, 100}, vec2{100, 100}); // Bottom +		world.add_component<BoxCollider>(vec2{-100, 0}, vec2{100, 100}); // Left +		world.add_component<BoxCollider>(vec2{100, 0}, vec2{100, 100}); // right + +		game_object1.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 1, +			.gravity_scale = 0.01, +			.body_type = Rigidbody::BodyType::DYNAMIC, +			.linear_velocity = {0, 0}, +			.constraints = {0, 0, 0}, +			.elastisity_coefficient = 1, +			.offset = {0, 0}, +			.collision_layers = {0}, +		}); +		game_object1.add_component<BoxCollider>(vec2{0, 0}, vec2{10, 10}); +		BehaviorScript & script_object1 +			= game_object1.add_component<BehaviorScript>().set_script<CollisionHandler>(1); +		script_object1_ref = static_cast<CollisionHandler *>(script_object1.script.get()); +		ASSERT_NE(script_object1_ref, nullptr); + +		game_object2.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 1, +			.gravity_scale = 0.01, +			.body_type = Rigidbody::BodyType::DYNAMIC, +			.linear_velocity = {0, 0}, +			.constraints = {0, 0, 0}, +			.elastisity_coefficient = 1, +			.offset = {0, 0}, +			.collision_layers = {0}, +		}); +		game_object2.add_component<BoxCollider>(vec2{0, 0}, vec2{10, 10}); +		BehaviorScript & script_object2 +			= game_object2.add_component<BehaviorScript>().set_script<CollisionHandler>(2); +		script_object2_ref = static_cast<CollisionHandler *>(script_object2.script.get()); +		ASSERT_NE(script_object2_ref, nullptr); + +		// Ensure Script::init() is called on all BehaviorScript instances +		script_sys.update(); +	} +}; + +TEST_F(CollisionTest, collision_example) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +	}; +	EXPECT_FALSE(collision_happend); +	collision_sys.update(); +	EXPECT_FALSE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_both_no_velocity) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, 10); +		EXPECT_EQ(ev.info.resolution.y, 10); +		EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 10); +		EXPECT_EQ(ev.info.resolution.y, 10); +		EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 30}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_x_direction_no_velocity) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, -5); +		EXPECT_EQ(ev.info.resolution.y, 0); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::X_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 5); +		EXPECT_EQ(ev.info.resolution.y, 0); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::X_DIRECTION); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {45, 30}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_y_direction_no_velocity) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, 0); +		EXPECT_EQ(ev.info.resolution.y, -5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::Y_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 0); +		EXPECT_EQ(ev.info.resolution.y, 5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::Y_DIRECTION); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 25}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_both) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, 10); +		EXPECT_EQ(ev.info.resolution.y, 10); +		EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 10); +		EXPECT_EQ(ev.info.resolution.y, 10); +		EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 30}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.linear_velocity = {10, 10}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_x_direction) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, -5); +		EXPECT_EQ(ev.info.resolution.y, -5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::X_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 5); +		EXPECT_EQ(ev.info.resolution.y, 5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::X_DIRECTION); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {45, 30}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.linear_velocity = {10, 10}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_dynamic_y_direction) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, -5); +		EXPECT_EQ(ev.info.resolution.y, -5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::Y_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 2); +		EXPECT_EQ(ev.info.resolution.x, 5); +		EXPECT_EQ(ev.info.resolution.y, 5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::Y_DIRECTION); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 25}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.linear_velocity = {10, 10}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_both) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, 10); +		EXPECT_EQ(ev.info.resolution.y, 10); +		EXPECT_EQ(ev.info.resolution_direction, crepe::CollisionSystem::Direction::BOTH); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		// is static should not be called +		FAIL(); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 30}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_x_direction) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, -5); +		EXPECT_EQ(ev.info.resolution.y, -5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::X_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		// is static should not be called +		FAIL(); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {45, 30}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_y_direction) { +	bool collision_happend = false; +	script_object1_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.resolution.x, -5); +		EXPECT_EQ(ev.info.resolution.y, -5); +		EXPECT_EQ(ev.info.resolution_direction, +				  crepe::CollisionSystem::Direction::Y_DIRECTION); +	}; +	script_object2_ref->test_fn = [&collision_happend](const CollisionEvent & ev) { +		// is static should not be called +		FAIL(); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {50, 25}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} + +TEST_F(CollisionTest, collision_box_box_static_multiple) { //todo check visually +	bool collision_happend = false; +	float offset_value = 0; +	float resolution = 0; +	script_object1_ref->test_fn = [&](const CollisionEvent & ev) { +		collision_happend = true; +		EXPECT_EQ(ev.info.this_collider.game_object_id, 1); +		EXPECT_EQ(ev.info.this_collider.offset.x, offset_value); +		EXPECT_EQ(ev.info.resolution.x, resolution); +	}; +	script_object2_ref->test_fn = [&](const CollisionEvent & ev) { +		// is static should not be called +		FAIL(); +	}; +	EXPECT_FALSE(collision_happend); +	Transform & tf = this->mgr.get_components_by_id<Transform>(1).front().get(); +	tf.position = {45, 30}; +	Rigidbody & rg1 = this->mgr.get_components_by_id<Rigidbody>(1).front().get(); +	rg1.data.linear_velocity = {10, 10}; +	Rigidbody & rg2 = this->mgr.get_components_by_id<Rigidbody>(2).front().get(); +	rg2.data.body_type = crepe::Rigidbody::BodyType::STATIC; +	BoxCollider & bxc = this->mgr.get_components_by_id<BoxCollider>(1).front().get(); +	bxc.offset = {5, 0}; +	this->game_object1.add_component<BoxCollider>(vec2{-5, 0}, vec2{10, 10}); +	offset_value = 5; +	resolution = 10; +	collision_sys.update(); +	offset_value = -5; +	resolution = 10; +	tf.position = {55, 30}; +	collision_sys.update(); +	EXPECT_TRUE(collision_happend); +} diff --git a/src/test/PhysicsTest.cpp b/src/test/PhysicsTest.cpp index 43af8e4..43d2931 100644 --- a/src/test/PhysicsTest.cpp +++ b/src/test/PhysicsTest.cpp @@ -30,8 +30,6 @@ public:  				.max_linear_velocity = vec2{10, 10},  				.max_angular_velocity = 10,  				.constraints = {0, 0}, -				.use_gravity = true, -				.bounce = false,  			});  		}  		transforms = mgr.get_components_by_id<Transform>(0); @@ -107,16 +105,16 @@ TEST_F(PhysicsTest, movement) {  	EXPECT_EQ(transform.position.y, 1);  	EXPECT_EQ(transform.rotation, 1); -	rigidbody.data.linear_damping.x = 0.5; -	rigidbody.data.linear_damping.y = 0.5; -	rigidbody.data.angular_damping = 0.5; +	rigidbody.data.linear_velocity_coefficient.x = 0.5; +	rigidbody.data.linear_velocity_coefficient.y = 0.5; +	rigidbody.data.angular_velocity_coefficient = 0.5;  	system.update();  	EXPECT_EQ(rigidbody.data.linear_velocity.x, 0.5);  	EXPECT_EQ(rigidbody.data.linear_velocity.y, 0.5);  	EXPECT_EQ(rigidbody.data.angular_velocity, 0.5);  	rigidbody.data.constraints = {1, 1, 0}; -	rigidbody.data.angular_damping = 0; +	rigidbody.data.angular_velocity_coefficient = 0;  	rigidbody.data.max_angular_velocity = 1000;  	rigidbody.data.angular_velocity = 360;  	system.update(); diff --git a/src/test/Profiling.cpp b/src/test/Profiling.cpp new file mode 100644 index 0000000..91be769 --- /dev/null +++ b/src/test/Profiling.cpp @@ -0,0 +1,230 @@ +#include "manager/Mediator.h" +#include "system/ParticleSystem.h" +#include "system/PhysicsSystem.h" +#include "system/RenderSystem.h" +#include <chrono> +#include <cmath> +#include <gtest/gtest.h> + +#define private public +#define protected public + +#include <crepe/manager/ComponentManager.h> +#include <crepe/api/Event.h> +#include <crepe/manager/EventManager.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/ParticleEmitter.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Script.h> +#include <crepe/api/Transform.h> +#include <crepe/system/CollisionSystem.h> +#include <crepe/system/ScriptSystem.h> +#include <crepe/types.h> +#include <crepe/util/Log.h> + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; +using namespace testing; + +class TestScript : public Script { +	bool oncollision(const CollisionEvent & test) { +		Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); +		return true; +	} +	void init() { +		subscribe<CollisionEvent>( +			[this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); +	} +	void update() { +		// Retrieve component from the same GameObject this script is on +	} +}; + +class Profiling : public Test { +public: +	// Config for test +	// Minimum amount to let test pass +	const int min_gameobject_count = 100; +	// Maximum amount to stop test +	const int max_gameobject_count = 150; +	// Amount of times a test runs to calculate average +	const int average = 5; +	// Maximum duration to stop test +	const std::chrono::microseconds duration = 16000us; + +	Mediator m; +	ComponentManager mgr{m}; +	// Add system used for profling tests +	CollisionSystem collision_sys{m}; +	PhysicsSystem physics_sys{m}; +	ParticleSystem particle_sys{m}; +	RenderSystem render_sys{m}; +	ScriptSystem script_sys{m}; + +	// Test data +	std::map<std::string, std::chrono::microseconds> timings; +	int game_object_count = 0; +	std::chrono::microseconds total_time = 0us; + +	void SetUp() override { + +		GameObject do_not_use = mgr.new_object("DO_NOT_USE", "", {0, 0}); +		do_not_use.add_component<Camera>(Color::WHITE, ivec2{1080, 720}, vec2{2000, 2000}, +										 1.0f); +		// initialize systems here: +		//calls init +		script_sys.update(); +		//creates window +		render_sys.update(); +	} + +	// Helper function to time an update call and store its duration +	template <typename Func> +	std::chrono::microseconds time_function(const std::string & name, Func && func) { +		auto start = std::chrono::steady_clock::now(); +		func(); +		auto end = std::chrono::steady_clock::now(); +		std::chrono::microseconds duration +			= std::chrono::duration_cast<std::chrono::microseconds>(end - start); +		timings[name] += duration; +		return duration; +	} + +	// Run and profile all systems, return the total time in milliseconds +	std::chrono::microseconds run_all_systems() { +		std::chrono::microseconds total_microseconds = 0us; +		total_microseconds += time_function("PhysicsSystem", [&]() { physics_sys.update(); }); +		total_microseconds +			+= time_function("CollisionSystem", [&]() { collision_sys.update(); }); +		total_microseconds +			+= time_function("ParticleSystem", [&]() { particle_sys.update(); }); +		total_microseconds += time_function("RenderSystem", [&]() { render_sys.update(); }); +		return total_microseconds; +	} + +	// Print timings of all functions +	void log_timings() const { +		std::string result = "\nFunction timings:\n"; + +		for (const auto & [name, duration] : timings) { +			result += name + " took " + std::to_string(duration.count() / 1000.0 / average) +					  + " ms (" + std::to_string(duration.count() / average) + " µs).\n"; +		} + +		result += "Total time: " + std::to_string(this->total_time.count() / 1000.0 / average) +				  + " ms (" + std::to_string(this->total_time.count() / average) + " µs)\n"; + +		result += "Amount of gameobjects: " + std::to_string(game_object_count) + "\n"; + +		GTEST_LOG_(INFO) << result; +	} + +	void clear_timings() { +		for (auto & [key, value] : timings) { +			value = std::chrono::microseconds(0); +		} +	} +}; + +TEST_F(Profiling, Profiling_1) { +	while (this->total_time / this->average < this->duration) { + +		{ +			//define gameobject used for testing +			GameObject gameobject = mgr.new_object("gameobject", "", {0, 0}); +		} + +		this->game_object_count++; + +		this->total_time = 0us; +		clear_timings(); + +		for (int amount = 0; amount < this->average; amount++) { +			this->total_time += run_all_systems(); +		} + +		if (this->game_object_count >= this->max_gameobject_count) break; +	} +	log_timings(); +	EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} + +TEST_F(Profiling, Profiling_2) { +	while (this->total_time / this->average < this->duration) { + +		{ +			//define gameobject used for testing +			GameObject gameobject = mgr.new_object( +				"gameobject", "", {static_cast<float>(game_object_count * 2), 0}); +			gameobject.add_component<Rigidbody>(Rigidbody::Data{ +				.gravity_scale = 0.0, +				.body_type = Rigidbody::BodyType::STATIC, +			}); +			gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1}); +			 +			gameobject.add_component<BehaviorScript>().set_script<TestScript>(); +			Color color(0, 0, 0, 0); +			auto img = Texture("asset/texture/green_square.png"); +			Sprite & test_sprite = gameobject.add_component<Sprite>( +				img, color, Sprite::FlipSettings{false, false}, 1, 1, 500); +		} + +		this->game_object_count++; + +		this->total_time = 0us; +		clear_timings(); +		for (int amount = 0; amount < this->average; amount++) { +			this->total_time += run_all_systems(); +		} + +		if (this->game_object_count >= this->max_gameobject_count) break; +	} +	log_timings(); +	EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} + +TEST_F(Profiling, Profiling_3) { +	while (this->total_time / this->average < this->duration) { + +		{ +			//define gameobject used for testing +			GameObject gameobject = mgr.new_object( +				"gameobject", "", {static_cast<float>(game_object_count * 2), 0}); +			gameobject.add_component<Rigidbody>(Rigidbody::Data{ +				.gravity_scale = 0, +				.body_type = Rigidbody::BodyType::STATIC, +			}); +			gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1}); +			gameobject.add_component<BehaviorScript>().set_script<TestScript>(); +			Color color(0, 0, 0, 0); +			auto img = Texture("asset/texture/green_square.png"); +			Sprite & test_sprite = gameobject.add_component<Sprite>( +				img, color, Sprite::FlipSettings{false, false}, 1, 1, 500); +			auto & test = gameobject.add_component<ParticleEmitter>(ParticleEmitter::Data{ +				.max_particles = 10, +				.emission_rate = 100, +				.end_lifespan = 100000, +				.boundary{ +					.width = 1000, +					.height = 1000, +					.offset = vec2{0, 0}, +					.reset_on_exit = false, +				}, +				.sprite = test_sprite, +			}); +		} +		render_sys.update(); +		this->game_object_count++; + +		this->total_time = 0us; +		clear_timings(); +		for (int amount = 0; amount < this->average; amount++) { +			this->total_time += run_all_systems(); +		} + +		if (this->game_object_count >= this->max_gameobject_count) break; +	} +	log_timings(); +	EXPECT_GE(this->game_object_count, this->min_gameobject_count); +} |