diff options
Diffstat (limited to 'src/crepe')
77 files changed, 1821 insertions, 1047 deletions
| diff --git a/src/crepe/Component.cpp b/src/crepe/Component.cpp index acfd35c..ae76e65 100644 --- a/src/crepe/Component.cpp +++ b/src/crepe/Component.cpp @@ -1,5 +1,17 @@  #include "Component.h"  using namespace crepe; +using namespace std;  Component::Component(game_object_id_t id) : game_object_id(id) {} + +Component & Component::operator=(const Component & other) { +	this->active = other.active; +	return *this; +} + +unique_ptr<Component> Component::save() const { +	return unique_ptr<Component>(new Component(*this)); +} + +void Component::restore(const Component & snapshot) { *this = snapshot; } diff --git a/src/crepe/Component.h b/src/crepe/Component.h index eff5a58..52e06d5 100644 --- a/src/crepe/Component.h +++ b/src/crepe/Component.h @@ -1,5 +1,7 @@  #pragma once +#include <memory> +  #include "types.h"  namespace crepe { @@ -32,11 +34,33 @@ protected:  	//! Only ComponentManager can create components  	friend class ComponentManager; -	Component(const Component &) = delete; +	// components are never moved  	Component(Component &&) = delete; -	virtual Component & operator=(const Component &) = delete;  	virtual Component & operator=(Component &&) = delete; +protected: +	/** +	 * \name ReplayManager (Memento) functions +	 * \{ +	 */ +	/** +	 * \brief Save a snapshot of this component's state +	 * \note This function should only be implemented on components that should be saved/restored +	 * by ReplayManager. +	 * \returns Unique pointer to a deep copy of this component +	 */ +	virtual std::unique_ptr<Component> save() const; +	//! Copy constructor (used by \c save()) +	Component(const Component &) = default; +	/** +	 * \brief Restore this component from a snapshot +	 * \param snapshot Data to fill this component with (as returned by \c save()) +	 */ +	virtual void restore(const Component & snapshot); +	//! Copy assignment operator (used by \c restore()) +	virtual Component & operator=(const Component &); +	//! \} +  public:  	virtual ~Component() = default; diff --git a/src/crepe/api/Animator.cpp b/src/crepe/api/Animator.cpp index 4ce4bf0..203cef3 100644 --- a/src/crepe/api/Animator.cpp +++ b/src/crepe/api/Animator.cpp @@ -1,5 +1,5 @@ -#include "util/Log.h" +#include "util/dbg.h"  #include "Animator.h"  #include "Component.h" diff --git a/src/crepe/api/BehaviorScript.cpp b/src/crepe/api/BehaviorScript.cpp index d22afdf..af7572c 100644 --- a/src/crepe/api/BehaviorScript.cpp +++ b/src/crepe/api/BehaviorScript.cpp @@ -10,6 +10,6 @@ BehaviorScript::BehaviorScript(game_object_id_t id, Mediator & mediator)  template <>  BehaviorScript & GameObject::add_component<BehaviorScript>() { -	ComponentManager & mgr = this->component_manager; -	return mgr.add_component<BehaviorScript>(this->id, mgr.mediator); +	ComponentManager & mgr = this->mediator.component_manager; +	return mgr.add_component<BehaviorScript>(this->id, this->mediator);  } diff --git a/src/crepe/api/BehaviorScript.hpp b/src/crepe/api/BehaviorScript.hpp index b9bb1e2..353d5e2 100644 --- a/src/crepe/api/BehaviorScript.hpp +++ b/src/crepe/api/BehaviorScript.hpp @@ -2,8 +2,6 @@  #include <type_traits> -#include "../util/Log.h" -  #include "BehaviorScript.h"  #include "Script.h" @@ -11,7 +9,6 @@ namespace crepe {  template <class T, typename... Args>  BehaviorScript & BehaviorScript::set_script(Args &&... args) { -	dbg_trace();  	static_assert(std::is_base_of<Script, T>::value);  	this->script = std::unique_ptr<Script>(new T(std::forward<Args>(args)...)); diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index e8d6f92..2bee3fb 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -13,7 +13,7 @@ target_sources(crepe PUBLIC  	Animator.cpp  	BoxCollider.cpp  	CircleCollider.cpp -	LoopManager.cpp +	Engine.cpp  	Asset.cpp  	EventHandler.cpp  	Script.cpp @@ -47,7 +47,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	EventHandler.h  	EventHandler.hpp  	Event.h -	LoopManager.h +	Engine.h +	Engine.hpp  	Asset.h  	Button.h  	UIObject.h diff --git a/src/crepe/api/Camera.cpp b/src/crepe/api/Camera.cpp index 179dc18..19a3296 100644 --- a/src/crepe/api/Camera.cpp +++ b/src/crepe/api/Camera.cpp @@ -1,4 +1,4 @@ -#include "util/Log.h" +#include "util/dbg.h"  #include "Camera.h"  #include "Component.h" diff --git a/src/crepe/api/Engine.cpp b/src/crepe/api/Engine.cpp new file mode 100644 index 0000000..2e9d35a --- /dev/null +++ b/src/crepe/api/Engine.cpp @@ -0,0 +1,63 @@ +#include "../util/Log.h" + +#include "Engine.h" + +using namespace crepe; +using namespace std; + +int Engine::main() noexcept { +	try { +		this->setup(); +	} catch (const exception & e) { +		Log::logf(Log::Level::ERROR, "Uncaught exception in setup: {}\n", e.what()); +		return EXIT_FAILURE; +	} + +	try { +		this->loop(); +	} catch (const exception & e) { +		Log::logf(Log::Level::ERROR, "Uncaught exception in main loop: {}\n", e.what()); +		this->event_manager.trigger_event<ShutDownEvent>(); +	} + +	return EXIT_SUCCESS; +} + +void Engine::setup() { +	this->loop_timer.start(); +	this->scene_manager.load_next_scene(); + +	this->event_manager.subscribe<ShutDownEvent>([this](const ShutDownEvent & event) { +		this->game_running = false; + +		// propagate to possible user ShutDownEvent listeners +		return false; +	}); +} + +void Engine::loop() { +	LoopTimerManager & timer = this->loop_timer; +	SystemManager & systems = this->system_manager; + +	while (this->game_running) { +		timer.update(); + +		while (timer.get_lag() >= timer.get_fixed_delta_time()) { +			try { +				systems.fixed_update(); +			} catch (const exception & e) { +				Log::logf(Log::Level::WARNING, +						  "Uncaught exception in fixed update function: {}\n", e.what()); +			} +			timer.advance_fixed_elapsed_time(); +		} + +		try { +			systems.frame_update(); +		} catch (const exception & e) { +			Log::logf(Log::Level::WARNING, "Uncaught exception in frame update function: {}\n", +					  e.what()); +		} +		timer.enforce_frame_rate(); +	} +} diff --git a/src/crepe/api/Engine.h b/src/crepe/api/Engine.h new file mode 100644 index 0000000..700a0cd --- /dev/null +++ b/src/crepe/api/Engine.h @@ -0,0 +1,81 @@ +#pragma once + +#include "../facade/SDLContext.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/ReplayManager.h" +#include "../manager/ResourceManager.h" +#include "../manager/SaveManager.h" +#include "../manager/SceneManager.h" +#include "../manager/SystemManager.h" + +namespace crepe { + +/** + * \brief Main game entrypoint + * + * This class is responsible for managing the game loop, including initialization and updating. + */ +class Engine { +public: +	/** +	 * \brief Engine entrypoint +	 * +	 * This function is called by the game programmer after registering all scenes +	 * +	 * \returns process exit code +	 */ +	int main() noexcept; + +	//! \copydoc SceneManager::add_scene +	template <typename T> +	void add_scene(); + +private: +	/** +	 * \brief Setup function for one-time initialization. +	 * +	 * This function initializes necessary components for the game. +	 */ +	void setup(); +	/** +	 * \brief Main game loop function. +	 * +	 * This function runs the main loop, handling game updates and rendering. +	 */ +	void loop(); + +	//! Game loop condition +	bool game_running = true; + +private: +	//! Global context +	Mediator mediator; + +	//! SystemManager +	SystemManager system_manager{mediator}; + +	//! SDLContext instance +	SDLContext sdl_context{mediator}; + +	//! Resource manager instance +	ResourceManager resource_manager{mediator}; + +	//! Component manager instance +	ComponentManager component_manager{mediator}; +	//! Scene manager instance +	SceneManager scene_manager{mediator}; +	//! LoopTimerManager instance +	LoopTimerManager loop_timer{mediator}; +	//! EventManager instance +	EventManager event_manager{mediator}; +	//! Save manager instance +	SaveManager save_manager{mediator}; +	//! ReplayManager instance +	ReplayManager replay_manager{mediator}; +}; + +} // namespace crepe + +#include "Engine.hpp" diff --git a/src/crepe/api/Engine.hpp b/src/crepe/api/Engine.hpp new file mode 100644 index 0000000..f2fdc0a --- /dev/null +++ b/src/crepe/api/Engine.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "Engine.h" + +namespace crepe { + +template <class T> +void Engine::add_scene() { +	this->scene_manager.add_scene<T>(); +} + +} // namespace crepe diff --git a/src/crepe/api/GameObject.cpp b/src/crepe/api/GameObject.cpp index 9ef4682..9b94cad 100644 --- a/src/crepe/api/GameObject.cpp +++ b/src/crepe/api/GameObject.cpp @@ -7,20 +7,17 @@  using namespace crepe;  using namespace std; -GameObject::GameObject(ComponentManager & component_manager, game_object_id_t id, -					   const std::string & name, const std::string & tag, -					   const vec2 & position, double rotation, double scale) +GameObject::GameObject(Mediator & mediator, game_object_id_t id, const std::string & name, +					   const std::string & tag, const vec2 & position, double rotation, +					   double scale)  	: id(id), -	  component_manager(component_manager) { - -	// Add Transform and Metadata components -	ComponentManager & mgr = this->component_manager; -	mgr.add_component<Transform>(this->id, position, rotation, scale); -	mgr.add_component<Metadata>(this->id, name, tag); -} +	  mediator(mediator), +	  transform(mediator.component_manager->add_component<Transform>(this->id, position, +																	 rotation, scale)), +	  metadata(mediator.component_manager->add_component<Metadata>(this->id, name, tag)) {}  void GameObject::set_parent(const GameObject & parent) { -	ComponentManager & mgr = this->component_manager; +	ComponentManager & mgr = this->mediator.component_manager;  	// Set parent on own Metadata component  	RefVector<Metadata> this_metadata = mgr.get_components_by_id<Metadata>(this->id); @@ -32,7 +29,7 @@ void GameObject::set_parent(const GameObject & parent) {  }  void GameObject::set_persistent(bool persistent) { -	ComponentManager & mgr = this->component_manager; +	ComponentManager & mgr = this->mediator.component_manager;  	mgr.set_persistent(this->id, persistent);  } diff --git a/src/crepe/api/GameObject.h b/src/crepe/api/GameObject.h index ff80f49..572ce3a 100644 --- a/src/crepe/api/GameObject.h +++ b/src/crepe/api/GameObject.h @@ -6,7 +6,9 @@  namespace crepe { -class ComponentManager; +class Mediator; +class Transform; +class Metadata;  /**   * \brief Represents a GameObject @@ -20,7 +22,7 @@ private:  	 * This constructor creates a new GameObject. It creates a new Transform and Metadata  	 * component and adds them to the ComponentManager.  	 * -	 * \param component_manager Reference to component_manager +	 * \param mediator Reference to mediator  	 * \param id The id of the GameObject  	 * \param name The name of the GameObject  	 * \param tag The tag of the GameObject @@ -28,13 +30,20 @@ private:  	 * \param rotation The rotation of the GameObject  	 * \param scale The scale of the GameObject  	 */ -	GameObject(ComponentManager & component_manager, game_object_id_t id, -			   const std::string & name, const std::string & tag, const vec2 & position, -			   double rotation, double scale); +	GameObject(Mediator & mediator, game_object_id_t id, const std::string & name, +			   const std::string & tag, const vec2 & position, double rotation, double scale);  	//! ComponentManager instances GameObject  	friend class ComponentManager;  public: +	//! The id of the GameObject +	const game_object_id_t id; +	//! This entity's transform +	Transform & transform; +	//! This entity's metadata +	Metadata & metadata; + +public:  	/**  	 * \brief Set the parent of this GameObject  	 * @@ -68,12 +77,8 @@ public:  	 */  	void set_persistent(bool persistent = true); -public: -	//! The id of the GameObject -	const game_object_id_t id; -  protected: -	ComponentManager & component_manager; +	Mediator & mediator;  };  } // namespace crepe diff --git a/src/crepe/api/GameObject.hpp b/src/crepe/api/GameObject.hpp index a6b45b0..69f7d73 100644 --- a/src/crepe/api/GameObject.hpp +++ b/src/crepe/api/GameObject.hpp @@ -8,7 +8,7 @@ namespace crepe {  template <typename T, typename... Args>  T & GameObject::add_component(Args &&... args) { -	ComponentManager & mgr = this->component_manager; +	ComponentManager & mgr = this->mediator.component_manager;  	return mgr.add_component<T>(this->id, std::forward<Args>(args)...);  } diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp deleted file mode 100644 index 7a78019..0000000 --- a/src/crepe/api/LoopManager.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "../facade/SDLContext.h" -#include "../manager/EventManager.h" -#include "../manager/LoopTimerManager.h" -#include "../system/AISystem.h" -#include "../system/AnimatorSystem.h" -#include "../system/AudioSystem.h" -#include "../system/CollisionSystem.h" -#include "../system/InputSystem.h" -#include "../system/ParticleSystem.h" -#include "../system/PhysicsSystem.h" -#include "../system/RenderSystem.h" -#include "../system/ScriptSystem.h" -#include "../util/Log.h" - -#include "LoopManager.h" - -using namespace crepe; -using namespace std; - -LoopManager::LoopManager() { -	this->load_system<AnimatorSystem>(); -	this->load_system<CollisionSystem>(); -	this->load_system<ParticleSystem>(); -	this->load_system<PhysicsSystem>(); -	this->load_system<RenderSystem>(); -	this->load_system<ScriptSystem>(); -	this->load_system<InputSystem>(); -	this->event_manager.subscribe<ShutDownEvent>( -		[this](const ShutDownEvent & event) { return this->on_shutdown(event); }); -	this->load_system<AudioSystem>(); -	this->load_system<AISystem>(); -} -void LoopManager::start() { -	this->setup(); -	this->loop(); -} - -void LoopManager::setup() { -	this->game_running = true; -	this->loop_timer.start(); -	this->scene_manager.load_next_scene(); -} - -void LoopManager::loop() { -	try { -		while (game_running) { -			this->loop_timer.update(); - -			while (this->loop_timer.get_lag() >= this->loop_timer.get_fixed_delta_time()) { -				this->fixed_update(); -				this->loop_timer.advance_fixed_elapsed_time(); -			} - -			this->frame_update(); -			this->loop_timer.enforce_frame_rate(); -		} -	} catch (const exception & e) { -		Log::logf(Log::Level::ERROR, "Exception caught in main loop: {}", e.what()); -		this->event_manager.trigger_event<ShutDownEvent>(ShutDownEvent{}); -	} -} - -// will be called at a fixed interval -void LoopManager::fixed_update() { -	this->get_system<InputSystem>().update(); -	this->event_manager.dispatch_events(); -	this->get_system<ScriptSystem>().update(); -	this->get_system<ParticleSystem>().update(); -	this->get_system<AISystem>().update(); -	this->get_system<PhysicsSystem>().update(); -	this->get_system<CollisionSystem>().update(); -	this->get_system<AudioSystem>().update(); -} - -// will be called every frame -void LoopManager::frame_update() { -	this->scene_manager.load_next_scene(); -	this->get_system<AnimatorSystem>().update(); -	//render -	this->get_system<RenderSystem>().update(); -} - -bool LoopManager::on_shutdown(const ShutDownEvent & e) { -	this->game_running = false; -	// propagate to possible user ShutDownEvent listeners -	return false; -} diff --git a/src/crepe/api/LoopManager.h b/src/crepe/api/LoopManager.h deleted file mode 100644 index 124cd3a..0000000 --- a/src/crepe/api/LoopManager.h +++ /dev/null @@ -1,123 +0,0 @@ -#pragma once - -#include <memory> - -#include "../facade/SDLContext.h" -#include "../manager/ComponentManager.h" -#include "../manager/EventManager.h" -#include "../manager/LoopTimerManager.h" -#include "../manager/Mediator.h" -#include "../manager/ResourceManager.h" -#include "../manager/SaveManager.h" -#include "../manager/SceneManager.h" -#include "../system/System.h" - -namespace crepe { -/** - * \brief Main game loop manager - * - * This class is responsible for managing the game loop, including initialization and updating. - */ -class LoopManager { -public: -	LoopManager(); -	/** -	 * \brief Start the gameloop -	 * -	 * This is the start of the engine where the setup is called and then the loop keeps running until the game stops running. -	 * The Game programmer needs to call this function to run the game. This should be done after creating and adding all scenes. -	 */ -	void start(); - -	/** -	 * \brief Add a new concrete scene to the scene manager -	 * -	 * \tparam T  Type of concrete scene -	 */ -	template <typename T> -	void add_scene(); - -private: -	/** -	 * \brief Setup function for one-time initialization. -	 * -	 * This function initializes necessary components for the game. -	 */ -	void setup(); -	/** -	 * \brief Main game loop function. -	 * -	 * This function runs the main loop, handling game updates and rendering. -	 */ -	void loop(); - -	/** -	 * \brief Per-frame update. -	 * -	 * Updates the game state based on the elapsed time since the last frame. -	 */ -	virtual void frame_update(); - -	/** -	 * \brief Fixed update executed at a fixed rate. -	 * -	 * This function updates physics and game logic based on LoopTimer's fixed_delta_time. -	 */ -	virtual void fixed_update(); - -	//! Indicates whether the game is running. -	bool game_running = false; - -private: -	//! Global context -	Mediator mediator; -	/** -	 * \brief Collection of System instances -	 * -	 * This map holds System instances indexed by the system's class typeid. It is filled in the -	 * constructor of LoopManager using LoopManager::load_system. -	 */ -	std::unordered_map<std::type_index, std::unique_ptr<System>> systems; - -	//! SDLContext instance -	SDLContext sdl_context{mediator}; -	//! Resource manager instance -	ResourceManager resource_manager{mediator}; - -	//! Component manager instance -	ComponentManager component_manager{mediator}; -	//! Scene manager instance -	SceneManager scene_manager{mediator}; -	//! LoopTimerManager instance -	LoopTimerManager loop_timer{mediator}; -	//! EventManager instance -	EventManager event_manager{mediator}; -	//! Save manager instance -	SaveManager save_manager{mediator}; - -private: -	/** -	 * \brief Callback function for ShutDownEvent -	 * -	 * This function sets the game_running variable to false, stopping the gameloop and therefor quitting the game. -	 */ -	bool on_shutdown(const ShutDownEvent & e); -	/** -	 * \brief Initialize a system -	 * \tparam T System type (must be derivative of \c System) -	 */ -	template <class T> -	void load_system(); -	/** -	 * \brief Retrieve a reference to ECS system -	 * \tparam T System type -	 * \returns Reference to system instance -	 * \throws std::runtime_error if the System is not initialized -	 */ -	template <class T> -	T & get_system(); -}; - -} // namespace crepe - -#include "LoopManager.hpp" diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp index 4f54bbd..9a70334 100644 --- a/src/crepe/api/ParticleEmitter.cpp +++ b/src/crepe/api/ParticleEmitter.cpp @@ -2,6 +2,7 @@  #include "api/Sprite.h"  using namespace crepe; +using namespace std;  ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Sprite & sprite,  								 const Data & data) @@ -12,3 +13,16 @@ ParticleEmitter::ParticleEmitter(game_object_id_t game_object_id, const Sprite &  		this->particles.emplace_back();  	}  } + +unique_ptr<Component> ParticleEmitter::save() const { +	return unique_ptr<Component>{new ParticleEmitter(*this)}; +} + +void ParticleEmitter::restore(const Component & snapshot) { +	*this = static_cast<const ParticleEmitter &>(snapshot); +} + +ParticleEmitter & ParticleEmitter::operator=(const ParticleEmitter & other) { +	this->particles = other.particles; +	return *this; +} diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index 8ac2e72..1edd2b5 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -84,6 +84,12 @@ public:  	//! Configuration data for particle emission settings.  	Data data; +protected: +	virtual std::unique_ptr<Component> save() const; +	ParticleEmitter(const ParticleEmitter &) = default; +	virtual void restore(const Component & snapshot); +	virtual ParticleEmitter & operator=(const ParticleEmitter &); +  private:  	//! Only ParticleSystem can move and read particles  	friend ParticleSystem; diff --git a/src/crepe/api/Rigidbody.h b/src/crepe/api/Rigidbody.h index 6900295..b63d941 100644 --- a/src/crepe/api/Rigidbody.h +++ b/src/crepe/api/Rigidbody.h @@ -2,6 +2,7 @@  #include <cmath>  #include <set> +#include <string>  #include "../Component.h" @@ -120,26 +121,49 @@ public:  		* above 0.0.  		*  		*/ -		float elastisity_coefficient = 0.0; +		float elasticity_coefficient = 0.0;  		/** -		* \brief Offset of all colliders relative to the object's transform position. +		 * \brief  Enables collision handling for objects colliding with kinematic objects. +		 * +		 * Enables collision handling for objects colliding with kinematic objects in the collision system. +     * If `kinematic_collision` is true, dynamic objects cannot pass through this kinematic object. +     * This ensures that kinematic objects delegate collision handling to the collision system. +		 */ +		bool kinematic_collision = true; + +		/** +		* \brief Defines the collision layers a GameObject interacts with.  		* -		* 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. +		* The `collision_layers` represent the set of layers the GameObject can detect collisions with. +		* Each element in this set corresponds to a layer ID. The GameObject will only collide with other +		* GameObjects that belong to one these layers. +		*/ +		std::set<int> collision_layers = {0}; + +		/** +		* \brief Specifies the collision layer of the GameObject.  		* +		* The `collision_layer` indicates the single layer that this GameObject belongs to.  +		* This determines which layers other objects must match to detect collisions with this object.  		*/ -		vec2 offset; +		int collision_layer = 0;  		/**  		 * \brief Defines the collision layers of a GameObject.  		 * -		 * The `collision_layers` specifies the layers that the GameObject will collide with. -		 * Each element represents a layer ID, and the GameObject will only detect -		 * collisions with other GameObjects that belong to these layers. +		 * The `collision_names` specifies where the GameObject will collide with. +		 * Each element represents a name from the Metadata of the gameobject.  		 */ -		std::set<int> collision_layers = {0}; +		std::set<std::string> collision_names; + +		/** +		 * \brief Defines the collision layers of a GameObject. +		 * +		 * The `collision_tags` specifies where the GameObject will collide with. +		 * Each element represents a tag from the Metadata of the gameobject. +		 */ +		std::set<std::string> collision_tags;  	};  public: diff --git a/src/crepe/api/Script.cpp b/src/crepe/api/Script.cpp index 7531388..b147252 100644 --- a/src/crepe/api/Script.cpp +++ b/src/crepe/api/Script.cpp @@ -2,6 +2,7 @@  #include "../facade/SDLContext.h"  #include "../manager/SceneManager.h" +  #include "Script.h"  using namespace crepe; @@ -28,6 +29,26 @@ SaveManager & Script::get_save_manager() const { return this->mediator->save_man  LoopTimerManager & Script::get_loop_timer() const { return this->mediator->loop_timer; } +void Script::replay::record_start() { +	ReplayManager & mgr = this->mediator->replay_manager; +	return mgr.record_start(); +} + +recording_t Script::replay::record_end() { +	ReplayManager & mgr = this->mediator->replay_manager; +	return mgr.record_end(); +} + +void Script::replay::play(recording_t recording) { +	ReplayManager & mgr = this->mediator->replay_manager; +	return mgr.play(recording); +} + +void Script::replay::release(recording_t recording) { +	ReplayManager & mgr = this->mediator->replay_manager; +	return mgr.release(recording); +} +  const keyboard_state_t & Script::get_keyboard_state() const {  	SDLContext & sdl_context = this->mediator->sdl_context;  	return sdl_context.get_keyboard_state(); diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index a87af4e..5f68928 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -6,8 +6,10 @@  #include "../manager/EventManager.h"  #include "../manager/LoopTimerManager.h"  #include "../manager/Mediator.h" +#include "../manager/ReplayManager.h"  #include "../system/CollisionSystem.h"  #include "../types.h" +#include "../util/Log.h"  #include "../util/OptionalRef.h"  namespace crepe { @@ -46,14 +48,23 @@ protected:  	 */  	virtual void init() {}  	/** -	 * \brief Script update function (empty by default) +	 * \brief Script fixed update function (empty by default)  	 *  	 * \param delta_time Time since last fixed update  	 * -	 * This function is called during the ScriptSystem::update() routine if the \c BehaviorScript -	 * component holding this script instance is active. +	 * \note This function is called during the ScriptSystem::update() routine if the \c +	 * BehaviorScript component holding this script instance is active.  	 */ -	virtual void update(duration_t delta_time) {} +	virtual void fixed_update(duration_t delta_time) {} +	/** +	 * \brief Script frame update function (empty by default) +	 * +	 * \param delta_time Time since last frame update +	 * +	 * \note This function is called during the ScriptSystem::update() routine if the \c +	 * BehaviorScript component holding this script instance is active. +	 */ +	virtual void frame_update(duration_t delta_time) {}  	//! \}  	//! ScriptSystem calls \c init() and \c update() @@ -61,93 +72,119 @@ protected:  protected:  	/** -	 * \name Utility functions +	 * \name Component query functions +	 * \see ComponentManager  	 * \{  	 */ -  	/**  	 * \brief Get single component of type \c T on this game object -	 *  	 * \tparam T Type of component -	 *  	 * \returns Reference to component -	 *  	 * \throws std::runtime_error if this game object does not have a component with type \c T  	 */  	template <typename T>  	T & get_component() const; -	// TODO: make get_component calls for component types that can have more than 1 instance -	// cause compile-time errors -  	/**  	 * \brief Get all components of type \c T on this game object -	 *  	 * \tparam T Type of component -	 *  	 * \returns List of component references  	 */  	template <typename T>  	RefVector<T> get_components() const; - -	/** -	 * \copydoc ComponentManager::get_components_by_id -	 * \see ComponentManager::get_components_by_id -	 */ +	//! \copydoc ComponentManager::get_components_by_id  	template <typename T>  	RefVector<T> get_components_by_id(game_object_id_t id) const; -	/** -	 * \copydoc ComponentManager::get_components_by_name -	 * \see ComponentManager::get_components_by_name -	 */ +	//! \copydoc ComponentManager::get_components_by_name  	template <typename T>  	RefVector<T> get_components_by_name(const std::string & name) const; -	/** -	 * \copydoc ComponentManager::get_components_by_tag -	 * \see ComponentManager::get_components_by_tag -	 */ +	//! \copydoc ComponentManager::get_components_by_tag  	template <typename T>  	RefVector<T> get_components_by_tag(const std::string & tag) const; +	//! \}  	/** -	 * \brief Log a message using Log::logf -	 * -	 * \tparam Args Log::logf parameters -	 * \param args  Log::logf parameters +	 * \name Logging functions +	 * \see Log +	 * \{  	 */ -	template <typename... Args> -	void logf(Args &&... args); +	//! \copydoc Log::logf +	template <class... Args> +	void logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args); +	//! \copydoc Log::logf +	template <class... Args> +	void logf(std::format_string<Args...> fmt, Args &&... args); +	// \}  	/** -	 * \brief Subscribe to an event with an explicit channel -	 * \see EventManager::subscribe +	 * \name Event manager functions +	 * \see EventManager +	 * \{  	 */ +	//! \copydoc EventManager::subscribe  	template <typename EventType>  	void subscribe(const EventHandler<EventType> & callback, event_channel_t channel); -	/** -	 * \brief Subscribe to an event on EventManager::CHANNEL_ALL -	 * \see EventManager::subscribe -	 */ +	//! \copydoc EventManager::subscribe  	template <typename EventType>  	void subscribe(const EventHandler<EventType> & callback); +	//! \copydoc EventManager::trigger_event +	template <typename EventType> +	void trigger_event(const EventType & event = {}, +					   event_channel_t channel = EventManager::CHANNEL_ALL); +	//! \copydoc EventManager::queue_event +	template <typename EventType> +	void queue_event(const EventType & event = {}, +					 event_channel_t channel = EventManager::CHANNEL_ALL); +	//! \}  	/** -	 * \brief Set the next scene using SceneManager -	 * \see SceneManager::set_next_scene +	 * \name Scene-related functions +	 * \see SceneManager +	 * \{  	 */ +	//! \copydoc SceneManager::set_next_scene  	void set_next_scene(const std::string & name); +	//! \} +	/** +	 * \name Save data management functions +	 * \see SaveManager +	 * \{ +	 */  	//! Retrieve SaveManager reference  	SaveManager & get_save_manager() const; +	//! \} +	/** +	 * \name Timing functions +	 * \see LoopTimerManager +	 * \{ +	 */  	//! Retrieve LoopTimerManager reference  	LoopTimerManager & get_loop_timer() const; +	//! \} + +	//! Replay management functions +	struct replay { // NOLINT +		//! \copydoc ReplayManager::record_start +		void record_start(); +		//! \copydoc ReplayManager::record_end +		recording_t record_end(); +		//! \copydoc ReplayManager::play +		void play(recording_t); +		//! \copydoc ReplayManager::release +		void release(recording_t); + +	private: +		OptionalRef<Mediator> & mediator; +		replay(OptionalRef<Mediator> & mediator) : mediator(mediator) {} +		friend class Script; +	} replay{mediator};  	/**  	 * \brief Utility function to retrieve the keyboard state  	 * \see SDLContext::get_keyboard_state  	 *   	 * \return current keyboard state map with Keycode as key and bool as value(true = pressed, false = not pressed) -	 *   	 */  	const keyboard_state_t & get_keyboard_state() const;  	/** @@ -155,7 +192,6 @@ protected:  	 * \see SDLContext::get_keyboard_state  	 *   	 * \return Keycode state (true if pressed, false if not pressed). -	 *   	 */  	bool get_key_state(Keycode key) const noexcept; diff --git a/src/crepe/api/Script.hpp b/src/crepe/api/Script.hpp index 225a51c..4462a41 100644 --- a/src/crepe/api/Script.hpp +++ b/src/crepe/api/Script.hpp @@ -1,6 +1,7 @@  #pragma once  #include "../manager/ComponentManager.h" +#include "../manager/ReplayManager.h"  #include "BehaviorScript.h"  #include "Script.h" @@ -23,9 +24,14 @@ RefVector<T> Script::get_components() const {  	return this->get_components_by_id<T>(this->game_object_id);  } -template <typename... Args> -void Script::logf(Args &&... args) { -	Log::logf(std::forward<Args>(args)...); +template <class... Args> +void Script::logf(const Log::Level & level, std::format_string<Args...> fmt, Args &&... args) { +	Log::logf(level, fmt, std::forward<Args>(args)...); +} + +template <class... Args> +void Script::logf(std::format_string<Args...> fmt, Args &&... args) { +	Log::logf(fmt, std::forward<Args>(args)...);  }  template <typename EventType> @@ -34,8 +40,18 @@ void Script::subscribe_internal(const EventHandler<EventType> & callback,  	EventManager & mgr = this->mediator->event_manager;  	subscription_t listener = mgr.subscribe<EventType>(  		[this, callback](const EventType & data) -> bool { +			// check if (parent) BehaviorScript component is active  			bool & active = this->active;  			if (!active) return false; + +			// check if replay manager is playing (if initialized) +			try { +				ReplayManager & replay = this->mediator->replay_manager; +				if (replay.get_state() == ReplayManager::PLAYING) return false; +			} catch (const std::runtime_error &) { +			} + +			// call user-provided callback  			return callback(data);  		},  		channel); @@ -52,6 +68,18 @@ void Script::subscribe(const EventHandler<EventType> & callback) {  	this->subscribe_internal(callback, EventManager::CHANNEL_ALL);  } +template <typename EventType> +void Script::trigger_event(const EventType & event, event_channel_t channel) { +	EventManager & mgr = this->mediator->event_manager; +	mgr.trigger_event(event, channel); +} + +template <typename EventType> +void Script::queue_event(const EventType & event, event_channel_t channel) { +	EventManager & mgr = this->mediator->event_manager; +	mgr.queue_event(event, channel); +} +  template <typename T>  RefVector<T> Script::get_components_by_id(game_object_id_t id) const {  	Mediator & mediator = this->mediator; diff --git a/src/crepe/api/Sprite.cpp b/src/crepe/api/Sprite.cpp index ba684ba..0107c7b 100644 --- a/src/crepe/api/Sprite.cpp +++ b/src/crepe/api/Sprite.cpp @@ -1,6 +1,6 @@  #include <cmath> -#include "../util/Log.h" +#include "../util/dbg.h"  #include "api/Asset.h"  #include "Component.h" diff --git a/src/crepe/api/Sprite.h b/src/crepe/api/Sprite.h index a2409c2..a3fc319 100644 --- a/src/crepe/api/Sprite.h +++ b/src/crepe/api/Sprite.h @@ -66,6 +66,16 @@ public:  		//! independent sprite offset position  		vec2 position_offset; + +		/** +		 * \brief gives the user the option to render this in world space or in camera space +		 * +		 * - if true will this be rendered in world space this means that the sprite can be +		 *   rendered off the screen  +		 * - if false --> will the sprite be rendered in camera space. this means that the +		 *   coordinates given on the \c Sprite and \c Transform will be inside the camera  +		 */ +		bool world_space = true;  	};  public: diff --git a/src/crepe/api/Text.cpp b/src/crepe/api/Text.cpp index 54a4370..4a94180 100644 --- a/src/crepe/api/Text.cpp +++ b/src/crepe/api/Text.cpp @@ -1,5 +1,3 @@ -#include "../facade/FontFacade.h" -  #include "Text.h"  using namespace crepe; diff --git a/src/crepe/api/Text.h b/src/crepe/api/Text.h index c30dc80..da40141 100644 --- a/src/crepe/api/Text.h +++ b/src/crepe/api/Text.h @@ -3,8 +3,6 @@  #include <optional>  #include <string> -#include "../Component.h" -  #include "Asset.h"  #include "Color.h"  #include "UIObject.h" diff --git a/src/crepe/api/Transform.cpp b/src/crepe/api/Transform.cpp index a85b792..fcfce14 100644 --- a/src/crepe/api/Transform.cpp +++ b/src/crepe/api/Transform.cpp @@ -1,8 +1,9 @@ -#include "../util/Log.h" +#include "../util/dbg.h"  #include "Transform.h"  using namespace crepe; +using namespace std;  Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, double scale)  	: Component(id), @@ -11,3 +12,11 @@ Transform::Transform(game_object_id_t id, const vec2 & point, double rotation, d  	  scale(scale) {  	dbg_trace();  } + +unique_ptr<Component> Transform::save() const { +	return unique_ptr<Component>{new Transform(*this)}; +} + +void Transform::restore(const Component & snapshot) { +	*this = static_cast<const Transform &>(snapshot); +} diff --git a/src/crepe/api/Transform.h b/src/crepe/api/Transform.h index 7ee6d65..a6f3486 100644 --- a/src/crepe/api/Transform.h +++ b/src/crepe/api/Transform.h @@ -35,6 +35,12 @@ protected:  	virtual int get_instances_max() const { return 1; }  	//! ComponentManager instantiates all components  	friend class ComponentManager; + +protected: +	virtual std::unique_ptr<Component> save() const; +	Transform(const Transform &) = default; +	virtual void restore(const Component & snapshot); +	virtual Transform & operator=(const Transform &) = default;  };  } // namespace crepe diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h index bf9d124..52e1bb6 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -90,6 +90,9 @@ struct Vector2 {  	//! Returns the perpendicular vector to this vector.  	Vector2 perpendicular() const; + +	//! Checks if both components of the vector are NaN. +	bool is_nan() const;  };  } // namespace crepe diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp index ff53cb0..e195760 100644 --- a/src/crepe/api/Vector2.hpp +++ b/src/crepe/api/Vector2.hpp @@ -163,4 +163,9 @@ Vector2<T> Vector2<T>::perpendicular() const {  	return {-y, x};  } +template <class T> +bool Vector2<T>::is_nan() const { +	return std::isnan(x) && std::isnan(y); +} +  } // namespace crepe diff --git a/src/crepe/facade/DB.cpp b/src/crepe/facade/DB.cpp index ae2d4bc..7a3e473 100644 --- a/src/crepe/facade/DB.cpp +++ b/src/crepe/facade/DB.cpp @@ -1,6 +1,6 @@  #include <cstring> -#include "util/Log.h" +#include "util/dbg.h"  #include "DB.h" diff --git a/src/crepe/facade/SDLContext.cpp b/src/crepe/facade/SDLContext.cpp index fffbe34..536ea43 100644 --- a/src/crepe/facade/SDLContext.cpp +++ b/src/crepe/facade/SDLContext.cpp @@ -11,7 +11,6 @@  #include <array>  #include <cmath>  #include <cstddef> -#include <cstdint>  #include <functional>  #include <memory>  #include <stdexcept> @@ -20,8 +19,12 @@  #include "../api/Color.h"  #include "../api/Config.h"  #include "../api/Sprite.h" -#include "../util/Log.h" +#include "../util/dbg.h" +#include "api/Text.h" +#include "api/Transform.h" +#include "facade/Font.h"  #include "manager/Mediator.h" +#include "util/AbsolutePosition.h"  #include "SDLContext.h"  #include "Texture.h" @@ -32,9 +35,6 @@ using namespace std;  SDLContext::SDLContext(Mediator & mediator) {  	dbg_trace(); -	if (TTF_Init() == -1) { -		throw runtime_error(format("SDL_ttf initialization failed: {}", TTF_GetError())); -	}  	if (SDL_Init(SDL_INIT_VIDEO) != 0) {  		throw runtime_error(format("SDLContext: SDL_Init error: {}", SDL_GetError()));  	} @@ -63,6 +63,10 @@ SDLContext::SDLContext(Mediator & mediator) {  		throw runtime_error("SDLContext: SDL_image could not initialize!");  	} +	if (TTF_Init() == -1) { +		throw runtime_error(format("SDL_ttf initialization failed: {}", TTF_GetError())); +	} +  	mediator.sdl_context = *this;  } @@ -75,8 +79,8 @@ SDLContext::~SDLContext() {  	// TODO: how are we going to ensure that these are called from the same  	// thread that SDL_Init() was called on? This has caused problems for me  	// before. -	IMG_Quit();  	TTF_Quit(); +	IMG_Quit();  	SDL_Quit();  } @@ -137,6 +141,8 @@ SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const {  		= (ctx.sprite.aspect_ratio == 0) ? ctx.texture.get_ratio() : ctx.sprite.aspect_ratio;  	vec2 size = data.size; +	vec2 screen_pos = ctx.pos; +  	if (data.size.x == 0 && data.size.y != 0) {  		size.x = data.size.y * aspect_ratio;  	} @@ -145,10 +151,15 @@ SDL_FRect SDLContext::get_dst_rect(const DestinationRectangleData & ctx) const {  	}  	size *= cam_aux_data.render_scale * ctx.img_scale * data.scale_offset; -	vec2 screen_pos = (ctx.pos + data.position_offset - cam_aux_data.cam_pos -					   + (cam_aux_data.zoomed_viewport) / 2) -						  * cam_aux_data.render_scale -					  - size / 2 + cam_aux_data.bar_size; +	if (ctx.sprite.data.world_space) { +		screen_pos = (screen_pos - cam_aux_data.cam_pos + cam_aux_data.zoomed_viewport / 2) +						 * cam_aux_data.render_scale +					 - size / 2 + cam_aux_data.bar_size; +	} else { +		screen_pos +			= (screen_pos + cam_aux_data.zoomed_viewport / 2) * cam_aux_data.render_scale +			  - size / 2 + cam_aux_data.bar_size; +	}  	return SDL_FRect{  		.x = screen_pos.x, @@ -188,6 +199,52 @@ void SDLContext::draw(const RenderContext & ctx) {  					  angle, NULL, render_flip);  } +void SDLContext::draw_text(const RenderText & data) { + +	const Text & text = data.text; +	const Font & font = data.font; +	vec2 absoluut_pos = AbsolutePosition::get_position(data.transform, data.text.offset); +	std::unique_ptr<SDL_Surface, std::function<void(SDL_Surface *)>> font_surface; +	std::unique_ptr<SDL_Texture, std::function<void(SDL_Texture *)>> font_texture; + +	SDL_Color color{ +		.r = text.data.text_color.r, +		.g = text.data.text_color.g, +		.b = text.data.text_color.b, +		.a = text.data.text_color.a, +	}; +	SDL_Surface * tmp_font_surface +		= TTF_RenderText_Solid(font.get_font(), text.text.c_str(), color); +	if (tmp_font_surface == NULL) { +		throw runtime_error(format("draw_text: font surface error: {}", SDL_GetError())); +	} +	font_surface = {tmp_font_surface, [](SDL_Surface * surface) { SDL_FreeSurface(surface); }}; + +	SDL_Texture * tmp_font_texture +		= SDL_CreateTextureFromSurface(this->game_renderer.get(), font_surface.get()); +	if (tmp_font_texture == NULL) { +		throw runtime_error(format("draw_text: font texture error: {}", SDL_GetError())); +	} +	font_texture +		= {tmp_font_texture, [](SDL_Texture * texture) { SDL_DestroyTexture(texture); }}; + +	vec2 size = text.dimensions * cam_aux_data.render_scale * data.transform.scale; +	vec2 screen_pos +		= (absoluut_pos - cam_aux_data.cam_pos + (cam_aux_data.zoomed_viewport) / 2) +			  * cam_aux_data.render_scale +		  - size / 2 + cam_aux_data.bar_size; + +	SDL_FRect dstrect{ +		.x = screen_pos.x, +		.y = screen_pos.y, +		.w = size.x, +		.h = size.y, +	}; + +	SDL_RenderCopyExF(this->game_renderer.get(), font_texture.get(), NULL, &dstrect, +					  data.transform.rotation, NULL, SDL_FLIP_NONE); +} +  void SDLContext::update_camera_view(const Camera & cam, const vec2 & new_pos) {  	const Camera::Data & cam_data = cam.data; diff --git a/src/crepe/facade/SDLContext.h b/src/crepe/facade/SDLContext.h index b687f87..e570073 100644 --- a/src/crepe/facade/SDLContext.h +++ b/src/crepe/facade/SDLContext.h @@ -12,18 +12,21 @@  #include <unordered_map>  #include "../types.h" +#include "EventData.h"  #include "api/Camera.h"  #include "api/Color.h"  #include "api/KeyCodes.h"  #include "api/Sprite.h"  #include "api/Transform.h" -#include "types.h"  #include "EventData.h"  #include "FontFacade.h" +#include "types.h"  namespace crepe {  class Texture; +class Text; +class Font;  class Mediator;  /** @@ -71,12 +74,11 @@ public:  		const double & scale;  	}; -public: -	/** -	 * \brief Gets the singleton instance of SDLContext. -	 * \return Reference to the SDLContext instance. -	 */ -	static SDLContext & get_instance(); +	struct RenderText { +		const Text & text; +		const Font & font; +		const Transform & transform; +	};  public:  	SDLContext(const SDLContext &) = delete; @@ -186,6 +188,13 @@ public:  	 */  	void draw(const RenderContext & ctx); +	/** +	 * \brief draws a text to the screen  +	 * +	 * \param data Reference to the rendering data needed to draw +	 */ +	void draw_text(const RenderText & data); +  	//! Clears the screen, preparing for a new frame.  	void clear_screen(); diff --git a/src/crepe/facade/Sound.cpp b/src/crepe/facade/Sound.cpp index 97e455e..b1e6463 100644 --- a/src/crepe/facade/Sound.cpp +++ b/src/crepe/facade/Sound.cpp @@ -1,5 +1,5 @@  #include "../api/Asset.h" -#include "../util/Log.h" +#include "../util/dbg.h"  #include "Sound.h" diff --git a/src/crepe/facade/SoundContext.cpp b/src/crepe/facade/SoundContext.cpp index b1f8cb3..5091e07 100644 --- a/src/crepe/facade/SoundContext.cpp +++ b/src/crepe/facade/SoundContext.cpp @@ -1,4 +1,4 @@ -#include "../util/Log.h" +#include "../util/dbg.h"  #include "SoundContext.h" diff --git a/src/crepe/facade/Texture.cpp b/src/crepe/facade/Texture.cpp index b63403d..06caa54 100644 --- a/src/crepe/facade/Texture.cpp +++ b/src/crepe/facade/Texture.cpp @@ -1,10 +1,11 @@ -#include "../util/Log.h" -#include "facade/SDLContext.h" -#include "manager/Mediator.h" +#include "../Resource.h" +#include "../facade/SDLContext.h" +#include "../manager/Mediator.h" +#include "../types.h" +#include "../util/dbg.h" -#include "Resource.h" +#include "SDLContext.h"  #include "Texture.h" -#include "types.h"  using namespace crepe;  using namespace std; @@ -23,6 +24,7 @@ Texture::~Texture() {  }  const ivec2 & Texture::get_size() const noexcept { return this->size; } +  const float & Texture::get_ratio() const noexcept { return this->aspect_ratio; }  SDL_Texture * Texture::get_img() const noexcept { return this->texture.get(); } diff --git a/src/crepe/manager/CMakeLists.txt b/src/crepe/manager/CMakeLists.txt index f73e165..48e444f 100644 --- a/src/crepe/manager/CMakeLists.txt +++ b/src/crepe/manager/CMakeLists.txt @@ -6,6 +6,8 @@ target_sources(crepe PUBLIC  	SceneManager.cpp  	LoopTimerManager.cpp  	ResourceManager.cpp +	ReplayManager.cpp +	SystemManager.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -21,5 +23,8 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	LoopTimerManager.h  	ResourceManager.h  	ResourceManager.hpp +	ReplayManager.h +	SystemManager.h +	SystemManager.hpp  ) diff --git a/src/crepe/manager/ComponentManager.cpp b/src/crepe/manager/ComponentManager.cpp index df30d27..745ddae 100644 --- a/src/crepe/manager/ComponentManager.cpp +++ b/src/crepe/manager/ComponentManager.cpp @@ -1,7 +1,7 @@  #include "../api/GameObject.h"  #include "../api/Metadata.h"  #include "../types.h" -#include "../util/Log.h" +#include "../util/dbg.h"  #include "ComponentManager.h" @@ -53,7 +53,7 @@ GameObject ComponentManager::new_object(const string & name, const string & tag,  		this->next_id++;  	} -	GameObject object{*this, this->next_id, name, tag, position, rotation, scale}; +	GameObject object{this->mediator, this->next_id, name, tag, position, rotation, scale};  	this->next_id++;  	return object; @@ -72,3 +72,28 @@ set<game_object_id_t> ComponentManager::get_objects_by_tag(const string & tag) c  	return this->get_objects_by_predicate<Metadata>(  		[tag](const Metadata & data) { return data.tag == tag; });  } + +ComponentManager::Snapshot ComponentManager::save() { +	Snapshot snapshot{}; +	for (const auto & [type, by_id_index] : this->components) { +		for (game_object_id_t id = 0; id < by_id_index.size(); id++) { +			const auto & components = by_id_index[id]; +			for (size_t index = 0; index < components.size(); index++) { +				const Component & component = *components[index]; +				snapshot.components.push_back(SnapshotComponent{ +					.type = type, +					.id = id, +					.index = index, +					.component = component.save(), +				}); +			} +		} +	} +	return snapshot; +} + +void ComponentManager::restore(const Snapshot & snapshot) { +	for (const SnapshotComponent & info : snapshot.components) { +		this->components[info.type][info.id][info.index]->restore(*info.component); +	} +} diff --git a/src/crepe/manager/ComponentManager.h b/src/crepe/manager/ComponentManager.h index 19a8e81..c3a5b4a 100644 --- a/src/crepe/manager/ComponentManager.h +++ b/src/crepe/manager/ComponentManager.h @@ -21,13 +21,6 @@ class GameObject;   * This class manages all components. It provides methods to add, delete and get components.   */  class ComponentManager : public Manager { -	// TODO: This relation should be removed! I (loek) believe that the scene manager should -	// create/destroy components because the GameObject's are stored in concrete Scene classes, -	// which will in turn call GameObject's destructor, which will in turn call -	// ComponentManager::delete_components_by_id or something. This is a pretty major change, so -	// here is a comment and temporary fix instead :tada: -	friend class SceneManager; -  public:  	ComponentManager(Mediator & mediator);  	~ComponentManager(); // dbg_trace @@ -49,12 +42,7 @@ public:  						  const vec2 & position = {0, 0}, double rotation = 0,  						  double scale = 1); -protected: -	/** -	 * GameObject is used as an interface to add/remove components, and the game programmer is -	 * supposed to use it instead of interfacing with the component manager directly. -	 */ -	friend class GameObject; +public:  	/**  	 * \brief Add a component to the ComponentManager  	 * @@ -154,6 +142,40 @@ public:  	template <typename T>  	RefVector<T> get_components_by_tag(const std::string & tag) const; +	//! Snapshot of single component (including path in \c components) +	struct SnapshotComponent { +		//! \c components path +		std::type_index type; +		//! \c components path +		game_object_id_t id; +		//! \c components path +		size_t index; +		//! Actual component snapshot +		std::unique_ptr<Component> component; +	}; +	//! Snapshot of the entire component manager state +	struct Snapshot { +		//! All components +		std::vector<SnapshotComponent> components; +		// TODO: some kind of hash code that ensures components exist in all the same places as +		// this snapshot +	}; +	/** +	 * \name ReplayManager (Memento) functions +	 * \{ +	 */ +	/** +	 * \brief Save a snapshot of the component manager state +	 * \returns Deep copy of the component manager's internal state +	 */ +	Snapshot save(); +	/** +	 * \brief Restore component manager from a snapshot +	 * \param snapshot Snapshot to restore from (as returned by \c save()) +	 */ +	void restore(const Snapshot & snapshot); +	//! \} +  private:  	/**  	 * \brief Get object IDs by predicate function diff --git a/src/crepe/manager/LoopTimerManager.cpp b/src/crepe/manager/LoopTimerManager.cpp index a6e4788..e78f92f 100644 --- a/src/crepe/manager/LoopTimerManager.cpp +++ b/src/crepe/manager/LoopTimerManager.cpp @@ -1,7 +1,7 @@  #include <chrono>  #include <thread> -#include "../util/Log.h" +#include "../util/dbg.h"  #include "LoopTimerManager.h" diff --git a/src/crepe/manager/LoopTimerManager.h b/src/crepe/manager/LoopTimerManager.h index 76b02d3..2f1e6b6 100644 --- a/src/crepe/manager/LoopTimerManager.h +++ b/src/crepe/manager/LoopTimerManager.h @@ -6,6 +6,8 @@  namespace crepe { +class Engine; +  typedef std::chrono::duration<float> duration_t;  typedef std::chrono::duration<unsigned long long, std::micro> elapsed_time_t; @@ -107,7 +109,7 @@ public:  private:  	//! Friend relation to use start,enforce_frame_rate,get_lag,update,advance_fixed_update. -	friend class LoopManager; +	friend class Engine;  	/**  	 * \brief Start the loop timer.  	 * diff --git a/src/crepe/manager/Mediator.h b/src/crepe/manager/Mediator.h index a336410..842f1de 100644 --- a/src/crepe/manager/Mediator.h +++ b/src/crepe/manager/Mediator.h @@ -11,6 +11,8 @@ class LoopTimerManager;  class SaveManager;  class ResourceManager;  class SDLContext; +class ReplayManager; +class SystemManager;  /**   * Struct to pass references to classes that would otherwise need to be singletons down to @@ -32,6 +34,8 @@ struct Mediator {  	OptionalRef<LoopTimerManager> loop_timer;  	OptionalRef<SaveManager> save_manager;  	OptionalRef<ResourceManager> resource_manager; +	OptionalRef<ReplayManager> replay_manager; +	OptionalRef<SystemManager> system_manager;  };  } // namespace crepe diff --git a/src/crepe/manager/ReplayManager.cpp b/src/crepe/manager/ReplayManager.cpp new file mode 100644 index 0000000..090a94e --- /dev/null +++ b/src/crepe/manager/ReplayManager.cpp @@ -0,0 +1,70 @@ +#include <format> + +#include "Manager.h" +#include "ReplayManager.h" + +using namespace crepe; +using namespace std; + +ReplayManager::ReplayManager(Mediator & mediator) : Manager(mediator) { +	mediator.replay_manager = *this; +} + +void ReplayManager::record_start() { +	if (this->state == RECORDING) this->release(this->id); +	this->id++; +	this->memory[this->id] = make_unique<Recording>(); +	this->recording = *this->memory.at(this->id); +	this->state = RECORDING; +} + +recording_t ReplayManager::record_end() { +	this->state = IDLE; +	return this->id; +} + +void ReplayManager::play(recording_t handle) { +	if (!this->memory.contains(handle)) +		throw out_of_range(format("ReplayManager: no recording for handle {}", handle)); +	this->recording = *this->memory.at(handle); +	this->recording->frame = 0; +	this->state = PLAYING; +} + +void ReplayManager::release(recording_t handle) { +	if (!this->memory.contains(handle)) return; +	this->memory.erase(handle); +} + +void ReplayManager::frame_record() { +	if (this->state != RECORDING) +		throw runtime_error("ReplayManager: frame_step called while not playing"); + +	ComponentManager & components = this->mediator.component_manager; +	Recording & recording = this->recording; + +	recording.frames.push_back(components.save()); +	recording.frame++; +} + +bool ReplayManager::frame_step() { +	if (this->state != PLAYING) +		throw runtime_error("ReplayManager: frame_step called while not playing"); + +	ComponentManager & components = this->mediator.component_manager; +	Recording & recording = this->recording; + +	ComponentManager::Snapshot & frame = recording.frames.at(recording.frame); + +	components.restore(frame); +	recording.frame++; + +	if (recording.frame < recording.frames.size()) return false; +	// end of recording +	recording.frame = 0; +	this->state = IDLE; +	this->recording.clear(); +	return true; +} + +ReplayManager::State ReplayManager::get_state() const { return this->state; } diff --git a/src/crepe/manager/ReplayManager.h b/src/crepe/manager/ReplayManager.h new file mode 100644 index 0000000..f06a58b --- /dev/null +++ b/src/crepe/manager/ReplayManager.h @@ -0,0 +1,96 @@ +#pragma once + +#include <unordered_map> + +#include "../util/OptionalRef.h" + +#include "ComponentManager.h" +#include "Manager.h" + +namespace crepe { + +//! Handle to recording held by ReplayManager +typedef size_t recording_t; + +/** + * \brief Replay manager + * + * The replay manager is responsible for creating, storing and restoring ComponentManager + * snapshots. Sequential snapshots can be recorded and replayed in combination with + * ReplaySystem. + */ +class ReplayManager : public Manager { +	// TODO: Delete recordings at end of scene + +public: +	ReplayManager(Mediator & mediator); + +public: +	//! Start a new recording +	void record_start(); +	/** +	 * \brief End the latest recording started by \c record_start() +	 * \returns Handle to recording +	 */ +	recording_t record_end(); +	/** +	 * \brief Play a recording +	 * \param handle Handle to recording (as returned by \c record_end()) +	 */ +	void play(recording_t handle); +	/** +	 * \brief Delete a recording from memory +	 * \param handle Handle to recording (as returned by \c record_end()) +	 */ +	void release(recording_t handle); + +public: +	//! Internal state +	enum State { +		IDLE, //!< Not doing anything +		RECORDING, //!< Currently recording +		PLAYING, //!< Currently playing back a recording +	}; +	//! Get current internal state +	State get_state() const; + +public: +	/** +	 * \brief Record a single frame to the current recording +	 * +	 * This function is called by ReplaySystem after the game programmer has called \c +	 * record_start() +	 */ +	void frame_record(); +	/** +	 * \brief Play the next frame of the current recording +	 * +	 * \returns `true` if the recording is finished playing +	 * \returns `false` if there are more frames +	 * +	 * This function also automatically resets the internal state from PLAYING to IDLE at the end +	 * of a recording. +	 */ +	bool frame_step(); + +private: +	/** +	 * \brief Recording data +	 */ +	struct Recording { +		//! Current frame being shown +		size_t frame = 0; +		//! All frames in recording +		std::vector<ComponentManager::Snapshot> frames; +	}; +	//! Internal state +	State state = IDLE; +	//! Current recording handle +	recording_t id = -1; +	//! Current recording data +	OptionalRef<Recording> recording; +	//! Recording storage +	std::unordered_map<recording_t, std::unique_ptr<Recording>> memory; +}; + +} // namespace crepe diff --git a/src/crepe/manager/ResourceManager.cpp b/src/crepe/manager/ResourceManager.cpp index a141a46..5713183 100644 --- a/src/crepe/manager/ResourceManager.cpp +++ b/src/crepe/manager/ResourceManager.cpp @@ -1,4 +1,4 @@ -#include "util/Log.h" +#include "util/dbg.h"  #include "ResourceManager.h" diff --git a/src/crepe/manager/SystemManager.cpp b/src/crepe/manager/SystemManager.cpp new file mode 100644 index 0000000..5ada30f --- /dev/null +++ b/src/crepe/manager/SystemManager.cpp @@ -0,0 +1,66 @@ +#include "../system/AISystem.h" +#include "../system/AnimatorSystem.h" +#include "../system/AudioSystem.h" +#include "../system/CollisionSystem.h" +#include "../system/EventSystem.h" +#include "../system/InputSystem.h" +#include "../system/ParticleSystem.h" +#include "../system/PhysicsSystem.h" +#include "../system/RenderSystem.h" +#include "../system/ReplaySystem.h" +#include "../system/ScriptSystem.h" + +#include "SystemManager.h" + +using namespace crepe; +using namespace std; + +SystemManager::SystemManager(Mediator & mediator) : Manager(mediator) { +	this->load_system<InputSystem>(); +	this->load_system<EventSystem>(); +	this->load_system<ScriptSystem>(); +	this->load_system<AISystem>(); +	this->load_system<PhysicsSystem>(); +	this->load_system<CollisionSystem>(); +	this->load_system<AudioSystem>(); +	this->load_system<AnimatorSystem>(); +	this->load_system<ParticleSystem>(); +	this->load_system<RenderSystem>(); +	this->load_system<ReplaySystem>(); + +	this->mediator.system_manager = *this; +} + +void SystemManager::fixed_update() { +	for (auto & [type, system] : this->systems) { +		if (!system->active) continue; +		system->fixed_update(); +	} +} + +void SystemManager::frame_update() { +	for (auto & [type, system] : this->systems) { +		if (!system->active) continue; +		system->frame_update(); +	} +} + +SystemManager::Snapshot SystemManager::save() { +	Snapshot snapshot; +	for (auto & [type, system] : this->systems) { +		snapshot[type] = system->active; +	} +	return snapshot; +} + +void SystemManager::restore(const Snapshot & snapshot) { +	for (auto & [type, active] : snapshot) { +		this->systems[type]->active = active; +	} +} + +void SystemManager::disable_all() { +	for (auto & [type, system] : this->systems) { +		system->active = false; +	} +} diff --git a/src/crepe/manager/SystemManager.h b/src/crepe/manager/SystemManager.h new file mode 100644 index 0000000..50acf77 --- /dev/null +++ b/src/crepe/manager/SystemManager.h @@ -0,0 +1,85 @@ +#pragma once + +#include <memory> +#include <typeindex> +#include <unordered_map> + +#include "../system/System.h" + +#include "Manager.h" + +namespace crepe { + +/** + * \brief Collection of all systems + * + * This manager aggregates all systems and provides utility functions to retrieve references to + * and update systems. + */ +class SystemManager : public Manager { +public: +	SystemManager(Mediator &); + +	/** +	 * \brief Per-frame update. +	 * +	 * Updates the game state based on the elapsed time since the last frame. +	 */ +	void frame_update(); + +	/** +	 * \brief Fixed update executed at a fixed rate. +	 * +	 * This function updates physics and game logic based on LoopTimer's fixed_delta_time. +	 */ +	void fixed_update(); + +private: +	/** +	 * \brief Collection of System instances +	 * +	 * This map holds System instances indexed by the system's class typeid. It is filled in the +	 * constructor of \c SystemManager using SystemManager::load_system. +	 */ +	std::unordered_map<std::type_index, std::unique_ptr<System>> systems; +	/** +	 * \brief Initialize a system +	 * \tparam T System type (must be derivative of \c System) +	 */ +	template <class T> +	void load_system(); + +public: +	/** +	 * \brief Retrieve a reference to ECS system +	 * \tparam T System type +	 * \returns Reference to system instance +	 * \throws std::runtime_error if the System is not initialized +	 */ +	template <class T> +	T & get_system(); + +public: +	/** +	 * \brief SystemManager snapshot +	 * +	 * The SystemManager snapshot only stores which systems are active +	 */ +	typedef std::unordered_map<std::type_index, bool> Snapshot; +	/** +	 * \brief Save a snapshot of the systems' state +	 * \returns Copy of each system's active property +	 */ +	Snapshot save(); +	/** +	 * \brief Restore system active state from a snapshot +	 * \param snapshot Snapshot to restore from (as returned by \c save()) +	 */ +	void restore(const Snapshot & snapshot); +	//! Disable all systems +	void disable_all(); +}; + +} // namespace crepe + +#include "SystemManager.hpp" diff --git a/src/crepe/api/LoopManager.hpp b/src/crepe/manager/SystemManager.hpp index 266758a..3d26e4c 100644 --- a/src/crepe/api/LoopManager.hpp +++ b/src/crepe/manager/SystemManager.hpp @@ -4,26 +4,19 @@  #include <format>  #include <memory> -#include "../system/System.h" - -#include "LoopManager.h" +#include "SystemManager.h"  namespace crepe {  template <class T> -void LoopManager::add_scene() { -	this->scene_manager.add_scene<T>(); -} - -template <class T> -T & LoopManager::get_system() { +T & SystemManager::get_system() {  	using namespace std;  	static_assert(is_base_of<System, T>::value,  				  "get_system must recieve a derivative class of System");  	const type_info & type = typeid(T);  	if (!this->systems.contains(type)) -		throw runtime_error(format("LoopManager: {} is not initialized", type.name())); +		throw runtime_error(format("SystemManager: {} is not initialized", type.name()));  	System * system = this->systems.at(type).get();  	T * concrete_system = dynamic_cast<T *>(system); @@ -33,14 +26,14 @@ T & LoopManager::get_system() {  }  template <class T> -void LoopManager::load_system() { +void SystemManager::load_system() {  	using namespace std;  	static_assert(is_base_of<System, T>::value,  				  "load_system must recieve a derivative class of System");  	const type_info & type = typeid(T);  	if (this->systems.contains(type)) -		throw runtime_error(format("LoopManager: {} is already initialized", type.name())); +		throw runtime_error(format("SystemManager: {} is already initialized", type.name()));  	System * system = new T(this->mediator);  	this->systems[type] = unique_ptr<System>(system);  } diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp index 680dbb8..0f35010 100644 --- a/src/crepe/system/AISystem.cpp +++ b/src/crepe/system/AISystem.cpp @@ -10,7 +10,7 @@  using namespace crepe;  using namespace std::chrono; -void AISystem::update() { +void AISystem::fixed_update() {  	const Mediator & mediator = this->mediator;  	ComponentManager & mgr = mediator.component_manager;  	LoopTimerManager & loop_timer = mediator.loop_timer; diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h index d5f8a8e..04807cf 100644 --- a/src/crepe/system/AISystem.h +++ b/src/crepe/system/AISystem.h @@ -20,7 +20,7 @@ public:  	using System::System;  	//! Update the AI system -	void update() override; +	void fixed_update() override;  private:  	/** diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 107b25d..e5ab2fa 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -1,5 +1,3 @@ - -  #include "../api/Animator.h"  #include "../manager/ComponentManager.h"  #include "../manager/LoopTimerManager.h" @@ -10,7 +8,7 @@  using namespace crepe;  using namespace std::chrono; -void AnimatorSystem::update() { +void AnimatorSystem::frame_update() {  	ComponentManager & mgr = this->mediator.component_manager;  	LoopTimerManager & timer = this->mediator.loop_timer;  	RefVector<Animator> animations = mgr.get_components_by_type<Animator>(); diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h index 7d3f565..092e131 100644 --- a/src/crepe/system/AnimatorSystem.h +++ b/src/crepe/system/AnimatorSystem.h @@ -22,7 +22,7 @@ public:  	 * Animator components, moving the animations forward and managing their behavior (e.g.,  	 * looping).  	 */ -	void update() override; +	void frame_update() override;  };  } // namespace crepe diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp index b1aa0f8..d4e8b9f 100644 --- a/src/crepe/system/AudioSystem.cpp +++ b/src/crepe/system/AudioSystem.cpp @@ -7,7 +7,7 @@  using namespace crepe;  using namespace std; -void AudioSystem::update() { +void AudioSystem::fixed_update() {  	ComponentManager & component_manager = this->mediator.component_manager;  	ResourceManager & resource_manager = this->mediator.resource_manager;  	RefVector<AudioSource> components diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h index 2ddc443..56fc98c 100644 --- a/src/crepe/system/AudioSystem.h +++ b/src/crepe/system/AudioSystem.h @@ -11,7 +11,7 @@ namespace crepe {  class AudioSystem : public System {  public:  	using System::System; -	void update() override; +	void fixed_update() override;  private:  	/** diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index 0e2db76..52369d0 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -8,6 +8,8 @@ target_sources(crepe PUBLIC  	AudioSystem.cpp  	AnimatorSystem.cpp  	InputSystem.cpp +	EventSystem.cpp +	ReplaySystem.cpp  	AISystem.cpp  ) @@ -20,5 +22,7 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	AudioSystem.h  	AnimatorSystem.h  	InputSystem.h +	EventSystem.h +	ReplaySystem.h  	AISystem.h  ) diff --git a/src/crepe/system/CollisionSystem.cpp b/src/crepe/system/CollisionSystem.cpp index af8adce..654d4c6 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -1,6 +1,7 @@  #include <algorithm>  #include <cmath>  #include <cstddef> +#include <emmintrin.h>  #include <functional>  #include <optional>  #include <utility> @@ -15,15 +16,25 @@  #include "api/Rigidbody.h"  #include "api/Transform.h"  #include "api/Vector2.h" +#include "util/AbsolutePosition.h" +#include "util/OptionalRef.h" -#include "Collider.h"  #include "CollisionSystem.h"  #include "types.h" -#include "util/OptionalRef.h"  using namespace crepe; +using enum Rigidbody::BodyType; + +CollisionSystem::CollisionInfo CollisionSystem::CollisionInfo::operator-() const { +	return { +		.self = this->other, +		.other = this->self, +		.resolution = -this->resolution, +		.resolution_direction = this->resolution_direction, +	}; +} -void CollisionSystem::update() { +void CollisionSystem::fixed_update() {  	std::vector<CollisionInternal> all_colliders;  	game_object_id_t id = 0;  	ComponentManager & mgr = this->mediator.component_manager; @@ -33,6 +44,7 @@ void CollisionSystem::update() {  		if (!rigidbody.active) continue;  		id = rigidbody.game_object_id;  		Transform & transform = mgr.get_components_by_id<Transform>(id).front().get(); +		Metadata & metadata = mgr.get_components_by_id<Metadata>(id).front().get();  		// Check if the boxcollider is active and has the same id as the rigidbody.  		RefVector<BoxCollider> boxcolliders = mgr.get_components_by_type<BoxCollider>();  		for (BoxCollider & boxcollider : boxcolliders) { @@ -40,8 +52,7 @@ void CollisionSystem::update() {  			if (!boxcollider.active) continue;  			all_colliders.push_back({.id = id,  									 .collider = collider_variant{boxcollider}, -									 .transform = transform, -									 .rigidbody = rigidbody}); +									 .info = {transform, rigidbody, metadata}});  		}  		// Check if the circlecollider is active and has the same id as the rigidbody.  		RefVector<CircleCollider> circlecolliders @@ -51,310 +62,450 @@ void CollisionSystem::update() {  			if (!circlecollider.active) continue;  			all_colliders.push_back({.id = id,  									 .collider = collider_variant{circlecollider}, -									 .transform = transform, -									 .rigidbody = rigidbody}); +									 .info = {transform, rigidbody, metadata}});  		}  	} -	// Check between all colliders if there is a collision +	// Check between all colliders if there is a collision (collision handling)  	std::vector<std::pair<CollisionInternal, CollisionInternal>> collided  		= this->gather_collisions(all_colliders); -	// For both objects call the collision handler +	// For the object convert the info and call the collision handler if needed  	for (auto & collision_pair : collided) { -		this->collision_handler_request(collision_pair.first, collision_pair.second); -		this->collision_handler_request(collision_pair.second, collision_pair.first); +		// Convert internal struct to external struct +		CollisionInfo info +			= this->get_collision_info(collision_pair.first, collision_pair.second); +		// Determine if and/or what collison handler is needed. +		this->determine_collision_handler(info);  	}  } -void CollisionSystem::collision_handler_request(CollisionInternal & this_data, -												CollisionInternal & other_data) { +// Below is for collision detection +std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>> +CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) { -	CollisionInternalType type -		= this->get_collider_type(this_data.collider, other_data.collider); -	std::pair<vec2, CollisionSystem::Direction> resolution_data -		= this->collision_handler(this_data, other_data, type); -	ComponentManager & mgr = this->mediator.component_manager; -	OptionalRef<Metadata> this_metadata -		= mgr.get_components_by_id<Metadata>(this_data.id).front().get(); -	OptionalRef<Metadata> other_metadata -		= mgr.get_components_by_id<Metadata>(other_data.id).front().get(); -	OptionalRef<Collider> this_collider; -	OptionalRef<Collider> other_collider; -	switch (type) { -		case CollisionInternalType::BOX_BOX: { -			this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); -			other_collider -				= std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); -			break; -		} -		case CollisionInternalType::BOX_CIRCLE: { -			this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); -			other_collider -				= std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); -			break; -		} -		case CollisionInternalType::CIRCLE_BOX: { -			this_collider -				= std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); -			other_collider -				= std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); -			break; -		} -		case CollisionInternalType::CIRCLE_CIRCLE: { -			this_collider -				= std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); -			other_collider -				= std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); -			break; +	// TODO: +	// If no colliders skip +	// Check if colliders has rigidbody if not skip + +	// TODO: +	// If amount is higer than lets say 16 for now use quadtree otwerwise skip +	// Quadtree code +	// Quadtree is placed over the input vector + +	// Return data of collided colliders which are variants +	std::vector<std::pair<CollisionInternal, CollisionInternal>> collisions_ret; +	//using visit to visit the variant to access the active and id. +	for (size_t i = 0; i < colliders.size(); ++i) { +		for (size_t j = i + 1; j < colliders.size(); ++j) { +			if (colliders[i].id == colliders[j].id) continue; +			if (!should_collide(colliders[i], colliders[j])) continue; +			CollisionInternalType type +				= get_collider_type(colliders[i].collider, colliders[j].collider); +			if (!detect_collision(colliders[i], colliders[j], type)) continue; +			//fet +			collisions_ret.emplace_back(colliders[i], colliders[j]);  		}  	} +	return collisions_ret; +} -	// collision info -	crepe::CollisionSystem::CollisionInfo collision_info{ -		.this_collider = this_collider, -		.this_transform = this_data.transform, -		.this_rigidbody = this_data.rigidbody, -		.this_metadata = this_metadata, -		.other_collider = other_collider, -		.other_transform = other_data.transform, -		.other_rigidbody = other_data.rigidbody, -		.other_metadata = other_metadata, -		.resolution = resolution_data.first, -		.resolution_direction = resolution_data.second, -	}; +bool CollisionSystem::should_collide(const CollisionInternal & self, +									 const CollisionInternal & other) const { + +	const Rigidbody::Data & self_rigidbody = self.info.rigidbody.data; +	const Rigidbody::Data & other_rigidbody = other.info.rigidbody.data; +	const Metadata & self_metadata = self.info.metadata; +	const Metadata & other_metadata = other.info.metadata; + +	// Check collision layers +	if (self_rigidbody.collision_layers.contains(other_rigidbody.collision_layer)) return true; +	if (other_rigidbody.collision_layers.contains(self_rigidbody.collision_layer)) return true; -	// Determine if static needs to be called -	this->determine_collision_handler(collision_info); +	// Check names +	if (self_rigidbody.collision_names.contains(other_metadata.name)) return true; +	if (other_rigidbody.collision_names.contains(self_metadata.name)) return true; + +	// Check tags +	if (self_rigidbody.collision_tags.contains(other_metadata.tag)) return true; +	if (other_rigidbody.collision_tags.contains(self_metadata.tag)) return true; + +	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; +		} +	}  } -std::pair<vec2, CollisionSystem::Direction> -CollisionSystem::collision_handler(CollisionInternal & data1, CollisionInternal & data2, -								   CollisionInternalType type) { +bool CollisionSystem::detect_collision(CollisionInternal & self, CollisionInternal & other, +									   const CollisionInternalType & type) {  	vec2 resolution;  	switch (type) {  		case CollisionInternalType::BOX_BOX: { -			const BoxCollider & collider1 -				= std::get<std::reference_wrapper<BoxCollider>>(data1.collider); -			const BoxCollider & collider2 -				= std::get<std::reference_wrapper<BoxCollider>>(data2.collider); -			vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, -															data1.rigidbody); -			vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, -															data2.rigidbody); -			resolution = this->get_box_box_resolution(collider1, collider2, collider_pos1, -													  collider_pos2); +			// Box-Box collision detection +			const BoxColliderInternal BOX1 +				= {.collider = std::get<std::reference_wrapper<BoxCollider>>(self.collider), +				   .transform = self.info.transform, +				   .rigidbody = self.info.rigidbody}; +			const BoxColliderInternal BOX2 +				= {.collider = std::get<std::reference_wrapper<BoxCollider>>(other.collider), +				   .transform = other.info.transform, +				   .rigidbody = other.info.rigidbody}; +			// Get resolution vector from box-box collision detection +			resolution = this->get_box_box_detection(BOX1, BOX2); +			// If no collision (NaN values), return false +			if (resolution.is_nan()) return false;  			break;  		}  		case CollisionInternalType::BOX_CIRCLE: { -			const BoxCollider & collider1 -				= std::get<std::reference_wrapper<BoxCollider>>(data1.collider); -			const CircleCollider & collider2 -				= std::get<std::reference_wrapper<CircleCollider>>(data2.collider); -			vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, -															data1.rigidbody); -			vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, -															data2.rigidbody); -			resolution = -this->get_circle_box_resolution(collider2, collider1, collider_pos2, -														  collider_pos1); +			// Box-Circle collision detection +			const BoxColliderInternal BOX1 +				= {.collider = std::get<std::reference_wrapper<BoxCollider>>(self.collider), +				   .transform = self.info.transform, +				   .rigidbody = self.info.rigidbody}; +			const CircleColliderInternal CIRCLE2 = { +				.collider = std::get<std::reference_wrapper<CircleCollider>>(other.collider), +				.transform = other.info.transform, +				.rigidbody = other.info.rigidbody}; +			// Get resolution vector from box-circle collision detection +			resolution = this->get_box_circle_detection(BOX1, CIRCLE2); +			// If no collision (NaN values), return false +			if (resolution.is_nan()) return false; +			// Invert the resolution vector for proper collision response +			resolution = -resolution;  			break;  		}  		case CollisionInternalType::CIRCLE_CIRCLE: { -			const CircleCollider & collider1 -				= std::get<std::reference_wrapper<CircleCollider>>(data1.collider); -			const CircleCollider & collider2 -				= std::get<std::reference_wrapper<CircleCollider>>(data2.collider); -			vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, -															data1.rigidbody); -			vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, -															data2.rigidbody); -			resolution = this->get_circle_circle_resolution(collider1, collider2, -															collider_pos1, collider_pos2); +			// Circle-Circle collision detection +			const CircleColliderInternal CIRCLE1 +				= {.collider = std::get<std::reference_wrapper<CircleCollider>>(self.collider), +				   .transform = self.info.transform, +				   .rigidbody = self.info.rigidbody}; +			const CircleColliderInternal CIRCLE2 = { +				.collider = std::get<std::reference_wrapper<CircleCollider>>(other.collider), +				.transform = other.info.transform, +				.rigidbody = other.info.rigidbody}; +			// Get resolution vector from circle-circle collision detection +			resolution = this->get_circle_circle_detection(CIRCLE1, CIRCLE2); +			// If no collision (NaN values), return false +			if (resolution.is_nan()) return false;  			break;  		}  		case CollisionInternalType::CIRCLE_BOX: { -			const CircleCollider & collider1 -				= std::get<std::reference_wrapper<CircleCollider>>(data1.collider); -			const BoxCollider & collider2 -				= std::get<std::reference_wrapper<BoxCollider>>(data2.collider); -			vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, -															data1.rigidbody); -			vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, -															data2.rigidbody); -			resolution = this->get_circle_box_resolution(collider1, collider2, collider_pos1, -														 collider_pos2); +			// Circle-Box collision detection +			const CircleColliderInternal CIRCLE1 +				= {.collider = std::get<std::reference_wrapper<CircleCollider>>(self.collider), +				   .transform = self.info.transform, +				   .rigidbody = self.info.rigidbody}; +			const BoxColliderInternal BOX2 +				= {.collider = std::get<std::reference_wrapper<BoxCollider>>(other.collider), +				   .transform = other.info.transform, +				   .rigidbody = other.info.rigidbody}; +			// Get resolution vector from box-circle collision detection (order swapped) +			resolution = this->get_box_circle_detection(BOX2, CIRCLE1); +			// If no collision (NaN values), return false +			if (resolution.is_nan()) return false;  			break;  		} +		case CollisionInternalType::NONE: +			// No collision detection needed if the type is NONE +			return false; +			break;  	} +	// Store the calculated resolution vector for the 'self' collider +	self.resolution = resolution; +	// Calculate the resolution direction based on the rigidbody data +	self.resolution_direction +		= this->resolution_correction(self.resolution, self.info.rigidbody.data); +	// For the 'other' collider, the resolution is the opposite direction of 'self' +	other.resolution = -self.resolution; +	other.resolution_direction = self.resolution_direction; + +	// Return true if a collision was detected and resolution was calculated +	return true; +} -	Direction resolution_direction = Direction::NONE; -	if (resolution.x != 0 && resolution.y != 0) { -		resolution_direction = Direction::BOTH; -	} else if (resolution.x != 0) { -		resolution_direction = Direction::X_DIRECTION; -		//checks if the other velocity has a value and if this object moved -		if (data1.rigidbody.data.linear_velocity.x != 0 -			&& data1.rigidbody.data.linear_velocity.y != 0) -			resolution.y = -data1.rigidbody.data.linear_velocity.y -						   * (resolution.x / data1.rigidbody.data.linear_velocity.x); -	} else if (resolution.y != 0) { -		resolution_direction = Direction::Y_DIRECTION; -		//checks if the other velocity has a value and if this object moved -		if (data1.rigidbody.data.linear_velocity.x != 0 -			&& data1.rigidbody.data.linear_velocity.y != 0) -			resolution.x = -data1.rigidbody.data.linear_velocity.x -						   * (resolution.y / data1.rigidbody.data.linear_velocity.y); -	} +vec2 CollisionSystem::get_box_box_detection(const BoxColliderInternal & box1, +											const BoxColliderInternal & box2) const { +	vec2 resolution{NAN, NAN}; +	// Get current positions of colliders +	vec2 pos1 = AbsolutePosition::get_position(box1.transform, box1.collider.offset); +	vec2 pos2 = AbsolutePosition::get_position(box2.transform, box2.collider.offset); -	return std::make_pair(resolution, resolution_direction); -} +	// Scale dimensions +	vec2 scaled_box1 = box1.collider.dimensions * box1.transform.scale; +	vec2 scaled_box2 = box2.collider.dimensions * box2.transform.scale; +	vec2 delta = pos2 - pos1; -vec2 CollisionSystem::get_box_box_resolution(const BoxCollider & box_collider1, -											 const BoxCollider & box_collider2, -											 const vec2 & final_position1, -											 const vec2 & final_position2) const { -	vec2 resolution; // Default resolution vector -	vec2 delta = final_position2 - final_position1; - -	// Compute half-dimensions of the boxes -	float half_width1 = box_collider1.dimensions.x / 2.0; -	float half_height1 = box_collider1.dimensions.y / 2.0; -	float half_width2 = box_collider2.dimensions.x / 2.0; -	float half_height2 = box_collider2.dimensions.y / 2.0; - -	// Calculate overlaps along X and Y axes -	float overlap_x = (half_width1 + half_width2) - std::abs(delta.x); -	float overlap_y = (half_height1 + half_height2) - std::abs(delta.y); - -	// Check if there is a collision should always be true -	if (overlap_x > 0 && overlap_y > 0) { -		// Determine the direction of resolution -		if (overlap_x < overlap_y) { -			// Resolve along the X-axis (smallest overlap) -			resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; -		} else if (overlap_y < overlap_x) { -			// Resolve along the Y-axis (smallest overlap) -			resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; -		} else { -			// Equal overlap, resolve both directions with preference -			resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; -			resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; +	// Calculate half-extents (half width and half height) +	float half_width1 = scaled_box1.x / 2.0; +	float half_height1 = scaled_box1.y / 2.0; +	float half_width2 = scaled_box2.x / 2.0; +	float half_height2 = scaled_box2.y / 2.0; + +	if (pos1.x + half_width1 > pos2.x - half_width2 +		&& pos1.x - half_width1 < pos2.x + half_width2 +		&& pos1.y + half_height1 > pos2.y - half_height2 +		&& pos1.y - half_height1 < pos2.y + half_height2) { +		resolution = {0, 0}; +		float overlap_x = (half_width1 + half_width2) - std::abs(delta.x); +		float overlap_y = (half_height1 + half_height2) - std::abs(delta.y); +		if (overlap_x > 0 && overlap_y > 0) { +			// Determine the direction of resolution +			if (overlap_x < overlap_y) { +				// Resolve along the X-axis (smallest overlap) +				resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; +			} else if (overlap_y < overlap_x) { +				// Resolve along the Y-axis (smallest overlap) +				resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; +			} else { +				// Equal overlap, resolve both directions with preference +				resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; +				resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; +			}  		}  	} -  	return resolution;  } -vec2 CollisionSystem::get_circle_circle_resolution(const CircleCollider & circle_collider1, -												   const CircleCollider & circle_collider2, -												   const vec2 & final_position1, -												   const vec2 & final_position2) const { -	vec2 delta = final_position2 - final_position1; +vec2 CollisionSystem::get_box_circle_detection(const BoxColliderInternal & box, +											   const CircleColliderInternal & circle) const { +	/// Get current positions of colliders +	vec2 box_pos = AbsolutePosition::get_position(box.transform, box.collider.offset); +	vec2 circle_pos = AbsolutePosition::get_position(circle.transform, circle.collider.offset); -	// Compute the distance between the two circle centers -	float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); +	// Scale dimensions +	vec2 scaled_box = box.collider.dimensions * box.transform.scale; +	float scaled_circle_radius = circle.collider.radius * circle.transform.scale; -	// Compute the combined radii of the two circles -	float combined_radius = circle_collider1.radius + circle_collider2.radius; +	// Calculate box half-extents +	float half_width = scaled_box.x / 2.0f; +	float half_height = scaled_box.y / 2.0f; -	// Compute the penetration depth -	float penetration_depth = combined_radius - distance; +	// Find the closest point on the box to the circle's center +	float closest_x +		= std::max(box_pos.x - half_width, std::min(circle_pos.x, box_pos.x + half_width)); +	float closest_y +		= std::max(box_pos.y - half_height, std::min(circle_pos.y, box_pos.y + half_height)); -	// Normalize the delta vector to get the collision direction -	vec2 collision_normal = delta / distance; +	float distance_x = circle_pos.x - closest_x; +	float distance_y = circle_pos.y - closest_y; +	float distance_squared = distance_x * distance_x + distance_y * distance_y; +	if (distance_squared < scaled_circle_radius * scaled_circle_radius) { +		vec2 delta = circle_pos - box_pos; -	// Compute the resolution vector -	vec2 resolution = -collision_normal * penetration_depth; +		// Clamp circle center to the nearest point on the box +		vec2 closest_point; +		closest_point.x = std::clamp(delta.x, -half_width, half_width); +		closest_point.y = std::clamp(delta.y, -half_height, half_height); -	return resolution; +		// Find the vector from the circle center to the closest point +		vec2 closest_delta = delta - closest_point; + +		float distance +			= std::sqrt(closest_delta.x * closest_delta.x + closest_delta.y * closest_delta.y); +		vec2 collision_normal = closest_delta / distance; + +		// Compute penetration depth +		float penetration_depth = scaled_circle_radius - distance; + +		// Compute the resolution vector +		return vec2{collision_normal * penetration_depth}; +	} +	// No collision +	return vec2{NAN, NAN};  } -vec2 CollisionSystem::get_circle_box_resolution(const CircleCollider & circle_collider, -												const BoxCollider & box_collider, -												const vec2 & circle_position, -												const vec2 & box_position) const { -	vec2 delta = circle_position - box_position; +vec2 CollisionSystem::get_circle_circle_detection( +	const CircleColliderInternal & circle1, const CircleColliderInternal & circle2) const { +	// Get current positions of colliders +	vec2 final_position1 +		= AbsolutePosition::get_position(circle1.transform, circle1.collider.offset); +	vec2 final_position2 +		= AbsolutePosition::get_position(circle2.transform, circle2.collider.offset); -	// Compute half-dimensions of the box -	float half_width = box_collider.dimensions.x / 2.0f; -	float half_height = box_collider.dimensions.y / 2.0f; +	// Scale dimensions +	float scaled_circle1 = circle1.collider.radius * circle1.transform.scale; +	float scaled_circle2 = circle2.collider.radius * circle2.transform.scale; -	// Clamp circle center to the nearest point on the box -	vec2 closest_point; -	closest_point.x = std::clamp(delta.x, -half_width, half_width); -	closest_point.y = std::clamp(delta.y, -half_height, half_height); +	float distance_x = final_position1.x - final_position2.x; +	float distance_y = final_position1.y - final_position2.y; +	float distance_squared = distance_x * distance_x + distance_y * distance_y; -	// Find the vector from the circle center to the closest point -	vec2 closest_delta = delta - closest_point; +	// Calculate the sum of the radii +	float radius_sum = scaled_circle1 + scaled_circle2; -	// 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; +	// Check for collision (distance squared must be less than the square of the radius sum) +	if (distance_squared < radius_sum * radius_sum) { +		vec2 delta = final_position2 - final_position1; -	// Compute penetration depth -	float penetration_depth = circle_collider.radius - distance; +		// Compute the distance between the two circle centers +		float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); -	// Compute the resolution vector -	vec2 resolution = collision_normal * penetration_depth; +		// Compute the combined radii of the two circles +		float combined_radius = scaled_circle1 + scaled_circle2; -	return resolution; +		// 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; +	} +	// No collision +	return vec2{NAN, NAN}; +	;  } -void CollisionSystem::determine_collision_handler(CollisionInfo & info) { -	// Check rigidbody type for static -	if (info.this_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) return; -	// If second body is static perform the static collision handler in this system -	if (info.other_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) { -		this->static_collision_handler(info); +CollisionSystem::Direction +CollisionSystem::resolution_correction(vec2 & resolution, const Rigidbody::Data & rigidbody) { + +	// Calculate the other value to move back correctly +	// If only X or Y has a value determine what is should be to move back. +	Direction resolution_direction = Direction::NONE; +	// If both are not zero a perfect corner has been hit +	if (resolution.x != 0 && resolution.y != 0) { +		resolution_direction = Direction::BOTH; +		// If x is not zero a horizontal action was latest action. +	} else if (resolution.x != 0) { +		resolution_direction = Direction::X_DIRECTION; +		// If both are 0 resolution y should not be changed (y_velocity can be 0 by kinematic object movement) +		if (rigidbody.linear_velocity.x != 0 && rigidbody.linear_velocity.y != 0) +			resolution.y +				= -rigidbody.linear_velocity.y * (resolution.x / rigidbody.linear_velocity.x); +	} else if (resolution.y != 0) { +		resolution_direction = Direction::Y_DIRECTION; +		// If both are 0 resolution x should not be changed (x_velocity can be 0 by kinematic object movement) +		if (rigidbody.linear_velocity.x != 0 && rigidbody.linear_velocity.y != 0) +			resolution.x +				= -rigidbody.linear_velocity.x * (resolution.y / rigidbody.linear_velocity.y); +	} + +	return resolution_direction; +} + +CollisionSystem::CollisionInfo +CollisionSystem::get_collision_info(const CollisionInternal & in_self, +									const CollisionInternal & in_other) const { + +	crepe::CollisionSystem::ColliderInfo self{ +		.transform = in_self.info.transform, +		.rigidbody = in_self.info.rigidbody, +		.metadata = in_self.info.metadata,  	}; -	// Call collision event for user -	CollisionEvent data(info); -	EventManager & emgr = this->mediator.event_manager; -	emgr.trigger_event<CollisionEvent>(data, info.this_collider.game_object_id); + +	crepe::CollisionSystem::ColliderInfo other{ +		.transform = in_other.info.transform, +		.rigidbody = in_other.info.rigidbody, +		.metadata = in_other.info.metadata, +	}; + +	struct CollisionInfo collision_info { +		.self = self, .other = other, .resolution = in_self.resolution, +		.resolution_direction = in_self.resolution_direction, +	}; +	return collision_info;  } -void CollisionSystem::static_collision_handler(CollisionInfo & info) { +void CollisionSystem::determine_collision_handler(const CollisionInfo & info) { +	Rigidbody::BodyType self_type = info.self.rigidbody.data.body_type; +	Rigidbody::BodyType other_type = info.other.rigidbody.data.body_type; +	bool self_kinematic = info.self.rigidbody.data.kinematic_collision; +	bool other_kinematic = info.other.rigidbody.data.kinematic_collision; +	// Inverted collision info +	CollisionInfo inverted = -info; +	// If both objects are static skip handle call collision script +	if (self_type == STATIC && other_type == STATIC) return; + +	//	First body is not dynamic +	if (self_type != DYNAMIC) { +		bool static_collision = self_type == STATIC && other_type == DYNAMIC; +		bool kinematic_collision +			= self_type == KINEMATIC && other_type == DYNAMIC && self_kinematic; + +		// Handle collision +		if (static_collision || kinematic_collision) this->static_collision_handler(inverted); +		// Call scripts +		this->call_collision_events(inverted); +		return; +	} + +	// Second body is not dynamic +	if (other_type != DYNAMIC) { +		bool static_collision = other_type == STATIC; +		bool kinematic_collision = other_type == KINEMATIC && other_kinematic; +		// Handle collision +		if (static_collision || kinematic_collision) this->static_collision_handler(info); +		// Call scripts +		this->call_collision_events(info); +		return; +	} + +	// Dynamic +	// Handle collision +	this->dynamic_collision_handler(info); +	// Call scripts +	this->call_collision_events(info); +} + +void CollisionSystem::static_collision_handler(const CollisionInfo & info) { + +	vec2 & transform_pos = info.self.transform.position; +	float elasticity = info.self.rigidbody.data.elasticity_coefficient; +	vec2 & rigidbody_vel = info.self.rigidbody.data.linear_velocity; +  	// Move object back using calculate move back value -	info.this_transform.position += info.resolution; +	transform_pos += info.resolution;  	switch (info.resolution_direction) {  		case Direction::BOTH:  			//bounce -			if (info.this_rigidbody.data.elastisity_coefficient > 0) { -				info.this_rigidbody.data.linear_velocity -					= -info.this_rigidbody.data.linear_velocity -					  * info.this_rigidbody.data.elastisity_coefficient; +			if (elasticity > 0) { +				rigidbody_vel = -rigidbody_vel * elasticity;  			}  			//stop movement  			else { -				info.this_rigidbody.data.linear_velocity = {0, 0}; +				rigidbody_vel = {0, 0};  			}  			break;  		case Direction::Y_DIRECTION:  			// Bounce -			if (info.this_rigidbody.data.elastisity_coefficient > 0) { -				info.this_rigidbody.data.linear_velocity.y -					= -info.this_rigidbody.data.linear_velocity.y -					  * info.this_rigidbody.data.elastisity_coefficient; +			if (elasticity > 0) { +				rigidbody_vel.y = -rigidbody_vel.y * elasticity;  			}  			// Stop movement  			else { -				info.this_rigidbody.data.linear_velocity.y = 0; -				info.this_transform.position.x -= info.resolution.x; +				rigidbody_vel.y = 0; +				transform_pos.x -= info.resolution.x;  			}  			break;  		case Direction::X_DIRECTION:  			// Bounce -			if (info.this_rigidbody.data.elastisity_coefficient > 0) { -				info.this_rigidbody.data.linear_velocity.x -					= -info.this_rigidbody.data.linear_velocity.x -					  * info.this_rigidbody.data.elastisity_coefficient; +			if (elasticity > 0) { +				rigidbody_vel.x = -rigidbody_vel.x * elasticity;  			}  			// Stop movement  			else { -				info.this_rigidbody.data.linear_velocity.x = 0; -				info.this_transform.position.y -= info.resolution.y; +				rigidbody_vel.x = 0; +				transform_pos.y -= info.resolution.y;  			}  			break;  		case Direction::NONE: @@ -363,213 +514,80 @@ void CollisionSystem::static_collision_handler(CollisionInfo & info) {  	}  } -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 +void CollisionSystem::dynamic_collision_handler(const CollisionInfo & info) { -	// 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]); -		} -	} +	vec2 & self_transform_pos = info.self.transform.position; +	vec2 & other_transform_pos = info.other.transform.position; +	float self_elasticity = info.self.rigidbody.data.elasticity_coefficient; +	float other_elasticity = info.other.rigidbody.data.elasticity_coefficient; +	vec2 & self_rigidbody_vel = info.self.rigidbody.data.linear_velocity; +	vec2 & other_rigidbody_vel = info.other.rigidbody.data.linear_velocity; -	return collisions_ret; -} +	self_transform_pos += info.resolution / 2; +	other_transform_pos += -(info.resolution / 2); -bool CollisionSystem::have_common_layer(const std::set<int> & layers1, -										const std::set<int> & layers2) { +	switch (info.resolution_direction) { +		case Direction::BOTH: +			if (self_elasticity > 0) { +				self_rigidbody_vel = -self_rigidbody_vel * self_elasticity; +			} else { +				self_rigidbody_vel = {0, 0}; +			} -	// Check if any number is equal in the layers -	for (int num : layers1) { -		if (layers2.contains(num)) { -			// Common layer found -			return true; +			if (other_elasticity > 0) { +				other_rigidbody_vel = -other_rigidbody_vel * other_elasticity; +			} else { +				other_rigidbody_vel = {0, 0}; +			}  			break; -		} -	} -	// No common layer found -	return false; -} +		case Direction::Y_DIRECTION: +			if (self_elasticity > 0) { +				self_rigidbody_vel.y = -self_rigidbody_vel.y * self_elasticity; +			} +			// Stop movement +			else { +				self_rigidbody_vel.y = 0; +				self_transform_pos.x -= info.resolution.x; +			} -CollisionSystem::CollisionInternalType -CollisionSystem::get_collider_type(const collider_variant & collider1, -								   const collider_variant & collider2) const { -	if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider1)) { -		if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { -			return CollisionInternalType::CIRCLE_CIRCLE; -		} else { -			return CollisionInternalType::CIRCLE_BOX; -		} -	} else { -		if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { -			return CollisionInternalType::BOX_CIRCLE; -		} else { -			return CollisionInternalType::BOX_BOX; -		} -	} -} +			if (other_elasticity > 0) { +				other_rigidbody_vel.y = -other_rigidbody_vel.y * other_elasticity; +			} +			// Stop movement +			else { +				other_rigidbody_vel.y = 0; +				other_transform_pos.x -= info.resolution.x; +			} +			break; +		case Direction::X_DIRECTION: +			if (self_elasticity > 0) { +				self_rigidbody_vel.x = -self_rigidbody_vel.x * self_elasticity; +			} +			// Stop movement +			else { +				self_rigidbody_vel.x = 0; +				self_transform_pos.y -= info.resolution.y; +			} -bool CollisionSystem::get_collision(const CollisionInternal & first_info, -									const CollisionInternal & second_info, -									CollisionInternalType type) const { -	switch (type) { -		case CollisionInternalType::BOX_BOX: { -			const BoxCollider & box_collider1 -				= std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); -			const BoxCollider & box_collider2 -				= std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); -			return this->get_box_box_collision(box_collider1, box_collider2, -											   first_info.transform, second_info.transform, -											   second_info.rigidbody, second_info.rigidbody); -		} -		case CollisionInternalType::BOX_CIRCLE: { -			const BoxCollider & box_collider -				= std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); -			const CircleCollider & circle_collider -				= std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); -			return this->get_box_circle_collision( -				box_collider, circle_collider, first_info.transform, second_info.transform, -				second_info.rigidbody, second_info.rigidbody); -		} -		case CollisionInternalType::CIRCLE_CIRCLE: { -			const CircleCollider & circle_collider1 -				= std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); -			const CircleCollider & circle_collider2 -				= std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); -			return this->get_circle_circle_collision( -				circle_collider1, circle_collider2, first_info.transform, -				second_info.transform, second_info.rigidbody, second_info.rigidbody); -		} -		case CollisionInternalType::CIRCLE_BOX: { -			const CircleCollider & circle_collider -				= std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); -			const BoxCollider & box_collider -				= std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); -			return this->get_box_circle_collision( -				box_collider, circle_collider, first_info.transform, second_info.transform, -				second_info.rigidbody, second_info.rigidbody); -		} +			if (other_elasticity > 0) { +				other_rigidbody_vel.x = -other_rigidbody_vel.x * other_elasticity; +			} +			// Stop movement +			else { +				other_rigidbody_vel.x = 0; +				other_transform_pos.y -= info.resolution.y; +			} +			break; +		case Direction::NONE: +			// Not possible +			break;  	} -	return false;  } -bool CollisionSystem::get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, -											const Transform & transform1, -											const Transform & transform2, -											const Rigidbody & rigidbody1, -											const Rigidbody & rigidbody2) const { -	// Get current positions of colliders -	vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); -	vec2 final_position2 = this->get_current_position(box2.offset, transform2, rigidbody2); - -	// Calculate half-extents (half width and half height) -	float half_width1 = box1.dimensions.x / 2.0; -	float half_height1 = box1.dimensions.y / 2.0; -	float half_width2 = box2.dimensions.x / 2.0; -	float half_height2 = box2.dimensions.y / 2.0; - -	// Check if the boxes overlap along the X and Y axes -	return (final_position1.x + half_width1 > final_position2.x - half_width2 -			&& final_position1.x - half_width1 < final_position2.x + half_width2 -			&& final_position1.y + half_height1 > final_position2.y - half_height2 -			&& final_position1.y - half_height1 < final_position2.y + half_height2); -} - -bool CollisionSystem::get_box_circle_collision(const BoxCollider & box1, -											   const CircleCollider & circle2, -											   const Transform & transform1, -											   const Transform & transform2, -											   const Rigidbody & rigidbody1, -											   const Rigidbody & rigidbody2) const { -	// Get current positions of colliders -	vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); -	vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2); - -	// Calculate box half-extents -	float half_width = box1.dimensions.x / 2.0; -	float half_height = box1.dimensions.y / 2.0; - -	// Find the closest point on the box to the circle's center -	float closest_x = std::max(final_position1.x - half_width, -							   std::min(final_position2.x, final_position1.x + half_width)); -	float closest_y = std::max(final_position1.y - half_height, -							   std::min(final_position2.y, final_position1.y + half_height)); - -	// Calculate the distance squared between the circle's center and the closest point on the box -	float distance_x = final_position2.x - closest_x; -	float distance_y = final_position2.y - closest_y; -	float distance_squared = distance_x * distance_x + distance_y * distance_y; - -	// Compare distance squared with the square of the circle's radius -	return distance_squared < circle2.radius * circle2.radius; -} - -bool CollisionSystem::get_circle_circle_collision(const CircleCollider & circle1, -												  const CircleCollider & circle2, -												  const Transform & transform1, -												  const Transform & transform2, -												  const Rigidbody & rigidbody1, -												  const Rigidbody & rigidbody2) const { -	// Get current positions of colliders -	vec2 final_position1 = this->get_current_position(circle1.offset, transform1, rigidbody1); -	vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2); - -	float distance_x = final_position1.x - final_position2.x; -	float distance_y = final_position1.y - final_position2.y; -	float distance_squared = distance_x * distance_x + distance_y * distance_y; - -	// Calculate the sum of the radii -	float radius_sum = circle1.radius + circle2.radius; - -	// Check if the distance between the centers is less than or equal to the sum of the radii -	return distance_squared < radius_sum * radius_sum; -} - -vec2 CollisionSystem::get_current_position(const vec2 & collider_offset, -										   const Transform & transform, -										   const Rigidbody & rigidbody) const { -	// Get the rotation in radians -	float radians1 = transform.rotation * (M_PI / 180.0); - -	// Calculate total offset with scale -	vec2 total_offset = (rigidbody.data.offset + collider_offset) * transform.scale; - -	// Rotate -	float rotated_total_offset_x1 -		= total_offset.x * cos(radians1) - total_offset.y * sin(radians1); -	float rotated_total_offset_y1 -		= total_offset.x * sin(radians1) + total_offset.y * cos(radians1); - -	// Final positions considering scaling and rotation -	return (transform.position + vec2(rotated_total_offset_x1, rotated_total_offset_y1)); +void CollisionSystem::call_collision_events(const CollisionInfo & info) { +	CollisionEvent data(info); +	CollisionEvent data_inverted(-info); +	EventManager & emgr = this->mediator.event_manager; +	emgr.trigger_event<CollisionEvent>(data, info.self.transform.game_object_id); +	emgr.trigger_event<CollisionEvent>(data_inverted, info.other.transform.game_object_id);  } diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h index 5b136c6..3fb9723 100644 --- a/src/crepe/system/CollisionSystem.h +++ b/src/crepe/system/CollisionSystem.h @@ -23,6 +23,42 @@ public:  	using System::System;  private: +	//! 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: +	//! Structure representing components of the collider +	struct ColliderInfo { +		Transform & transform; +		Rigidbody & rigidbody; +		Metadata & metadata; +	}; + +	/** +		* \brief Structure representing detailed collision information between two colliders. +		* +		* Includes information about the colliding objects and the resolution data for handling the collision. +		*/ +	struct CollisionInfo { +		ColliderInfo self; +		ColliderInfo other; +		//! The resolution vector for the collision. +		vec2 resolution; +		//! The direction of movement for resolving the collision. +		Direction resolution_direction = Direction::NONE; +		CollisionInfo operator-() const; +	}; + +private:  	//! A variant type that can hold either a BoxCollider or a CircleCollider.  	using collider_variant = std::variant<std::reference_wrapper<BoxCollider>,  										  std::reference_wrapper<CircleCollider>>; @@ -33,12 +69,13 @@ private:  		CIRCLE_CIRCLE,  		BOX_CIRCLE,  		CIRCLE_BOX, +		NONE,  	};  	/**  		* \brief A structure to store the collision data of a single collider.  		* -		* This structure all components and id that are for needed within this system when calculating or handeling collisions. +		* This structure all components and id that are for needed within this system when calculating or handling collisions.  		* The transform and rigidbody are mostly needed for location and rotation.  		* In rigidbody additional info is written about what the body of the object is,  		* and how it should respond on a collision. @@ -46,46 +83,26 @@ private:  	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. +		ColliderInfo info;  		vec2 resolution; -		//! The direction of movement for resolving the collision.  		Direction resolution_direction = Direction::NONE;  	}; +	//! Structure of a collider with additional components +	template <typename ColliderType> +	struct ColliderInternal { +		ColliderType & collider; +		Transform & transform; +		Rigidbody & rigidbody; +	}; +	//! Predefined BoxColliderInternal. (System is only made for this type) +	using BoxColliderInternal = ColliderInternal<BoxCollider>; +	//! Predefined CircleColliderInternal. (System is only made for this type) +	using CircleColliderInternal = ColliderInternal<CircleCollider>; +  public:  	//! Updates the collision system by checking for collisions between colliders and handling them. -	void update() override; +	void fixed_update() override;  private:  	/** @@ -100,114 +117,90 @@ private:  	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. +		* \brief Converts internal collision data into user-accessible collision information.  		* -		* Processes collision data and adjusts objects to resolve collisions and/or calls the user oncollision script function. +		* This function processes collision data from two colliding entities and packages it + 		* into a structured format that is accessible for further use, + 		* such as resolving collisions and triggering user-defined collision scripts.  		*  		* \param data1 Collision data for the first collider.  		* \param data2 Collision data for the second collider.  		*/ -	void collision_handler_request(CollisionInternal & data1, CollisionInternal & data2); +	CollisionInfo get_collision_info(const CollisionInternal & data1, +									 const CollisionInternal & data2) const;  	/** -		* \brief Resolves collision between two colliders and calculates the movement required. +		* \brief Corrects the collision resolution vector and determines its direction.  		* -		* Determines the displacement and direction needed to separate colliders based on their types. +		* This function adjusts the provided resolution vector based on the  +		* rigidbody's linear velocity to ensure consistent collision correction. If the resolution  +		* vector has only one non-zero component (either x or y), the missing component is computed  +		* based on the rigidbody's velocity. If both components are non-zero, it indicates a corner  +		* collision. The function also identifies the direction of the resolution and returns it.  		* -		* \param data1 Collision data for the first collider. -		* \param data2 Collision data for the second collider. -		* \param type The type of collider pair. -		* \return A pair containing the resolution vector and direction for the first collider. +		* \param resolution resolution vector that needs to be corrected +		* \param rigidbody rigidbody data used to correct resolution +		* \return A Direction indicating the resolution direction  		*/ -	std::pair<vec2, Direction> collision_handler(CollisionInternal & data1, -												 CollisionInternal & data2, -												 CollisionInternalType type); +	Direction resolution_correction(vec2 & resolution, const Rigidbody::Data & rigidbody);  	/** -		* \brief Calculates the resolution vector for two BoxColliders. +		* \brief Determines the appropriate collision handler for a given collision event.  		* -		* Computes the displacement required to separate two overlapping BoxColliders. +		* This function identifies the correct collision resolution process based on the body types  +		* of the colliders involved in the collision. It delegates  +		* collision handling to specific handlers and calls collision event scripts  +		* as needed.  		* -		* \param box_collider1 The first BoxCollider. -		* \param box_collider2 The second BoxCollider. -		* \param position1 The position of the first BoxCollider. -		* \param position2 The position of the second BoxCollider. -		* \return The resolution vector for the collision. +		* \param info Collision information containing data about both colliders.  		*/ -	vec2 get_box_box_resolution(const BoxCollider & box_collider1, -								const BoxCollider & box_collider2, const vec2 & position1, -								const vec2 & position2) const; +	void determine_collision_handler(const CollisionInfo & info);  	/** -		* \brief Calculates the resolution vector for two CircleCollider. +		* \brief Calls both collision script  		* -		* Computes the displacement required to separate two overlapping CircleCollider. +		* Calls both collision script to let user add additonal handling or handle full collision.  		* -		* \param circle_collider1 The first CircleCollider. -		* \param circle_collider2 The second CircleCollider. -		* \param final_position1 The position of the first CircleCollider. -		* \param final_position2 The position of the second CircleCollider. -		* \return The resolution vector for the collision. +		* \param info Collision information containing data about both colliders.  		*/ -	vec2 get_circle_circle_resolution(const CircleCollider & circle_collider1, -									  const CircleCollider & circle_collider2, -									  const vec2 & final_position1, -									  const vec2 & final_position2) const; +	void call_collision_events(const CollisionInfo & info);  	/** -		* \brief Calculates the resolution vector for two CircleCollider. -		* -		* Computes the displacement required to separate two overlapping CircleCollider. +		* \brief Handles collisions involving static objects.  		* -		* \param circle_collider The first CircleCollider. -		* \param box_collider The second CircleCollider. -		* \param circle_position The position of the CircleCollider. -		* \param box_position The position of the BoxCollider. -		* \return The resolution vector for the collision. -		*/ -	vec2 get_circle_box_resolution(const CircleCollider & circle_collider, -								   const BoxCollider & box_collider, -								   const vec2 & circle_position, -								   const vec2 & box_position) const; - -	/** -		* \brief Determines the appropriate collision handler for a collision. +		* This function resolves collisions between static and dynamic objects by adjusting  +		* the position of the static object and modifying the velocity of the dynamic object  +		* if elasticity is enabled. The position of the static object is corrected  +		* based on the collision resolution, and the dynamic object's velocity is adjusted  + 		* accordingly to reflect the collision response.  		* -		* Decides the correct resolution process based on the dynamic or static nature of the colliders involved. +		* The handling includes stopping movement, applying bouncing based on the elasticity  + 		* coefficient, and adjusting the position of the dynamic object if needed.  		*  		* \param info Collision information containing data about both colliders.  		*/ -	void determine_collision_handler(CollisionInfo & info); +	void static_collision_handler(const CollisionInfo & info);  	/** -		* \brief Handles collisions involving static objects. +		* \brief Handles collisions involving dynamic objects.  		* -		* Resolves collisions by adjusting positions and modifying velocities if bounce is enabled. +		* Resolves collisions between two dynamic objects by adjusting their positions and modifying  +		* their velocities based on the collision resolution. If elasticity is enabled,  +		* the velocity of both objects is reversed and scaled by the respective elasticity coefficient.  +		* The positions of the objects are adjusted based on the collision resolution.  		*  		* \param info Collision information containing data about both colliders.  		*/ -	void static_collision_handler(CollisionInfo & info); +	void dynamic_collision_handler(const CollisionInfo & info);  private:  	/**  		* \brief Checks for collisions between colliders.  		* -		* Identifies collisions and generates pairs of colliding objects for further processing. +		* This function checks all active colliders and identifies pairs of colliding objects. + 		* For each identified collision, the appropriate collision data is returned as pairs for further processing.  		*  		* \param colliders A collection of all active colliders.  		* \return A list of collision pairs with their associated data. @@ -216,86 +209,77 @@ private:  	gather_collisions(std::vector<CollisionInternal> & colliders);  	/** -	 * \brief Checks if two collision layers have at least one common layer. +	 * \brief Checks if the settings allow collision  	 * -	 * This function checks if there is any overlapping layer between the two inputs. -	 * It compares each layer from the first input to see -	 * if it exists in the second input. If at least one common layer is found, -	 * the function returns true, indicating that the two colliders share a common -	 * collision layer. +	 * This function checks if there is any collison layer where each object is located in. +	 * After checking the layers it checks the names and at last the tags. +	 * if in all three sets nothing is found collision can not happen.  	 * -	 * \param layers1 all collision layers for the first collider. -	 * \param layers2 all collision layers for the second collider. -	 * \return Returns true if there is at least one common layer, false otherwise. +	 * \param this_rigidbody Rigidbody of first object +	 * \param other_rigidbody Rigidbody of second collider +	 * \param this_metadata Rigidbody of first object +	 * \param other_metadata Rigidbody of second object +	 * \return Returns true if there is at least one comparison found.  	 */ - -	bool have_common_layer(const std::set<int> & layers1, const std::set<int> & layers2); +	bool should_collide(const CollisionInternal & self, +						const CollisionInternal & other) const; //done  	/**  		* \brief Checks for collision between two colliders.  		* -		* Calls the appropriate collision detection function based on the collider types. +		* This function determines whether two colliders are colliding based on their types. +		* It calls the appropriate collision detection function based on the collider pair type and stores the collision resolution data. +		* If a collision is detected, it returns true, otherwise false.  		*  		* \param first_info Collision data for the first collider.  		* \param second_info Collision data for the second collider.  		* \param type The type of collider pair.  		* \return True if a collision is detected, otherwise false.  		*/ -	bool get_collision(const CollisionInternal & first_info, -					   const CollisionInternal & second_info, -					   CollisionInternalType type) const; +	bool detect_collision(CollisionInternal & first_info, CollisionInternal & second_info, +						  const CollisionInternalType & type);  	/**  		* \brief Detects collisions between two BoxColliders.  		* -		* \param box1 The first BoxCollider. -		* \param box2 The second BoxCollider. -		* \param transform1 Transform of the first object. -		* \param transform2 Transform of the second object. -		* \param rigidbody1 Rigidbody of the first object. -		* \param rigidbody2 Rigidbody of the second object. -		* \return True if a collision is detected, otherwise false. +		* This function checks whether two `BoxCollider` are colliding based on their positions and scaled dimensions. +		* If a collision is detected, it calculates the overlap along the X and Y axes and returns the resolution vector. +		* If no collision is detected, it returns a vector with NaN values. + +		* \param box1 Information about the first BoxCollider. +		* \param box2 Information about the second BoxCollider. +		* \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}.  		*/ -	bool get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, -							   const Transform & transform1, const Transform & transform2, -							   const Rigidbody & rigidbody1, -							   const Rigidbody & rigidbody2) const; +	vec2 get_box_box_detection(const BoxColliderInternal & box1, +							   const BoxColliderInternal & box2) const;  	/**  	 * \brief Check collision for box on circle collider  	 * -	 * \param box1 The BoxCollider -	 * \param circle2 The CircleCollider -	 * \param transform1 Transform of the first object. -	 * \param transform2 Transform of the second object. -	 * \param rigidbody1 Rigidbody of the first object. -	 * \param rigidbody2 Rigidbody of the second object. -	 * \return True if a collision is detected, otherwise false. +	 * This function detects if a collision occurs between a rectangular box and a circular collider. + 	 * If a collision is detected, the function calculates the resolution vector to resolve the collision. +   * If no collision is detected, it returns a vector with NaN values + 	 * +	 * \param box1 Information about the BoxCollider. +	 * \param circle2 Information about the circleCollider. +	 * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}.  	 */ -	bool get_box_circle_collision(const BoxCollider & box1, const CircleCollider & circle2, -								  const Transform & transform1, const Transform & transform2, -								  const Rigidbody & rigidbody1, -								  const Rigidbody & rigidbody2) const; +	vec2 get_box_circle_detection(const BoxColliderInternal & box1, +								  const CircleColliderInternal & circle2) const;  	/**  	 * \brief Check collision for circle on circle collider  	 * -	 * \param circle1 First CircleCollider -	 * \param circle2 Second CircleCollider -	 * \param transform1 Transform of the first object. -	 * \param transform2 Transform of the second object. -	 * \param rigidbody1 Rigidbody of the first object. -	 * \param rigidbody2 Rigidbody of the second object. -	 * \return True if a collision is detected, otherwise false. +	 * This function detects if a collision occurs between two circular colliders.  +	 * If a collision is detected, it calculates the resolution vector to resolve the collision.  +   * If no collision is detected, it returns a vector with NaN values.  	 * -	 * \return status of collision +	 * \param circle1 Information about the first circleCollider. +	 * \param circle2 Information about the second circleCollider. +	 * \return If colliding, returns the resolution vector; otherwise, returns {NaN, NaN}.  	 */ -	bool get_circle_circle_collision(const CircleCollider & circle1, -									 const CircleCollider & circle2, -									 const Transform & transform1, -									 const Transform & transform2, -									 const Rigidbody & rigidbody1, -									 const Rigidbody & rigidbody2) const; +	vec2 get_circle_circle_detection(const CircleColliderInternal & circle1, +									 const CircleColliderInternal & circle2) const;  };  /** diff --git a/src/crepe/system/EventSystem.cpp b/src/crepe/system/EventSystem.cpp new file mode 100644 index 0000000..7e168ab --- /dev/null +++ b/src/crepe/system/EventSystem.cpp @@ -0,0 +1,9 @@ +#include "EventSystem.h" +#include "../manager/EventManager.h" + +using namespace crepe; + +void EventSystem::fixed_update() { +	EventManager & ev = this->mediator.event_manager; +	ev.dispatch_events(); +} diff --git a/src/crepe/system/EventSystem.h b/src/crepe/system/EventSystem.h new file mode 100644 index 0000000..0ae48d2 --- /dev/null +++ b/src/crepe/system/EventSystem.h @@ -0,0 +1,21 @@ +#pragma once + +#include "System.h" + +namespace crepe { + +/** + * \brief EventManager dispatch helper system + */ +class EventSystem : public System { +public: +	using System::System; + +	/** +	 * \brief Dispatch queued events +	 * \see EventManager::dispatch_events +	 */ +	void fixed_update() override; +}; + +} // namespace crepe diff --git a/src/crepe/system/InputSystem.cpp b/src/crepe/system/InputSystem.cpp index cdcd8c7..858a645 100644 --- a/src/crepe/system/InputSystem.cpp +++ b/src/crepe/system/InputSystem.cpp @@ -8,7 +8,7 @@  using namespace crepe; -void InputSystem::update() { +void InputSystem::fixed_update() {  	ComponentManager & mgr = this->mediator.component_manager;  	SDLContext & context = this->mediator.sdl_context;  	std::vector<EventData> event_list = context.get_events(); diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h index fbcf3f6..2cb80e5 100644 --- a/src/crepe/system/InputSystem.h +++ b/src/crepe/system/InputSystem.h @@ -61,7 +61,7 @@ public:  	 * \brief Updates the system, processing all input events.  	 * This method processes all events and triggers corresponding actions.  	 */ -	void update() override; +	void fixed_update() override;  private:  	//! Stores the last position of the mouse when the button was pressed. diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 35a1d41..5e575e4 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -7,12 +7,13 @@  #include "../api/Transform.h"  #include "../manager/ComponentManager.h"  #include "../manager/LoopTimerManager.h" +#include "util/AbsolutePosition.h"  #include "ParticleSystem.h"  using namespace crepe; -void ParticleSystem::update() { +void ParticleSystem::fixed_update() {  	// Get all emitters  	const Mediator & mediator = this->mediator;  	LoopTimerManager & loop_timer = mediator.loop_timer; @@ -48,9 +49,10 @@ void ParticleSystem::update() {  void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) {  	constexpr float DEG_TO_RAD = M_PI / 180.0; -	vec2 initial_position = emitter.data.offset + transform.position; +	vec2 initial_position = AbsolutePosition::get_position(transform, emitter.data.offset);  	float random_angle -		= this->generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); +		= this->generate_random_angle(emitter.data.min_angle + transform.rotation, +									  emitter.data.max_angle + transform.rotation);  	float random_speed  		= this->generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index 154521d..4296ff3 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -20,7 +20,7 @@ public:  	 * \brief Updates all particle emitters by emitting particles, updating particle states, and  	 * checking bounds.  	 */ -	void update() override; +	void fixed_update() override;  private:  	/** diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index 3b3b8ab..62f8132 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -12,8 +12,7 @@  using namespace crepe; -void PhysicsSystem::update() { - +void PhysicsSystem::fixed_update() {  	const Mediator & mediator = this->mediator;  	ComponentManager & mgr = mediator.component_manager;  	LoopTimerManager & loop_timer = mediator.loop_timer; diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h index 26152a5..5ed624f 100644 --- a/src/crepe/system/PhysicsSystem.h +++ b/src/crepe/system/PhysicsSystem.h @@ -18,7 +18,7 @@ public:  	 *  	 * It calculates new velocties and changes the postion in the transform.  	 */ -	void update() override; +	void fixed_update() override;  };  } // namespace crepe diff --git a/src/crepe/system/RenderSystem.cpp b/src/crepe/system/RenderSystem.cpp index 62d42ec..698301e 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -16,6 +16,9 @@  #include "../facade/Texture.h"  #include "../manager/ComponentManager.h"  #include "../manager/ResourceManager.h" +#include "api/Text.h" +#include "facade/Font.h" +#include "util/AbsolutePosition.h"  #include "RenderSystem.h"  #include "types.h" @@ -67,14 +70,37 @@ RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) const {  	return sorted_objs;  } -void RenderSystem::update() { +void RenderSystem::frame_update() {  	this->clear_screen();  	this->render(); +	this->render_text();  	this->present_screen();  } -bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { +void RenderSystem::render_text() { +	SDLContext & ctx = this->mediator.sdl_context; +	ComponentManager & mgr = this->mediator.component_manager; +	ResourceManager & resource_manager = this->mediator.resource_manager; + +	RefVector<Text> texts = mgr.get_components_by_type<Text>(); + +	for (Text & text : texts) { +		if (!text.active) continue; +		if (!text.font.has_value()) +			text.font.emplace(ctx.get_font_from_name(text.font_family)); + +		const Font & font = resource_manager.get<Font>(text.font.value()); +		const auto & transform +			= mgr.get_components_by_id<Transform>(text.game_object_id).front().get(); +		ctx.draw_text(SDLContext::RenderText{ +			.text = text, +			.font = font, +			.transform = transform, +		}); +	} +} +bool RenderSystem::render_particle(const Sprite & sprite, const Transform & transform) {  	ComponentManager & mgr = this->mediator.component_manager;  	SDLContext & ctx = this->mediator.sdl_context;  	ResourceManager & resource_manager = this->mediator.resource_manager; @@ -92,29 +118,30 @@ bool RenderSystem::render_particle(const Sprite & sprite, const double & scale)  		for (const Particle & p : em.particles) {  			if (!p.active) continue; +			if (p.time_in_life < em.data.begin_lifespan) continue;  			ctx.draw(SDLContext::RenderContext{  				.sprite = sprite,  				.texture = res,  				.pos = p.position, -				.angle = p.angle, -				.scale = scale, +				.angle = p.angle + transform.rotation, +				.scale = transform.scale,  			});  		}  	}  	return rendering_particles;  } -void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) { +void RenderSystem::render_normal(const Sprite & sprite, const Transform & transform) {  	SDLContext & ctx = this->mediator.sdl_context;  	ResourceManager & resource_manager = this->mediator.resource_manager;  	const Texture & res = resource_manager.get<Texture>(sprite.source); - +	vec2 pos = AbsolutePosition::get_position(transform, sprite.data.position_offset);  	ctx.draw(SDLContext::RenderContext{  		.sprite = sprite,  		.texture = res, -		.pos = tm.position, -		.angle = tm.rotation, -		.scale = tm.scale, +		.pos = pos, +		.angle = transform.rotation, +		.scale = transform.scale,  	});  } @@ -126,35 +153,16 @@ void RenderSystem::render() {  	ResourceManager & resource_manager = this->mediator.resource_manager;  	RefVector<Sprite> sorted_sprites = this->sort(sprites);  	RefVector<Text> text_components = mgr.get_components_by_type<Text>(); -	for (Text & text : text_components) { -		const Transform & transform -			= mgr.get_components_by_id<Transform>(text.game_object_id).front().get(); -		this->render_text(text, transform); -	} +  	for (const Sprite & sprite : sorted_sprites) {  		if (!sprite.active) continue;  		const Transform & transform  			= mgr.get_components_by_id<Transform>(sprite.game_object_id).front().get(); -		bool rendered_particles = this->render_particle(sprite, transform.scale); +		bool rendered_particles = this->render_particle(sprite, transform);  		if (rendered_particles) continue;  		this->render_normal(sprite, transform);  	}  } -void RenderSystem::render_text(Text & text, const Transform & tm) { -	SDLContext & ctx = this->mediator.sdl_context; - -	if (!text.font.has_value()) { -		text.font.emplace(ctx.get_font_from_name(text.font_family)); -	} - -	ResourceManager & resource_manager = this->mediator.resource_manager; - -	if (!text.font.has_value()) { -		return; -	} -	const Asset & font_asset = text.font.value(); -	const Font & res = resource_manager.get<Font>(font_asset); -} diff --git a/src/crepe/system/RenderSystem.h b/src/crepe/system/RenderSystem.h index 56a0553..627a743 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -24,7 +24,7 @@ public:  	 * \brief Updates the RenderSystem for the current frame.  	 * This method is called to perform all rendering operations for the current game frame.  	 */ -	void update() override; +	void frame_update() override;  private:  	//! Clears the screen in preparation for rendering. @@ -36,34 +36,28 @@ private:  	//! Updates the active camera used for rendering.  	void update_camera(); -	//! Renders the whole screen +	//! Renders all the sprites and particles  	void render(); +	//! Renders all Text components +	void render_text(); + +private:  	/**  	 * \brief Renders all the particles on the screen from a given sprite.  	 *  	 * \param sprite renders the particles with given texture -	 * \param tm the Transform component for scale. This is not a const reference because each -	 *  particle has a position and rotation that needs to overwrite the transform position and -	 *  rotation without overwriting the current transform. and because the transform -	 *  constructor is now protected i cannot make tmp inside +	 * \param transform the component that holds the position, rotation, and scale.  	 * \return true if particles have been rendered  	 */ -	bool render_particle(const Sprite & sprite, const double & scale); -	/** -	 * \brief Renders all Text components -	 * -	 * \param text The text component to be rendered. -	 * \param tm the Transform component that holds the position,rotation and scale -	 */ -	void render_text(Text & text, const Transform & tm); +	bool render_particle(const Sprite & sprite, const Transform & transform);  	/**  	 * \brief renders a sprite with a Transform component on the screen  	 *  	 * \param sprite  the sprite component that holds all the data -	 * \param tm the Transform component that holds the position,rotation and scale +	 * \param transform the Transform component that holds the position,rotation and scale  	 */ -	void render_normal(const Sprite & sprite, const Transform & tm); +	void render_normal(const Sprite & sprite, const Transform & transform);  	/**  	 * \brief sort a vector sprite objects with @@ -72,12 +66,6 @@ private:  	 * \return returns a sorted reference vector  	 */  	RefVector<Sprite> sort(RefVector<Sprite> & objs) const; - -	/** -	 * \todo Add text rendering using SDL_ttf for text components. -	 * \todo Implement a text component and a button component. -	 * \todo Consider adding text input functionality. -	 */  };  } // namespace crepe diff --git a/src/crepe/system/ReplaySystem.cpp b/src/crepe/system/ReplaySystem.cpp new file mode 100644 index 0000000..efc3be4 --- /dev/null +++ b/src/crepe/system/ReplaySystem.cpp @@ -0,0 +1,54 @@ +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "EventSystem.h" +#include "RenderSystem.h" +#include "ReplaySystem.h" + +using namespace crepe; +using namespace std; + +void ReplaySystem::fixed_update() { +	ReplayManager & replay = this->mediator.replay_manager; +	ReplayManager::State state = replay.get_state(); +	ReplayManager::State last_state = this->last_state; +	this->last_state = state; + +	switch (state) { +		case ReplayManager::IDLE: +			break; +		case ReplayManager::RECORDING: { +			replay.frame_record(); +			break; +		} +		case ReplayManager::PLAYING: { +			if (last_state != ReplayManager::PLAYING) this->playback_begin(); +			bool last = replay.frame_step(); +			if (last) this->playback_end(); +			break; +		} +	} +} + +void ReplaySystem::playback_begin() { +	SystemManager & systems = this->mediator.system_manager; +	ComponentManager & components = this->mediator.component_manager; + +	this->playback = { +		.components = components.save(), +		.systems = systems.save(), +	}; + +	systems.disable_all(); +	systems.get_system<RenderSystem>().active = true; +	systems.get_system<ReplaySystem>().active = true; +	systems.get_system<EventSystem>().active = true; +} + +void ReplaySystem::playback_end() { +	SystemManager & systems = this->mediator.system_manager; +	ComponentManager & components = this->mediator.component_manager; + +	components.restore(this->playback.components); +	systems.restore(this->playback.systems); +} diff --git a/src/crepe/system/ReplaySystem.h b/src/crepe/system/ReplaySystem.h new file mode 100644 index 0000000..bbc8d76 --- /dev/null +++ b/src/crepe/system/ReplaySystem.h @@ -0,0 +1,44 @@ +#pragma once + +#include "../manager/ReplayManager.h" +#include "../manager/SystemManager.h" + +#include "System.h" + +namespace crepe { + +/** + * \brief ReplayManager helper system + * + * This system records and replays recordings using ReplayManager. + */ +class ReplaySystem : public System { +public: +	using System::System; + +	void fixed_update() override; + +private: +	//! Last ReplayManager state +	ReplayManager::State last_state = ReplayManager::IDLE; + +	/** +	 * \brief Playback snapshot +	 * +	 * When starting playback, the component state is saved and most systems are disabled. This +	 * struct stores the engine state before ReplayManager::play is called. +	 */ +	struct Snapshot { +		ComponentManager::Snapshot components; +		SystemManager::Snapshot systems; +	}; +	//! Before playback snapshot +	Snapshot playback; + +	//! Snapshot state and disable systems during playback +	void playback_begin(); +	//! Restore state from before \c playback_begin() +	void playback_end(); +}; + +} // namespace crepe diff --git a/src/crepe/system/ScriptSystem.cpp b/src/crepe/system/ScriptSystem.cpp index 0605c7a..93b4853 100644 --- a/src/crepe/system/ScriptSystem.cpp +++ b/src/crepe/system/ScriptSystem.cpp @@ -7,11 +7,21 @@  using namespace std;  using namespace crepe; -void ScriptSystem::update() { -	dbg_trace(); +void ScriptSystem::fixed_update() { +	LoopTimerManager & timer = this->mediator.loop_timer; +	duration_t delta_time = timer.get_scaled_fixed_delta_time(); +	this->update(&Script::fixed_update, delta_time); +} -	ComponentManager & mgr = this->mediator.component_manager; +void ScriptSystem::frame_update() {  	LoopTimerManager & timer = this->mediator.loop_timer; +	duration_t delta_time = timer.get_delta_time(); +	this->update(&Script::frame_update, delta_time); +} + +void ScriptSystem::update(void (Script::*update_function)(duration_t), +						  const duration_t & delta_time) { +	ComponentManager & mgr = this->mediator.component_manager;  	RefVector<BehaviorScript> behavior_scripts = mgr.get_components_by_type<BehaviorScript>();  	for (BehaviorScript & behavior_script : behavior_scripts) { @@ -25,7 +35,6 @@ void ScriptSystem::update() {  			script->initialized = true;  		} -		duration_t delta_time = timer.get_scaled_fixed_delta_time(); -		script->update(delta_time); +		(*script.*update_function)(delta_time);  	}  } diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h index 3db1b1e..257b615 100644 --- a/src/crepe/system/ScriptSystem.h +++ b/src/crepe/system/ScriptSystem.h @@ -1,5 +1,7 @@  #pragma once +#include "../manager/LoopTimerManager.h" +  #include "System.h"  namespace crepe { @@ -9,20 +11,27 @@ class Script;  /**   * \brief Script system   * - * The script system is responsible for all \c BehaviorScript components, and - * calls the methods on classes derived from \c Script. + * The script system is responsible for all \c BehaviorScript components, and calls the methods + * on classes derived from \c Script.   */  class ScriptSystem : public System {  public:  	using System::System; + +public: +	//! Call Script::fixed_update() on all active \c BehaviorScript instances +	void fixed_update() override; +	//! Call Script::frame_update() on all active \c BehaviorScript instances +	void frame_update() override; + +private:  	/** -	 * \brief Call Script::update() on all active \c BehaviorScript instances +	 * \brief Call Script `*_update` member function on all active \c BehaviorScript instances  	 * -	 * This routine updates all scripts sequentially using the Script::update() -	 * method. It also calls Script::init() if this has not been done before on -	 * the \c BehaviorScript instance. +	 * \note This routine also calls Script::init() if this has not been done before on the \c +	 * BehaviorScript instance.  	 */ -	void update() override; +	void update(void (Script::*update_function)(duration_t), const duration_t & delta_time);  };  } // namespace crepe diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h index 063dfbf..e2ce7eb 100644 --- a/src/crepe/system/System.h +++ b/src/crepe/system/System.h @@ -14,10 +14,12 @@ class ComponentManager;   */  class System {  public: -	/** -	 * \brief Process all components this system is responsible for. -	 */ -	virtual void update() = 0; +	//! Code that runs in the fixed loop +	virtual void fixed_update() {}; +	//! Code that runs in the frame loop +	virtual void frame_update() {}; +	//! Indicates that the update functions of this system should be run +	bool active = true;  public:  	System(const Mediator & m); diff --git a/src/crepe/util/AbsolutePosition.cpp b/src/crepe/util/AbsolutePosition.cpp new file mode 100644 index 0000000..29ade23 --- /dev/null +++ b/src/crepe/util/AbsolutePosition.cpp @@ -0,0 +1,20 @@ +#include "AbsolutePosition.h" + +using namespace crepe; + +vec2 AbsolutePosition::get_position(const Transform & transform, const vec2 & offset) { +	// Get the rotation in radians +	float radians1 = transform.rotation * (M_PI / 180.0); + +	// Calculate total offset with scale +	vec2 total_offset = 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/util/AbsolutePosition.h b/src/crepe/util/AbsolutePosition.h new file mode 100644 index 0000000..857c1ac --- /dev/null +++ b/src/crepe/util/AbsolutePosition.h @@ -0,0 +1,29 @@ +#pragma once + +#include "api/Transform.h" + +#include "types.h" + +namespace crepe { + +/** + * \brief A class for calculating the absolute position of an object. + * + * This class provides a utility function to get the position of an object in the world space, + * taking into account the transform and any additional offset. + */ +class AbsolutePosition { +public: +	/** +	 * \brief Get the absolute position of an object. +	 * +	 * This function calculates the absolute position by combining the transform position with an optional offset. +	 * +	 * \param transform The transform of the object, which contains its position, rotation, and scale. +	 * \param offset The offset to apply to the object's position (in local space). +	 * \return The absolute position of the object as a 2D vector. +	 */ +	static vec2 get_position(const Transform & transform, const vec2 & offset); +}; + +} // namespace crepe diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt index 94ed906..33160a7 100644 --- a/src/crepe/util/CMakeLists.txt +++ b/src/crepe/util/CMakeLists.txt @@ -1,6 +1,7 @@  target_sources(crepe PUBLIC  	LogColor.cpp  	Log.cpp +	AbsolutePosition.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -11,5 +12,6 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Proxy.hpp  	OptionalRef.h  	OptionalRef.hpp +	AbsolutePosition.h  ) diff --git a/src/crepe/util/Log.cpp b/src/crepe/util/Log.cpp index 84d80a8..ce25a1d 100644 --- a/src/crepe/util/Log.cpp +++ b/src/crepe/util/Log.cpp @@ -4,6 +4,7 @@  #include "../api/Config.h"  #include "Log.h" +#include "LogColor.h"  using namespace crepe;  using namespace std; diff --git a/src/crepe/util/Log.h b/src/crepe/util/Log.h index fc0bb3a..b43fe30 100644 --- a/src/crepe/util/Log.h +++ b/src/crepe/util/Log.h @@ -2,27 +2,6 @@  #include <format> -// allow user to disable debug macros -#ifndef CREPE_DISABLE_MACROS - -#include "LogColor.h" - -// utility macros -#define _crepe_logf_here(level, fmt, ...) \ -	crepe::Log::logf(level, "{}" fmt, \ -					 crepe::LogColor().fg_white(false).str(std::format( \ -						 "{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__)), \ -					 __VA_ARGS__) - -// very illegal global function-style macros -// NOLINTBEGIN -#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__) -#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str) -#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "") -// NOLINTEND - -#endif -  namespace crepe {  /** diff --git a/src/crepe/util/dbg.h b/src/crepe/util/dbg.h new file mode 100644 index 0000000..c7283ee --- /dev/null +++ b/src/crepe/util/dbg.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Log.h" +#include "LogColor.h" + +// utility macros +#define _crepe_logf_here(level, fmt, ...) \ +	crepe::Log::logf(level, "{}" fmt, \ +					 crepe::LogColor().fg_white(false).str(std::format( \ +						 "{} ({}:{})", __PRETTY_FUNCTION__, __FILE_NAME__, __LINE__)), \ +					 __VA_ARGS__) + +// very illegal global function-style macros +// NOLINTBEGIN +#define dbg_logf(fmt, ...) _crepe_logf_here(crepe::Log::Level::DEBUG, ": " fmt, __VA_ARGS__) +#define dbg_log(str) _crepe_logf_here(crepe::Log::Level::DEBUG, ": {}", str) +#define dbg_trace() _crepe_logf_here(crepe::Log::Level::TRACE, "", "") +// NOLINTEND |