diff options
Diffstat (limited to 'src/crepe/system')
25 files changed, 2038 insertions, 240 deletions
| diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp new file mode 100644 index 0000000..0f35010 --- /dev/null +++ b/src/crepe/system/AISystem.cpp @@ -0,0 +1,186 @@ +#include <algorithm> +#include <cmath> + +#include "manager/ComponentManager.h" +#include "manager/LoopTimerManager.h" +#include "manager/Mediator.h" + +#include "AISystem.h" + +using namespace crepe; +using namespace std::chrono; + +void AISystem::fixed_update() { +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	LoopTimerManager & loop_timer = mediator.loop_timer; +	RefVector<AI> ai_components = mgr.get_components_by_type<AI>(); + +	float dt = loop_timer.get_scaled_fixed_delta_time().count(); + +	// Loop through all AI components +	for (AI & ai : ai_components) { +		if (!ai.active) { +			continue; +		} + +		RefVector<Rigidbody> rigidbodies +			= mgr.get_components_by_id<Rigidbody>(ai.game_object_id); +		if (rigidbodies.empty()) { +			throw std::runtime_error( +				"AI component must be attached to a GameObject with a Rigidbody component"); +		} +		Rigidbody & rigidbody = rigidbodies.front().get(); +		if (!rigidbody.active) { +			continue; +		} +		if (rigidbody.data.mass <= 0) { +			throw std::runtime_error("Mass must be greater than 0"); +		} + +		// Calculate the force to apply to the entity +		vec2 force = this->calculate(ai, rigidbody); +		// Calculate the acceleration (using the above calculated force) +		vec2 acceleration = force / rigidbody.data.mass; +		// Finally, update Rigidbody's velocity +		rigidbody.data.linear_velocity += acceleration * dt; +	} +} + +vec2 AISystem::calculate(AI & ai, const Rigidbody & rigidbody) { +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	RefVector<Transform> transforms = mgr.get_components_by_id<Transform>(ai.game_object_id); +	Transform & transform = transforms.front().get(); + +	vec2 force; + +	// Run all the behaviors that are on, and stop if the force gets too high +	if (ai.on(AI::BehaviorTypeMask::FLEE)) { +		vec2 force_to_add = this->flee(ai, rigidbody, transform); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} +	if (ai.on(AI::BehaviorTypeMask::ARRIVE)) { +		vec2 force_to_add = this->arrive(ai, rigidbody, transform); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} +	if (ai.on(AI::BehaviorTypeMask::SEEK)) { +		vec2 force_to_add = this->seek(ai, rigidbody, transform); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} +	if (ai.on(AI::BehaviorTypeMask::PATH_FOLLOW)) { +		vec2 force_to_add = this->path_follow(ai, rigidbody, transform); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} + +	return force; +} + +bool AISystem::accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add) { +	float magnitude = running_total.length(); +	float magnitude_remaining = ai.max_force - magnitude; + +	if (magnitude_remaining <= 0.0f) { +		// If the force is already at/above the max force, return false +		return false; +	} + +	float magnitude_to_add = force_to_add.length(); +	if (magnitude_to_add < magnitude_remaining) { +		// If the force to add is less than the remaining force, add it +		running_total += force_to_add; +	} else { +		// If the force to add is greater than the remaining force, add a fraction of it +		force_to_add.normalize(); +		running_total += force_to_add * magnitude_remaining; +	} + +	return true; +} + +vec2 AISystem::seek(const AI & ai, const Rigidbody & rigidbody, +					const Transform & transform) const { +	// Calculate the desired velocity +	vec2 desired_velocity = ai.seek_target - transform.position; +	desired_velocity.normalize(); +	desired_velocity *= rigidbody.data.max_linear_velocity; + +	return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::flee(const AI & ai, const Rigidbody & rigidbody, +					const Transform & transform) const { +	// Calculate the desired velocity if the entity is within the panic distance +	vec2 desired_velocity = transform.position - ai.flee_target; +	if (desired_velocity.length_squared() > ai.square_flee_panic_distance) { +		return vec2{0, 0}; +	} +	desired_velocity.normalize(); +	desired_velocity *= rigidbody.data.max_linear_velocity; + +	return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::arrive(const AI & ai, const Rigidbody & rigidbody, +					  const Transform & transform) const { +	// Calculate the desired velocity (taking into account the deceleration rate) +	vec2 to_target = ai.arrive_target - transform.position; +	float distance = to_target.length(); +	if (distance > 0.0f) { +		if (ai.arrive_deceleration <= 0.0f) { +			throw std::runtime_error("Deceleration rate must be greater than 0"); +		} + +		float speed = distance / ai.arrive_deceleration; +		speed = std::min(speed, rigidbody.data.max_linear_velocity); +		vec2 desired_velocity = to_target * (speed / distance); + +		return desired_velocity - rigidbody.data.linear_velocity; +	} + +	return vec2{0, 0}; +} + +vec2 AISystem::path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform) { +	if (ai.path.empty()) { +		return vec2{0, 0}; +	} + +	// Get the target node +	vec2 target = ai.path.at(ai.path_index); +	// Calculate the force to apply to the entity +	vec2 to_target = target - transform.position; +	if (to_target.length_squared() > ai.path_node_distance * ai.path_node_distance) { +		// If the entity is not close enough to the target node, seek it +		ai.seek_target = target; +		ai.arrive_target = target; +	} else { +		// If the entity is close enough to the target node, move to the next node +		ai.path_index++; +		if (ai.path_index >= ai.path.size()) { +			if (ai.path_loop) { +				// If the path is looping, reset the path index +				ai.path_index = 0; +			} else { +				// If the path is not looping, arrive at the last node +				ai.path_index = ai.path.size() - 1; +				return this->arrive(ai, rigidbody, transform); +			} +		} +	} + +	// Seek the target node +	return this->seek(ai, rigidbody, transform); +} diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h new file mode 100644 index 0000000..04807cf --- /dev/null +++ b/src/crepe/system/AISystem.h @@ -0,0 +1,81 @@ +#pragma once + +#include "api/AI.h" +#include "api/Rigidbody.h" + +#include "System.h" +#include "api/Transform.h" +#include "types.h" + +namespace crepe { + +/** + * \brief The AISystem is used to control the movement of entities using AI. + * + * The AISystem is used to control the movement of entities using AI. The AISystem can be used to + * implement different behaviors such as seeking, fleeing, arriving, and path following. + */ +class AISystem : public System { +public: +	using System::System; + +	//! Update the AI system +	void fixed_update() override; + +private: +	/** +	 * \brief Calculate the total force to apply to the entity +	 * +	 * \param ai The AI component +	 * \param rigidbody The Rigidbody component +	 */ +	vec2 calculate(AI & ai, const Rigidbody & rigidbody); +	/** +	 * \brief Accumulate the force to apply to the entity +	 * +	 * \param ai The AI component +	 * \param running_total The running total of the force +	 * \param force_to_add The force to add +	 * \return true if the force was added, false otherwise +	 */ +	bool accumulate_force(const AI & ai, vec2 & running_total, vec2 & force_to_add); + +	/** +	 * \brief Calculate the seek force +	 * +	 * \param ai The AI component +	 * \param rigidbody The Rigidbody component +	 * \param transform The Transform component +	 * \return The seek force +	 */ +	vec2 seek(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; +	/** +	 * \brief Calculate the flee force +	 * +	 * \param ai The AI component +	 * \param rigidbody The Rigidbody component +	 * \param transform The Transform component +	 * \return The flee force +	 */ +	vec2 flee(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; +	/** +	 * \brief Calculate the arrive force +	 * +	 * \param ai The AI component +	 * \param rigidbody The Rigidbody component +	 * \param transform The Transform component +	 * \return The arrive force +	 */ +	vec2 arrive(const AI & ai, const Rigidbody & rigidbody, const Transform & transform) const; +	/** +	 * \brief Calculate the path follow force +	 * +	 * \param ai The AI component +	 * \param rigidbody The Rigidbody component +	 * \param transform The Transform component +	 * \return The path follow force +	 */ +	vec2 path_follow(AI & ai, const Rigidbody & rigidbody, const Transform & transform); +}; + +} // namespace crepe diff --git a/src/crepe/system/AnimatorSystem.cpp b/src/crepe/system/AnimatorSystem.cpp index 676e485..e5ab2fa 100644 --- a/src/crepe/system/AnimatorSystem.cpp +++ b/src/crepe/system/AnimatorSystem.cpp @@ -1,24 +1,40 @@ -#include <cstdint> - -#include "api/Animator.h" -#include "facade/SDLContext.h" +#include "../api/Animator.h" +#include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" +#include <chrono>  #include "AnimatorSystem.h" -#include "ComponentManager.h"  using namespace crepe; +using namespace std::chrono; -void AnimatorSystem::update() { -	ComponentManager & mgr = this->component_manager; - +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>(); -	uint64_t tick = SDLContext::get_instance().get_ticks(); +	float elapsed_time = duration_cast<duration<float>>(timer.get_elapsed_time()).count(); +  	for (Animator & a : animations) { -		if (a.active) { -			a.curr_row = (tick / 100) % a.row; -			a.animator_rect.x = (a.curr_row * a.animator_rect.w) + a.curr_col; -			a.spritesheet.sprite_rect = a.animator_rect; +		if (!a.active) continue; +		if (a.data.fps == 0) continue; + +		Animator::Data & ctx = a.data; +		float frame_duration = 1.0f / ctx.fps; + +		int last_frame = ctx.row; + +		int cycle_end = (ctx.cycle_end == -1) ? a.grid_size.x : ctx.cycle_end; +		int total_frames = cycle_end - ctx.cycle_start; + +		int curr_frame = static_cast<int>(elapsed_time / frame_duration) % total_frames; + +		ctx.row = ctx.cycle_start + curr_frame; +		a.spritesheet.mask.x = ctx.row * a.spritesheet.mask.w; +		a.spritesheet.mask.y = (ctx.col * a.spritesheet.mask.h); + +		if (!ctx.looping && curr_frame == ctx.cycle_start && last_frame == total_frames - 1) { +			a.active = false;  		}  	}  } diff --git a/src/crepe/system/AnimatorSystem.h b/src/crepe/system/AnimatorSystem.h index 56cc7b3..092e131 100644 --- a/src/crepe/system/AnimatorSystem.h +++ b/src/crepe/system/AnimatorSystem.h @@ -2,9 +2,6 @@  #include "System.h" -//TODO: -// control if flip works with animation system -  namespace crepe {  /** @@ -21,12 +18,11 @@ public:  	/**  	 * \brief Updates the Animator components.  	 * -	 * This method is called periodically (likely every frame) to update the state of all +	 * This method is called to update the state of all  	 * Animator components, moving the animations forward and managing their behavior (e.g.,  	 * looping).  	 */ -	void update() override; -	// FIXME: never say "likely" in the documentation lmao +	void frame_update() override;  };  } // namespace crepe diff --git a/src/crepe/system/AudioSystem.cpp b/src/crepe/system/AudioSystem.cpp new file mode 100644 index 0000000..d4e8b9f --- /dev/null +++ b/src/crepe/system/AudioSystem.cpp @@ -0,0 +1,62 @@ +#include "AudioSystem.h" + +#include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h" +#include "../types.h" + +using namespace crepe; +using namespace std; + +void AudioSystem::fixed_update() { +	ComponentManager & component_manager = this->mediator.component_manager; +	ResourceManager & resource_manager = this->mediator.resource_manager; +	RefVector<AudioSource> components +		= component_manager.get_components_by_type<AudioSource>(); + +	for (AudioSource & component : components) { +		Sound & resource = resource_manager.get<Sound>(component.source); + +		this->diff_update(component, resource); + +		this->update_last(component); +	} +} + +void AudioSystem::diff_update(AudioSource & component, Sound & resource) { +	SoundContext & context = this->get_context(); + +	if (component.active != component.last_active) { +		if (!component.active) { +			context.stop(component.voice); +			return; +		} +		if (component.play_on_awake) component.oneshot_play = true; +	} +	if (!component.active) return; + +	if (component.oneshot_play) { +		component.voice = context.play(resource); +		component.oneshot_play = false; +	} +	if (component.oneshot_stop) { +		context.stop(component.voice); +		component.oneshot_stop = false; +	} +	if (component.volume != component.last_volume) { +		context.set_volume(component.voice, component.volume); +	} +	if (component.loop != component.last_loop) { +		context.set_loop(component.voice, component.loop); +	} +} + +void AudioSystem::update_last(AudioSource & component) { +	component.last_active = component.active; +	component.last_loop = component.loop; +	component.last_volume = component.volume; +} + +SoundContext & AudioSystem::get_context() { +	if (this->context == nullptr) this->context = make_unique<SoundContext>(); +	return *this->context.get(); +} diff --git a/src/crepe/system/AudioSystem.h b/src/crepe/system/AudioSystem.h new file mode 100644 index 0000000..56fc98c --- /dev/null +++ b/src/crepe/system/AudioSystem.h @@ -0,0 +1,51 @@ +#pragma once + +#include "../api/AudioSource.h" +#include "../facade/Sound.h" +#include "../facade/SoundContext.h" + +#include "System.h" + +namespace crepe { + +class AudioSystem : public System { +public: +	using System::System; +	void fixed_update() override; + +private: +	/** +	 * \brief Update `last_*` members of \c component +	 * +	 * Copies all component properties stored for comparison between AudioSystem::update() calls +	 * +	 * \param component AudioSource component to update +	 */ +	void update_last(AudioSource & component); + +	/** +	 * \brief Compare update component +	 * +	 * Compares properties of \c component and \c data, and calls SoundContext functions where +	 * applicable. +	 * +	 * \param component AudioSource component to update +	 * \param resource Sound instance for AudioSource's Asset +	 */ +	void diff_update(AudioSource & component, Sound & resource); + +protected: +	/** +	 * \brief Get SoundContext +	 * +	 * SoundContext is retrieved through this function instead of being a direct member of +	 * AudioSystem to aid with testability. +	 */ +	virtual SoundContext & get_context(); + +private: +	//! SoundContext +	std::unique_ptr<SoundContext> context = nullptr; +}; + +} // namespace crepe diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index d658b25..52369d0 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -5,7 +5,12 @@ target_sources(crepe PUBLIC  	PhysicsSystem.cpp  	CollisionSystem.cpp  	RenderSystem.cpp +	AudioSystem.cpp  	AnimatorSystem.cpp +	InputSystem.cpp +	EventSystem.cpp +	ReplaySystem.cpp +	AISystem.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -14,5 +19,10 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	PhysicsSystem.h  	CollisionSystem.h  	RenderSystem.h +	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 c74ca1d..9d88d9f 100644 --- a/src/crepe/system/CollisionSystem.cpp +++ b/src/crepe/system/CollisionSystem.cpp @@ -1,5 +1,575 @@ +#include <algorithm> +#include <cmath> +#include <cstddef> +#include <functional> +#include <optional> +#include <utility> +#include <variant> + +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" + +#include "Collider.h"  #include "CollisionSystem.h" +#include "types.h" +#include "util/OptionalRef.h"  using namespace crepe; -void CollisionSystem::update() {} +void CollisionSystem::fixed_update() { +	std::vector<CollisionInternal> all_colliders; +	game_object_id_t id = 0; +	ComponentManager & mgr = this->mediator.component_manager; +	RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); +	// Collisions can only happen on object with a rigidbody +	for (Rigidbody & rigidbody : rigidbodies) { +		if (!rigidbody.active) continue; +		id = rigidbody.game_object_id; +		Transform & transform = mgr.get_components_by_id<Transform>(id).front().get(); +		// Check if the boxcollider is active and has the same id as the rigidbody. +		RefVector<BoxCollider> boxcolliders = mgr.get_components_by_type<BoxCollider>(); +		for (BoxCollider & boxcollider : boxcolliders) { +			if (boxcollider.game_object_id != id) continue; +			if (!boxcollider.active) continue; +			all_colliders.push_back({.id = id, +									 .collider = collider_variant{boxcollider}, +									 .transform = transform, +									 .rigidbody = rigidbody}); +		} +		// Check if the circlecollider is active and has the same id as the rigidbody. +		RefVector<CircleCollider> circlecolliders +			= mgr.get_components_by_type<CircleCollider>(); +		for (CircleCollider & circlecollider : circlecolliders) { +			if (circlecollider.game_object_id != id) continue; +			if (!circlecollider.active) continue; +			all_colliders.push_back({.id = id, +									 .collider = collider_variant{circlecollider}, +									 .transform = transform, +									 .rigidbody = rigidbody}); +		} +	} + +	// Check between all colliders if there is a collision +	std::vector<std::pair<CollisionInternal, CollisionInternal>> collided +		= this->gather_collisions(all_colliders); + +	// For both objects call the collision handler +	for (auto & collision_pair : collided) { +		this->collision_handler_request(collision_pair.first, collision_pair.second); +		this->collision_handler_request(collision_pair.second, collision_pair.first); +	} +} + +void CollisionSystem::collision_handler_request(CollisionInternal & this_data, +												CollisionInternal & other_data) { + +	CollisionInternalType type +		= this->get_collider_type(this_data.collider, other_data.collider); +	std::pair<vec2, CollisionSystem::Direction> resolution_data +		= this->collision_handler(this_data, other_data, type); +	ComponentManager & mgr = this->mediator.component_manager; +	OptionalRef<Metadata> this_metadata +		= mgr.get_components_by_id<Metadata>(this_data.id).front().get(); +	OptionalRef<Metadata> other_metadata +		= mgr.get_components_by_id<Metadata>(other_data.id).front().get(); +	OptionalRef<Collider> this_collider; +	OptionalRef<Collider> other_collider; +	switch (type) { +		case CollisionInternalType::BOX_BOX: { +			this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); +			other_collider +				= std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); +			break; +		} +		case CollisionInternalType::BOX_CIRCLE: { +			this_collider = std::get<std::reference_wrapper<BoxCollider>>(this_data.collider); +			other_collider +				= std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); +			break; +		} +		case CollisionInternalType::CIRCLE_BOX: { +			this_collider +				= std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); +			other_collider +				= std::get<std::reference_wrapper<BoxCollider>>(other_data.collider); +			break; +		} +		case CollisionInternalType::CIRCLE_CIRCLE: { +			this_collider +				= std::get<std::reference_wrapper<CircleCollider>>(this_data.collider); +			other_collider +				= std::get<std::reference_wrapper<CircleCollider>>(other_data.collider); +			break; +		} +	} + +	// collision info +	crepe::CollisionSystem::CollisionInfo collision_info{ +		.this_collider = this_collider, +		.this_transform = this_data.transform, +		.this_rigidbody = this_data.rigidbody, +		.this_metadata = this_metadata, +		.other_collider = other_collider, +		.other_transform = other_data.transform, +		.other_rigidbody = other_data.rigidbody, +		.other_metadata = other_metadata, +		.resolution = resolution_data.first, +		.resolution_direction = resolution_data.second, +	}; + +	// Determine if static needs to be called +	this->determine_collision_handler(collision_info); +} + +std::pair<vec2, CollisionSystem::Direction> +CollisionSystem::collision_handler(CollisionInternal & data1, CollisionInternal & data2, +								   CollisionInternalType type) { +	vec2 resolution; +	switch (type) { +		case CollisionInternalType::BOX_BOX: { +			const BoxCollider & collider1 +				= std::get<std::reference_wrapper<BoxCollider>>(data1.collider); +			const BoxCollider & collider2 +				= std::get<std::reference_wrapper<BoxCollider>>(data2.collider); +			vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, +															data1.rigidbody); +			vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, +															data2.rigidbody); +			resolution = this->get_box_box_resolution(collider1, collider2, collider_pos1, +													  collider_pos2); +			break; +		} +		case CollisionInternalType::BOX_CIRCLE: { +			const BoxCollider & collider1 +				= std::get<std::reference_wrapper<BoxCollider>>(data1.collider); +			const CircleCollider & collider2 +				= std::get<std::reference_wrapper<CircleCollider>>(data2.collider); +			vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, +															data1.rigidbody); +			vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, +															data2.rigidbody); +			resolution = -this->get_circle_box_resolution(collider2, collider1, collider_pos2, +														  collider_pos1); +			break; +		} +		case CollisionInternalType::CIRCLE_CIRCLE: { +			const CircleCollider & collider1 +				= std::get<std::reference_wrapper<CircleCollider>>(data1.collider); +			const CircleCollider & collider2 +				= std::get<std::reference_wrapper<CircleCollider>>(data2.collider); +			vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, +															data1.rigidbody); +			vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, +															data2.rigidbody); +			resolution = this->get_circle_circle_resolution(collider1, collider2, +															collider_pos1, collider_pos2); +			break; +		} +		case CollisionInternalType::CIRCLE_BOX: { +			const CircleCollider & collider1 +				= std::get<std::reference_wrapper<CircleCollider>>(data1.collider); +			const BoxCollider & collider2 +				= std::get<std::reference_wrapper<BoxCollider>>(data2.collider); +			vec2 collider_pos1 = this->get_current_position(collider1.offset, data1.transform, +															data1.rigidbody); +			vec2 collider_pos2 = this->get_current_position(collider2.offset, data2.transform, +															data2.rigidbody); +			resolution = this->get_circle_box_resolution(collider1, collider2, collider_pos1, +														 collider_pos2); +			break; +		} +	} + +	Direction resolution_direction = Direction::NONE; +	if (resolution.x != 0 && resolution.y != 0) { +		resolution_direction = Direction::BOTH; +	} else if (resolution.x != 0) { +		resolution_direction = Direction::X_DIRECTION; +		//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); +	} + +	return std::make_pair(resolution, resolution_direction); +} + +vec2 CollisionSystem::get_box_box_resolution(const BoxCollider & box_collider1, +											 const BoxCollider & box_collider2, +											 const vec2 & final_position1, +											 const vec2 & final_position2) const { +	vec2 resolution; // Default resolution vector +	vec2 delta = final_position2 - final_position1; + +	// Compute half-dimensions of the boxes +	float half_width1 = box_collider1.dimensions.x / 2.0; +	float half_height1 = box_collider1.dimensions.y / 2.0; +	float half_width2 = box_collider2.dimensions.x / 2.0; +	float half_height2 = box_collider2.dimensions.y / 2.0; + +	// Calculate overlaps along X and Y axes +	float overlap_x = (half_width1 + half_width2) - std::abs(delta.x); +	float overlap_y = (half_height1 + half_height2) - std::abs(delta.y); + +	// Check if there is a collision should always be true +	if (overlap_x > 0 && overlap_y > 0) { +		// Determine the direction of resolution +		if (overlap_x < overlap_y) { +			// Resolve along the X-axis (smallest overlap) +			resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; +		} else if (overlap_y < overlap_x) { +			// Resolve along the Y-axis (smallest overlap) +			resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; +		} else { +			// Equal overlap, resolve both directions with preference +			resolution.x = (delta.x > 0) ? -overlap_x : overlap_x; +			resolution.y = (delta.y > 0) ? -overlap_y : overlap_y; +		} +	} + +	return resolution; +} + +vec2 CollisionSystem::get_circle_circle_resolution(const CircleCollider & circle_collider1, +												   const CircleCollider & circle_collider2, +												   const vec2 & final_position1, +												   const vec2 & final_position2) const { +	vec2 delta = final_position2 - final_position1; + +	// Compute the distance between the two circle centers +	float distance = std::sqrt(delta.x * delta.x + delta.y * delta.y); + +	// Compute the combined radii of the two circles +	float combined_radius = circle_collider1.radius + circle_collider2.radius; + +	// Compute the penetration depth +	float penetration_depth = combined_radius - distance; + +	// Normalize the delta vector to get the collision direction +	vec2 collision_normal = delta / distance; + +	// Compute the resolution vector +	vec2 resolution = -collision_normal * penetration_depth; + +	return resolution; +} + +vec2 CollisionSystem::get_circle_box_resolution(const CircleCollider & circle_collider, +												const BoxCollider & box_collider, +												const vec2 & circle_position, +												const vec2 & box_position) const { +	vec2 delta = circle_position - box_position; + +	// Compute half-dimensions of the box +	float half_width = box_collider.dimensions.x / 2.0f; +	float half_height = box_collider.dimensions.y / 2.0f; + +	// Clamp circle center to the nearest point on the box +	vec2 closest_point; +	closest_point.x = std::clamp(delta.x, -half_width, half_width); +	closest_point.y = std::clamp(delta.y, -half_height, half_height); + +	// Find the vector from the circle center to the closest point +	vec2 closest_delta = delta - closest_point; + +	// Normalize the delta to get the collision direction +	float distance +		= std::sqrt(closest_delta.x * closest_delta.x + closest_delta.y * closest_delta.y); +	vec2 collision_normal = closest_delta / distance; + +	// Compute penetration depth +	float penetration_depth = circle_collider.radius - distance; + +	// Compute the resolution vector +	vec2 resolution = collision_normal * penetration_depth; + +	return resolution; +} + +void CollisionSystem::determine_collision_handler(CollisionInfo & info) { +	// Check rigidbody type for static +	if (info.this_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) return; +	// If second body is static perform the static collision handler in this system +	if (info.other_rigidbody.data.body_type == Rigidbody::BodyType::STATIC) { +		this->static_collision_handler(info); +	}; +	// Call collision event for user +	CollisionEvent data(info); +	EventManager & emgr = this->mediator.event_manager; +	emgr.trigger_event<CollisionEvent>(data, info.this_collider.game_object_id); +} + +void CollisionSystem::static_collision_handler(CollisionInfo & info) { +	// Move object back using calculate move back value +	info.this_transform.position += info.resolution; + +	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; +			} +			//stop movement +			else { +				info.this_rigidbody.data.linear_velocity = {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; +			} +			// Stop movement +			else { +				info.this_rigidbody.data.linear_velocity.y = 0; +				info.this_transform.position.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; +			} +			// Stop movement +			else { +				info.this_rigidbody.data.linear_velocity.x = 0; +				info.this_transform.position.y -= info.resolution.y; +			} +			break; +		case Direction::NONE: +			// Not possible +			break; +	} +} + +std::vector<std::pair<CollisionSystem::CollisionInternal, CollisionSystem::CollisionInternal>> +CollisionSystem::gather_collisions(std::vector<CollisionInternal> & colliders) { + +	// TODO: +	// If no colliders skip +	// Check if colliders has rigidbody if not skip + +	// TODO: +	// If amount is higer than lets say 16 for now use quadtree otwerwise skip +	// Quadtree code +	// Quadtree is placed over the input vector + +	// Return data of collided colliders which are variants +	std::vector<std::pair<CollisionInternal, CollisionInternal>> collisions_ret; +	//using visit to visit the variant to access the active and id. +	for (size_t i = 0; i < colliders.size(); ++i) { +		for (size_t j = i + 1; j < colliders.size(); ++j) { +			if (colliders[i].id == colliders[j].id) continue; +			if (!have_common_layer(colliders[i].rigidbody.data.collision_layers, +								   colliders[j].rigidbody.data.collision_layers)) +				continue; +			CollisionInternalType type +				= get_collider_type(colliders[i].collider, colliders[j].collider); +			if (!get_collision( +					{ +						.collider = colliders[i].collider, +						.transform = colliders[i].transform, +						.rigidbody = colliders[i].rigidbody, +					}, +					{ +						.collider = colliders[j].collider, +						.transform = colliders[j].transform, +						.rigidbody = colliders[j].rigidbody, +					}, +					type)) +				continue; +			collisions_ret.emplace_back(colliders[i], colliders[j]); +		} +	} + +	return collisions_ret; +} + +bool CollisionSystem::have_common_layer(const std::set<int> & layers1, +										const std::set<int> & layers2) { + +	// Check if any number is equal in the layers +	for (int num : layers1) { +		if (layers2.contains(num)) { +			// Common layer found +			return true; +			break; +		} +	} +	// No common layer found +	return false; +} + +CollisionSystem::CollisionInternalType +CollisionSystem::get_collider_type(const collider_variant & collider1, +								   const collider_variant & collider2) const { +	if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider1)) { +		if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { +			return CollisionInternalType::CIRCLE_CIRCLE; +		} else { +			return CollisionInternalType::CIRCLE_BOX; +		} +	} else { +		if (std::holds_alternative<std::reference_wrapper<CircleCollider>>(collider2)) { +			return CollisionInternalType::BOX_CIRCLE; +		} else { +			return CollisionInternalType::BOX_BOX; +		} +	} +} + +bool CollisionSystem::get_collision(const CollisionInternal & first_info, +									const CollisionInternal & second_info, +									CollisionInternalType type) const { +	switch (type) { +		case CollisionInternalType::BOX_BOX: { +			const BoxCollider & box_collider1 +				= std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); +			const BoxCollider & box_collider2 +				= std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); +			return this->get_box_box_collision(box_collider1, box_collider2, +											   first_info.transform, second_info.transform, +											   second_info.rigidbody, second_info.rigidbody); +		} +		case CollisionInternalType::BOX_CIRCLE: { +			const BoxCollider & box_collider +				= std::get<std::reference_wrapper<BoxCollider>>(first_info.collider); +			const CircleCollider & circle_collider +				= std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); +			return this->get_box_circle_collision( +				box_collider, circle_collider, first_info.transform, second_info.transform, +				second_info.rigidbody, second_info.rigidbody); +		} +		case CollisionInternalType::CIRCLE_CIRCLE: { +			const CircleCollider & circle_collider1 +				= std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); +			const CircleCollider & circle_collider2 +				= std::get<std::reference_wrapper<CircleCollider>>(second_info.collider); +			return this->get_circle_circle_collision( +				circle_collider1, circle_collider2, first_info.transform, +				second_info.transform, second_info.rigidbody, second_info.rigidbody); +		} +		case CollisionInternalType::CIRCLE_BOX: { +			const CircleCollider & circle_collider +				= std::get<std::reference_wrapper<CircleCollider>>(first_info.collider); +			const BoxCollider & box_collider +				= std::get<std::reference_wrapper<BoxCollider>>(second_info.collider); +			return this->get_box_circle_collision( +				box_collider, circle_collider, first_info.transform, second_info.transform, +				second_info.rigidbody, second_info.rigidbody); +		} +	} +	return false; +} + +bool CollisionSystem::get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, +											const Transform & transform1, +											const Transform & transform2, +											const Rigidbody & rigidbody1, +											const Rigidbody & rigidbody2) const { +	// Get current positions of colliders +	vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); +	vec2 final_position2 = this->get_current_position(box2.offset, transform2, rigidbody2); + +	// Calculate half-extents (half width and half height) +	float half_width1 = box1.dimensions.x / 2.0; +	float half_height1 = box1.dimensions.y / 2.0; +	float half_width2 = box2.dimensions.x / 2.0; +	float half_height2 = box2.dimensions.y / 2.0; + +	// Check if the boxes overlap along the X and Y axes +	return (final_position1.x + half_width1 > final_position2.x - half_width2 +			&& final_position1.x - half_width1 < final_position2.x + half_width2 +			&& final_position1.y + half_height1 > final_position2.y - half_height2 +			&& final_position1.y - half_height1 < final_position2.y + half_height2); +} + +bool CollisionSystem::get_box_circle_collision(const BoxCollider & box1, +											   const CircleCollider & circle2, +											   const Transform & transform1, +											   const Transform & transform2, +											   const Rigidbody & rigidbody1, +											   const Rigidbody & rigidbody2) const { +	// Get current positions of colliders +	vec2 final_position1 = this->get_current_position(box1.offset, transform1, rigidbody1); +	vec2 final_position2 = this->get_current_position(circle2.offset, transform2, rigidbody2); + +	// Calculate box half-extents +	float half_width = box1.dimensions.x / 2.0; +	float half_height = box1.dimensions.y / 2.0; + +	// Find the closest point on the box to the circle's center +	float closest_x = std::max(final_position1.x - half_width, +							   std::min(final_position2.x, final_position1.x + half_width)); +	float closest_y = std::max(final_position1.y - half_height, +							   std::min(final_position2.y, final_position1.y + half_height)); + +	// Calculate the distance squared between the circle's center and the closest point on the box +	float distance_x = final_position2.x - closest_x; +	float distance_y = final_position2.y - closest_y; +	float distance_squared = distance_x * distance_x + distance_y * distance_y; + +	// Compare distance squared with the square of the circle's radius +	return distance_squared < circle2.radius * circle2.radius; +} + +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)); +} diff --git a/src/crepe/system/CollisionSystem.h b/src/crepe/system/CollisionSystem.h index c1a70d8..48a8f86 100644 --- a/src/crepe/system/CollisionSystem.h +++ b/src/crepe/system/CollisionSystem.h @@ -1,13 +1,311 @@  #pragma once +#include <optional> +#include <variant> +#include <vector> + +#include "api/BoxCollider.h" +#include "api/CircleCollider.h" +#include "api/Event.h" +#include "api/Metadata.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "api/Vector2.h" + +#include "Collider.h"  #include "System.h"  namespace crepe { +//! A system responsible for detecting and handling collisions between colliders.  class CollisionSystem : public System {  public:  	using System::System; -	void update() override; + +private: +	//! A variant type that can hold either a BoxCollider or a CircleCollider. +	using collider_variant = std::variant<std::reference_wrapper<BoxCollider>, +										  std::reference_wrapper<CircleCollider>>; + +	//! Enum representing the types of collider pairs for collision detection. +	enum class CollisionInternalType { +		BOX_BOX, +		CIRCLE_CIRCLE, +		BOX_CIRCLE, +		CIRCLE_BOX, +	}; + +	/** +		* \brief A structure to store the collision data of a single collider. +		* +		* This structure all components and id that are for needed within this system when calculating or handeling collisions. +		* The transform and rigidbody are mostly needed for location and rotation. +		* In rigidbody additional info is written about what the body of the object is, +		* and how it should respond on a collision. +		*/ +	struct CollisionInternal { +		game_object_id_t id = 0; +		collider_variant collider; +		Transform & transform; +		Rigidbody & rigidbody; +	}; + +	//! Enum representing movement directions during collision resolution. +	enum class Direction { +		//! No movement required. +		NONE, +		//! Movement in the X direction. +		X_DIRECTION, +		//! Movement in the Y direction. +		Y_DIRECTION, +		//! Movement in both X and Y directions. +		BOTH +	}; + +public: +	/** +		* \brief Structure representing detailed collision information between two colliders. +		* +		* Includes information about the colliding objects and the resolution data for handling the collision. +		*/ +	struct CollisionInfo { +		Collider & this_collider; +		Transform & this_transform; +		Rigidbody & this_rigidbody; +		Metadata & this_metadata; +		Collider & other_collider; +		Transform & other_transform; +		Rigidbody & other_rigidbody; +		Metadata & other_metadata; +		//! The resolution vector for the collision. +		vec2 resolution; +		//! The direction of movement for resolving the collision. +		Direction resolution_direction = Direction::NONE; +	}; + +public: +	//! Updates the collision system by checking for collisions between colliders and handling them. +	void fixed_update() override; + +private: +	/** +		* \brief Determines the type of collider pair from two colliders. +		* +		* Uses std::holds_alternative to identify the types of the provided colliders. +		* +		* \param collider1 First collider variant (BoxCollider or CircleCollider). +		* \param collider2 Second collider variant (BoxCollider or CircleCollider). +		* \return The combined type of the two colliders. +		*/ +	CollisionInternalType get_collider_type(const collider_variant & collider1, +											const collider_variant & collider2) const; + +	/** +		* \brief Calculates the current position of a collider. +		* +		* Combines the Collider offset, Transform position, and Rigidbody offset to compute the position of the collider. +		* +		* \param collider_offset The offset of the collider. +		* \param transform The Transform of the associated game object. +		* \param rigidbody The Rigidbody of the associated game object. +		* \return The calculated position of the collider. +		*/ +	vec2 get_current_position(const vec2 & collider_offset, const Transform & transform, +							  const Rigidbody & rigidbody) const; + +private: +	/** +		* \brief Handles collision resolution between two colliders. +		* +		* Processes collision data and adjusts objects to resolve collisions and/or calls the user oncollision script function. +		* +		* \param data1 Collision data for the first collider. +		* \param data2 Collision data for the second collider. +		*/ +	void collision_handler_request(CollisionInternal & data1, CollisionInternal & data2); + +	/** +		* \brief Resolves collision between two colliders and calculates the movement required. +		* +		* Determines the displacement and direction needed to separate colliders based on their types. +		* +		* \param data1 Collision data for the first collider. +		* \param data2 Collision data for the second collider. +		* \param type The type of collider pair. +		* \return A pair containing the resolution vector and direction for the first collider. +		*/ +	std::pair<vec2, Direction> collision_handler(CollisionInternal & data1, +												 CollisionInternal & data2, +												 CollisionInternalType type); + +	/** +		* \brief Calculates the resolution vector for two BoxColliders. +		* +		* Computes the displacement required to separate two overlapping BoxColliders. +		* +		* \param box_collider1 The first BoxCollider. +		* \param box_collider2 The second BoxCollider. +		* \param position1 The position of the first BoxCollider. +		* \param position2 The position of the second BoxCollider. +		* \return The resolution vector for the collision. +		*/ +	vec2 get_box_box_resolution(const BoxCollider & box_collider1, +								const BoxCollider & box_collider2, const vec2 & position1, +								const vec2 & position2) const; + +	/** +		* \brief Calculates the resolution vector for two CircleCollider. +		* +		* Computes the displacement required to separate two overlapping CircleCollider. +		* +		* \param circle_collider1 The first CircleCollider. +		* \param circle_collider2 The second CircleCollider. +		* \param final_position1 The position of the first CircleCollider. +		* \param final_position2 The position of the second CircleCollider. +		* \return The resolution vector for the collision. +		*/ +	vec2 get_circle_circle_resolution(const CircleCollider & circle_collider1, +									  const CircleCollider & circle_collider2, +									  const vec2 & final_position1, +									  const vec2 & final_position2) const; + +	/** +		* \brief Calculates the resolution vector for two CircleCollider. +		* +		* Computes the displacement required to separate two overlapping CircleCollider. +		* +		* \param circle_collider The first CircleCollider. +		* \param box_collider The second CircleCollider. +		* \param circle_position The position of the CircleCollider. +		* \param box_position The position of the BoxCollider. +		* \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. +		* +		* Decides the correct resolution process based on the dynamic or static nature of the colliders involved. +		* +		* \param info Collision information containing data about both colliders. +		*/ +	void determine_collision_handler(CollisionInfo & info); + +	/** +		* \brief Handles collisions involving static objects. +		* +		* Resolves collisions by adjusting positions and modifying velocities if bounce is enabled. +		* +		* \param info Collision information containing data about both colliders. +		*/ +	void static_collision_handler(CollisionInfo & info); + +private: +	/** +		* \brief Checks for collisions between colliders. +		* +		* Identifies collisions and generates pairs of colliding objects for further processing. +		* +		* \param colliders A collection of all active colliders. +		* \return A list of collision pairs with their associated data. +		*/ +	std::vector<std::pair<CollisionInternal, CollisionInternal>> +	gather_collisions(std::vector<CollisionInternal> & colliders); + +	/** +	 * \brief Checks if two collision layers have at least one common layer. +	 * +	 * This function checks if there is any overlapping layer between the two 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. +	 * +	 * \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. +	 */ + +	bool have_common_layer(const std::set<int> & layers1, const std::set<int> & layers2); + +	/** +		* \brief Checks for collision between two colliders. +		* +		* Calls the appropriate collision detection function based on the collider types. +		* +		* \param first_info Collision data for the first collider. +		* \param second_info Collision data for the second collider. +		* \param type The type of collider pair. +		* \return True if a collision is detected, otherwise false. +		*/ +	bool get_collision(const CollisionInternal & first_info, +					   const CollisionInternal & second_info, +					   CollisionInternalType type) const; + +	/** +		* \brief Detects collisions between two BoxColliders. +		* +		* \param box1 The first BoxCollider. +		* \param box2 The second BoxCollider. +		* \param transform1 Transform of the first object. +		* \param transform2 Transform of the second object. +		* \param rigidbody1 Rigidbody of the first object. +		* \param rigidbody2 Rigidbody of the second object. +		* \return True if a collision is detected, otherwise false. +		*/ +	bool get_box_box_collision(const BoxCollider & box1, const BoxCollider & box2, +							   const Transform & transform1, const Transform & transform2, +							   const Rigidbody & rigidbody1, +							   const Rigidbody & rigidbody2) const; + +	/** +	 * \brief Check collision for box on circle collider +	 * +	 * \param box1 The BoxCollider +	 * \param circle2 The CircleCollider +	 * \param transform1 Transform of the first object. +	 * \param transform2 Transform of the second object. +	 * \param rigidbody1 Rigidbody of the first object. +	 * \param rigidbody2 Rigidbody of the second object. +	 * \return True if a collision is detected, otherwise false. +	 */ +	bool get_box_circle_collision(const BoxCollider & box1, const CircleCollider & circle2, +								  const Transform & transform1, const Transform & transform2, +								  const Rigidbody & rigidbody1, +								  const Rigidbody & rigidbody2) const; + +	/** +	 * \brief Check collision for circle on circle collider +	 * +	 * \param circle1 First CircleCollider +	 * \param circle2 Second CircleCollider +	 * \param transform1 Transform of the first object. +	 * \param transform2 Transform of the second object. +	 * \param rigidbody1 Rigidbody of the first object. +	 * \param rigidbody2 Rigidbody of the second object. +	 * \return True if a collision is detected, otherwise false. +	 * +	 * \return status of collision +	 */ +	bool get_circle_circle_collision(const CircleCollider & circle1, +									 const CircleCollider & circle2, +									 const Transform & transform1, +									 const Transform & transform2, +									 const Rigidbody & rigidbody1, +									 const Rigidbody & rigidbody2) const; +}; + +/** + * \brief Event triggered during a collision between objects. + */ +class CollisionEvent : public Event { +public: +	crepe::CollisionSystem::CollisionInfo info; +	CollisionEvent(const crepe::CollisionSystem::CollisionInfo & collisionInfo) +		: info(collisionInfo) {}  };  } // namespace crepe diff --git a/src/crepe/system/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 new file mode 100644 index 0000000..d209282 --- /dev/null +++ b/src/crepe/system/InputSystem.cpp @@ -0,0 +1,209 @@ +#include "../api/Button.h" +#include "../facade/SDLContext.h" +#include "../manager/ComponentManager.h" +#include "../manager/EventManager.h" +#include "util/Log.h" + +#include "InputSystem.h" + +using namespace crepe; + +void InputSystem::fixed_update() { +	ComponentManager & mgr = this->mediator.component_manager; +	SDLContext & context = this->mediator.sdl_context; +	std::vector<EventData> event_list = context.get_events(); +	RefVector<Camera> cameras = mgr.get_components_by_type<Camera>(); +	OptionalRef<Camera> curr_cam_ref; + +	// Find the active camera +	for (Camera & cam : cameras) { +		if (!cam.active) continue; +		curr_cam_ref = cam; +		break; +	} +	if (!curr_cam_ref) return; + +	Camera & current_cam = curr_cam_ref; +	RefVector<Transform> transform_vec +		= mgr.get_components_by_id<Transform>(current_cam.game_object_id); +	Transform & cam_transform = transform_vec.front().get(); + +	vec2 camera_origin = cam_transform.position + current_cam.data.postion_offset +						 - (current_cam.viewport_size / 2); + +	for (const EventData & event : event_list) { +		// Only calculate mouse coordinates for relevant events +		if (event.event_type == EventType::MOUSE_DOWN +			|| event.event_type == EventType::MOUSE_UP +			|| event.event_type == EventType::MOUSE_MOVE +			|| event.event_type == EventType::MOUSE_WHEEL) { +			this->handle_mouse_event(event, camera_origin, current_cam); + +		} else { +			this->handle_non_mouse_event(event); +		} +	} +} + +void InputSystem::handle_mouse_event(const EventData & event, const vec2 & camera_origin, +									 const Camera & current_cam) { +	EventManager & event_mgr = this->mediator.event_manager; +	vec2 adjusted_mouse; +	adjusted_mouse.x = event.data.mouse_data.mouse_position.x + camera_origin.x; +	adjusted_mouse.x = event.data.mouse_data.mouse_position.y + camera_origin.y; +	// Check if the mouse is within the viewport +	if ((adjusted_mouse.x < camera_origin.x +		 || adjusted_mouse.x > camera_origin.x + current_cam.viewport_size.x +		 || adjusted_mouse.y < camera_origin.y +		 || adjusted_mouse.y > camera_origin.y + current_cam.viewport_size.y)) +		return; + +	// Handle mouse-specific events +	switch (event.event_type) { +		case EventType::MOUSE_DOWN: +			event_mgr.queue_event<MousePressEvent>({ +				.mouse_pos = adjusted_mouse, +				.button = event.data.mouse_data.mouse_button, +			}); +			this->last_mouse_down_position = adjusted_mouse; +			this->last_mouse_button = event.data.mouse_data.mouse_button; +			break; + +		case EventType::MOUSE_UP: { +			event_mgr.queue_event<MouseReleaseEvent>({ +				.mouse_pos = adjusted_mouse, +				.button = event.data.mouse_data.mouse_button, +			}); +			vec2 delta_move = adjusted_mouse - this->last_mouse_down_position; +			int click_tolerance = Config::get_instance().input.click_tolerance; +			if (this->last_mouse_button == event.data.mouse_data.mouse_button +				&& std::abs(delta_move.x) <= click_tolerance +				&& std::abs(delta_move.y) <= click_tolerance) { +				event_mgr.queue_event<MouseClickEvent>({ +					.mouse_pos = adjusted_mouse, +					.button = event.data.mouse_data.mouse_button, +				}); +				this->handle_click(event.data.mouse_data.mouse_button, adjusted_mouse); +			} +			break; +		} + +		case EventType::MOUSE_MOVE: +			event_mgr.queue_event<MouseMoveEvent>({ +				.mouse_pos = adjusted_mouse, +				.mouse_delta = event.data.mouse_data.rel_mouse_move, +			}); +			this->handle_move(event, adjusted_mouse); +			break; + +		case EventType::MOUSE_WHEEL: +			event_mgr.queue_event<MouseScrollEvent>({ +				.mouse_pos = adjusted_mouse, +				.scroll_direction = event.data.mouse_data.scroll_direction, +				.scroll_delta = event.data.mouse_data.scroll_delta, +			}); +			break; + +		default: +			break; +	} +} + +void InputSystem::handle_non_mouse_event(const EventData & event) { +	EventManager & event_mgr = this->mediator.event_manager; +	switch (event.event_type) { +		case EventType::KEY_DOWN: + +			event_mgr.queue_event<KeyPressEvent>( +				{.repeat = event.data.key_data.key_repeat, .key = event.data.key_data.key}); +			break; +		case EventType::KEY_UP: +			event_mgr.queue_event<KeyReleaseEvent>({.key = event.data.key_data.key}); +			break; +		case EventType::SHUTDOWN: +			event_mgr.queue_event<ShutDownEvent>({}); +			break; +		case EventType::WINDOW_EXPOSE: +			event_mgr.queue_event<WindowExposeEvent>({}); +			break; +		case EventType::WINDOW_RESIZE: +			event_mgr.queue_event<WindowResizeEvent>( +				WindowResizeEvent{.dimensions = event.data.window_data.resize_dimension}); +			break; +		case EventType::WINDOW_MOVE: +			event_mgr.queue_event<WindowMoveEvent>( +				{.delta_move = event.data.window_data.move_delta}); +			break; +		case EventType::WINDOW_MINIMIZE: +			event_mgr.queue_event<WindowMinimizeEvent>({}); +			break; +		case EventType::WINDOW_MAXIMIZE: +			event_mgr.queue_event<WindowMaximizeEvent>({}); +			break; +		case EventType::WINDOW_FOCUS_GAIN: +			event_mgr.queue_event<WindowFocusGainEvent>({}); +			break; +		case EventType::WINDOW_FOCUS_LOST: +			event_mgr.queue_event<WindowFocusLostEvent>({}); +			break; +		default: +			break; +	} +} + +void InputSystem::handle_move(const EventData & event_data, const vec2 & mouse_pos) { +	ComponentManager & mgr = this->mediator.component_manager; +	EventManager & event_mgr = this->mediator.event_manager; +	RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + +	for (Button & button : buttons) { +		if (!button.active) continue; +		Metadata & metadata +			= mgr.get_components_by_id<Metadata>(button.game_object_id).front(); +		Transform & transform +			= mgr.get_components_by_id<Transform>(button.game_object_id).front(); +		bool was_hovering = button.hover; +		if (this->is_mouse_inside_button(mouse_pos, button, transform)) { +			button.hover = true; +			if (!was_hovering) { +				event_mgr.trigger_event<ButtonEnterEvent>(metadata); +			} +		} else { +			button.hover = false; +			if (was_hovering) { +				event_mgr.trigger_event<ButtonExitEvent>(metadata); +			} +		} +	} +} + +void InputSystem::handle_click(const MouseButton & mouse_button, const vec2 & mouse_pos) { +	ComponentManager & mgr = this->mediator.component_manager; +	EventManager & event_mgr = this->mediator.event_manager; +	RefVector<Button> buttons = mgr.get_components_by_type<Button>(); + +	for (Button & button : buttons) { +		if (!button.active) continue; +		Metadata & metadata +			= mgr.get_components_by_id<Metadata>(button.game_object_id).front(); +		Transform & transform +			= mgr.get_components_by_id<Transform>(button.game_object_id).front(); + +		if (this->is_mouse_inside_button(mouse_pos, button, transform)) { +			event_mgr.trigger_event<ButtonPressEvent>(metadata); +		} +	} +} + +bool InputSystem::is_mouse_inside_button(const vec2 & mouse_pos, const Button & button, +										 const Transform & transform) { +	int actual_x = transform.position.x + button.offset.x; +	int actual_y = transform.position.y + button.offset.y; + +	int half_width = button.dimensions.x / 2; +	int half_height = button.dimensions.y / 2; + +	// Check if the mouse is within the button's boundaries +	return mouse_pos.x >= actual_x - half_width && mouse_pos.x <= actual_x + half_width +		   && mouse_pos.y >= actual_y - half_height && mouse_pos.y <= actual_y + half_height; +} diff --git a/src/crepe/system/InputSystem.h b/src/crepe/system/InputSystem.h new file mode 100644 index 0000000..0f1bfa1 --- /dev/null +++ b/src/crepe/system/InputSystem.h @@ -0,0 +1,132 @@ +#pragma once + +#include "../api/Config.h" +#include "../facade/EventData.h" + +#include "../api/Event.h" +#include "../api/Metadata.h" +#include "../types.h" +#include "../util/OptionalRef.h" + +#include "System.h" + +namespace crepe { + +class Camera; +class Button; +class Transform; +//! Event triggered when a button is clicked +class ButtonPressEvent : public Event { +public: +	//! Metadata of the button. +	const Metadata & metadata; +	/** +	 * \param metadata Metadata of the button pressed +	 */ +	ButtonPressEvent(const Metadata & metadata) : metadata(metadata){}; +}; +//! Event triggered when the mouse enters a button +class ButtonEnterEvent : public Event { +public: +	//! Metadata of the button. +	const Metadata & metadata; +	/** +	 * \param metadata Metadata of the button pressed +	 */ +	ButtonEnterEvent(const Metadata & metadata) : metadata(metadata){}; +}; +//! Event triggered when the mouse leaves a button +class ButtonExitEvent : public Event { +public: +	//! Metadata of the button. +	const Metadata & metadata; +	/** +	 * \param metadata Metadata of the button pressed +	 */ +	ButtonExitEvent(const Metadata & metadata) : metadata(metadata){}; +}; + +/** + * \brief Handles the processing of input events created by SDLContext + * + * This system processes events such as mouse clicks, mouse movement, and keyboard + * actions. It is responsible for detecting interactions with UI buttons and + * passing the corresponding events to the registered listeners. + */ +class InputSystem : public System { +public: +	using System::System; + +	/** +	 * \brief Updates the system, processing all input events. +	 * This method processes all events and triggers corresponding actions. +	 */ +	void fixed_update() override; + +private: +	//! Stores the last position of the mouse when the button was pressed. +	vec2 last_mouse_down_position; +	// TODO: specify world/hud space and make regular `vec2` + +	//! Stores the last mouse button pressed. +	MouseButton last_mouse_button = MouseButton::NONE; +	/** +	 * \brief Handles mouse-related events. +	 * \param event The event data for the mouse event. +	 * \param camera_origin The origin position of the camera in world space. +	 * \param current_cam The currently active camera. +	 * +	 * This method processes mouse events, adjusts the mouse position to world coordinates, +	 * and triggers the appropriate mouse-specific event handling logic. +	 */ +	void handle_mouse_event(const EventData & event, const vec2 & camera_origin, +							const Camera & current_cam); +	/** +	 * \brief Handles non-mouse-related events. +	 * \param event The event data for the non-mouse event. +	 * +	 * This method processes events that do not involve the mouse, such as keyboard events, +	 * window events, and shutdown events, and triggers the corresponding event actions. +	 */ +	void handle_non_mouse_event(const EventData & event); +	/** +	* \brief Handles the mouse click event. +	* \param mouse_button The mouse button involved in the click. +	* \param world_mouse_x The X coordinate of the mouse in world space. +	* \param world_mouse_y The Y coordinate of the mouse in world space. +	* +	* This method processes the mouse click event and triggers the corresponding button action. +	*/ +	void handle_click(const MouseButton & mouse_button, const vec2 & mouse_pos); + +	/** +	* \brief Handles the mouse movement event. +	* \param event_data The event data containing information about the mouse movement. +	* \param world_mouse_x The X coordinate of the mouse in world space. +	* \param world_mouse_y The Y coordinate of the mouse in world space. +	* +	* This method processes the mouse movement event and updates the button hover state. +	*/ +	void handle_move(const EventData & event_data, const vec2 & mouse_pos); + +	/** +	* \brief Checks if the mouse position is inside the bounds of the button. +	* \param world_mouse_x The X coordinate of the mouse in world space. +	* \param world_mouse_y The Y coordinate of the mouse in world space. +	* \param button The button to check. +	* \param transform The transform component of the button. +	* \return True if the mouse is inside the button, false otherwise. +	*/ +	bool is_mouse_inside_button(const vec2 & mouse_pos, const Button & button, +								const Transform & transform); + +	/** +	* \brief Handles the button press event, calling the on_click callback if necessary. +	* \param button The button being pressed. +	* +	* This method triggers the on_click action for the button when it is pressed. +	*/ +	void handle_button_press(Button & button); +}; + +} // namespace crepe diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index fcf7522..bbc7366 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -1,19 +1,24 @@ +#include <chrono>  #include <cmath>  #include <cstdlib>  #include <ctime> -#include "api/ParticleEmitter.h" -#include "api/Transform.h" -#include "api/Vector2.h" +#include "../api/ParticleEmitter.h" +#include "../api/Transform.h" +#include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" -#include "ComponentManager.h"  #include "ParticleSystem.h"  using namespace crepe; -void ParticleSystem::update() { +void ParticleSystem::fixed_update() {  	// Get all emitters -	ComponentManager & mgr = this->component_manager; +	const Mediator & mediator = this->mediator; +	LoopTimerManager & loop_timer = mediator.loop_timer; +	ComponentManager & mgr = mediator.component_manager; +	float dt = loop_timer.get_scaled_fixed_delta_time().count(); +  	RefVector<ParticleEmitter> emitters = mgr.get_components_by_type<ParticleEmitter>();  	for (ParticleEmitter & emitter : emitters) { @@ -22,40 +27,39 @@ void ParticleSystem::update() {  			= mgr.get_components_by_id<Transform>(emitter.game_object_id).front().get();  		// Emit particles based on emission_rate -		int updates = calculate_update(this->update_count, emitter.data.emission_rate); -		for (size_t i = 0; i < updates; i++) { -			emit_particle(emitter, transform); +		emitter.spawn_accumulator += emitter.data.emission_rate * dt; +		while (emitter.spawn_accumulator >= 1.0) { +			this->emit_particle(emitter, transform); +			emitter.spawn_accumulator -= 1.0;  		}  		// Update all particles -		for (Particle & particle : emitter.data.particles) { +		for (Particle & particle : emitter.particles) {  			if (particle.active) { -				particle.update(); +				particle.update(dt);  			}  		}  		// Check if within boundary -		check_bounds(emitter, transform); +		this->check_bounds(emitter, transform);  	} - -	this->update_count = (this->update_count + 1) % this->MAX_UPDATE_COUNT;  }  void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform & transform) { -	constexpr double DEG_TO_RAD = M_PI / 180.0; +	constexpr float DEG_TO_RAD = M_PI / 180.0; -	Vector2 initial_position = emitter.data.position + transform.position; -	double random_angle -		= generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); +	vec2 initial_position = emitter.data.offset + transform.position; +	float random_angle +		= this->generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); -	double random_speed -		= generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); -	double angle_radians = random_angle * DEG_TO_RAD; +	float random_speed +		= this->generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); +	float angle_radians = random_angle * DEG_TO_RAD; -	Vector2 velocity +	vec2 velocity  		= {random_speed * std::cos(angle_radians), random_speed * std::sin(angle_radians)}; -	for (Particle & particle : emitter.data.particles) { +	for (Particle & particle : emitter.particles) {  		if (!particle.active) {  			particle.reset(emitter.data.end_lifespan, initial_position, velocity,  						   random_angle); @@ -64,66 +68,54 @@ void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform &  	}  } -int ParticleSystem::calculate_update(int count, double emission) const { -	double integer_part = std::floor(emission); -	double fractional_part = emission - integer_part; - -	if (fractional_part > 0) { -		int denominator = static_cast<int>(1.0 / fractional_part); -		return (count % denominator == 0) ? 1 : 0; -	} - -	return static_cast<int>(emission); -} -  void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform & transform) { -	Vector2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position; -	double half_width = emitter.data.boundary.width / 2.0; -	double half_height = emitter.data.boundary.height / 2.0; - -	const double LEFT = offset.x - half_width; -	const double RIGHT = offset.x + half_width; -	const double TOP = offset.y - half_height; -	const double BOTTOM = offset.y + half_height; - -	for (Particle & particle : emitter.data.particles) { -		const Vector2 & position = particle.position; -		bool within_bounds = (position.x >= LEFT && position.x <= RIGHT && position.y >= TOP -							  && position.y <= BOTTOM); - +	vec2 offset = emitter.data.boundary.offset + transform.position + emitter.data.offset; +	float half_width = emitter.data.boundary.width / 2.0; +	float half_height = emitter.data.boundary.height / 2.0; + +	float left = offset.x - half_width; +	float right = offset.x + half_width; +	float top = offset.y - half_height; +	float bottom = offset.y + half_height; + +	for (Particle & particle : emitter.particles) { +		const vec2 & position = particle.position; +		bool within_bounds = (position.x >= left && position.x <= right && position.y >= top +							  && position.y <= bottom); +		//if not within bounds do a reset or stop velocity  		if (!within_bounds) {  			if (emitter.data.boundary.reset_on_exit) {  				particle.active = false;  			} else {  				particle.velocity = {0, 0}; -				if (position.x < LEFT) particle.position.x = LEFT; -				else if (position.x > RIGHT) particle.position.x = RIGHT; -				if (position.y < TOP) particle.position.y = TOP; -				else if (position.y > BOTTOM) particle.position.y = BOTTOM; +				if (position.x < left) particle.position.x = left; +				else if (position.x > right) particle.position.x = right; +				if (position.y < top) particle.position.y = top; +				else if (position.y > bottom) particle.position.y = bottom;  			}  		}  	}  } -double ParticleSystem::generate_random_angle(double min_angle, double max_angle) const { +float ParticleSystem::generate_random_angle(float min_angle, float max_angle) const {  	if (min_angle == max_angle) {  		return min_angle;  	} else if (min_angle < max_angle) {  		return min_angle -			   + static_cast<double>(std::rand() % static_cast<int>(max_angle - min_angle)); +			   + static_cast<float>(std::rand() % static_cast<int>(max_angle - min_angle));  	} else { -		double angle_offset = (360 - min_angle) + max_angle; -		double random_angle -			= min_angle + static_cast<double>(std::rand() % static_cast<int>(angle_offset)); +		float angle_offset = (360 - min_angle) + max_angle; +		float random_angle +			= min_angle + static_cast<float>(std::rand() % static_cast<int>(angle_offset));  		return (random_angle >= 360) ? random_angle - 360 : random_angle;  	}  } -double ParticleSystem::generate_random_speed(double min_speed, double max_speed) const { +float ParticleSystem::generate_random_speed(float min_speed, float max_speed) const {  	if (min_speed == max_speed) {  		return min_speed;  	} else {  		return min_speed -			   + static_cast<double>(std::rand() % static_cast<int>(max_speed - min_speed)); +			   + static_cast<float>(std::rand() % static_cast<int>(max_speed - min_speed));  	}  } diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index c647284..4296ff3 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -20,31 +20,21 @@ public:  	 * \brief Updates all particle emitters by emitting particles, updating particle states, and  	 * checking bounds.  	 */ -	void update() override; +	void fixed_update() override;  private:  	/**  	 * \brief Emits a particle from the specified emitter based on its emission properties. -	 *  +	 *  	 * \param emitter Reference to the ParticleEmitter.  	 * \param transform Const reference to the Transform component associated with the emitter.  	 */  	void emit_particle(ParticleEmitter & emitter, const Transform & transform);  	/** -	 * \brief Calculates the number of times particles should be emitted based on emission rate -	 * and update count. -	 *  -	 * \param count Current update count. -	 * \param emission Emission rate. -	 * \return The number of particles to emit. -	 */ -	int calculate_update(int count, double emission) const; - -	/**  	 * \brief Checks whether particles are within the emitter’s boundary, resets or stops  	 * particles if they exit. -	 *  +	 *  	 * \param emitter Reference to the ParticleEmitter.  	 * \param transform Const reference to the Transform component associated with the emitter.  	 */ @@ -52,29 +42,21 @@ private:  	/**  	 * \brief Generates a random angle for particle emission within the specified range. -	 *  +	 *  	 * \param min_angle Minimum emission angle in degrees.  	 * \param max_angle Maximum emission angle in degrees.  	 * \return Random angle in degrees.  	 */ -	double generate_random_angle(double min_angle, double max_angle) const; +	float generate_random_angle(float min_angle, float max_angle) const;  	/**  	 * \brief Generates a random speed for particle emission within the specified range. -	 *  +	 *  	 * \param min_speed Minimum emission speed.  	 * \param max_speed Maximum emission speed.  	 * \return Random speed.  	 */ -	double generate_random_speed(double min_speed, double max_speed) const; - -private: -	//! Counter to count updates to determine how many times emit_particle is -	// called. -	unsigned int update_count = 0; -	//! Determines the lowest amount of emission rate (1000 = 0.001 = 1 particle per 1000 -	// updates). -	static constexpr unsigned int MAX_UPDATE_COUNT = 100; +	float generate_random_speed(float min_speed, float max_speed) const;  };  } // namespace crepe diff --git a/src/crepe/system/PhysicsSystem.cpp b/src/crepe/system/PhysicsSystem.cpp index bcde431..62f8132 100644 --- a/src/crepe/system/PhysicsSystem.cpp +++ b/src/crepe/system/PhysicsSystem.cpp @@ -1,89 +1,98 @@  #include <cmath> -#include "../ComponentManager.h"  #include "../api/Config.h"  #include "../api/Rigidbody.h"  #include "../api/Transform.h"  #include "../api/Vector2.h" +#include "../manager/ComponentManager.h" +#include "../manager/LoopTimerManager.h" +#include "../manager/Mediator.h"  #include "PhysicsSystem.h"  using namespace crepe; -void PhysicsSystem::update() { -	ComponentManager & mgr = this->component_manager; +void PhysicsSystem::fixed_update() { +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	LoopTimerManager & loop_timer = mediator.loop_timer;  	RefVector<Rigidbody> rigidbodies = mgr.get_components_by_type<Rigidbody>(); -	RefVector<Transform> transforms = mgr.get_components_by_type<Transform>(); +	float dt = loop_timer.get_scaled_fixed_delta_time().count(); -	double gravity = Config::get_instance().physics.gravity; +	float gravity = Config::get_instance().physics.gravity;  	for (Rigidbody & rigidbody : rigidbodies) {  		if (!rigidbody.active) continue; +		Transform & transform +			= mgr.get_components_by_id<Transform>(rigidbody.game_object_id).front().get();  		switch (rigidbody.data.body_type) {  			case Rigidbody::BodyType::DYNAMIC: -				for (Transform & transform : transforms) { -					if (transform.game_object_id == rigidbody.game_object_id) { +				if (transform.game_object_id == rigidbody.game_object_id) { +					// Add gravity -						// Add gravity -						if (rigidbody.data.use_gravity) { -							rigidbody.data.linear_velocity.y -								+= (rigidbody.data.mass * rigidbody.data.gravity_scale -									* gravity); -						} -						// Add damping -						if (rigidbody.data.angular_damping != 0) { -							rigidbody.data.angular_velocity *= rigidbody.data.angular_damping; -						} -						if (rigidbody.data.linear_damping != Vector2{0, 0}) { -							rigidbody.data.linear_velocity *= rigidbody.data.linear_damping; -						} +					if (rigidbody.data.mass <= 0) { +						throw std::runtime_error("Mass must be greater than 0"); +					} -						// Max velocity check -						if (rigidbody.data.angular_velocity -							> rigidbody.data.max_angular_velocity) { -							rigidbody.data.angular_velocity -								= rigidbody.data.max_angular_velocity; -						} else if (rigidbody.data.angular_velocity -								   < -rigidbody.data.max_angular_velocity) { -							rigidbody.data.angular_velocity -								= -rigidbody.data.max_angular_velocity; -						} +					if (gravity <= 0) { +						throw std::runtime_error("Config Gravity must be greater than 0"); +					} -						if (rigidbody.data.linear_velocity.x -							> rigidbody.data.max_linear_velocity.x) { -							rigidbody.data.linear_velocity.x -								= rigidbody.data.max_linear_velocity.x; -						} else if (rigidbody.data.linear_velocity.x -								   < -rigidbody.data.max_linear_velocity.x) { -							rigidbody.data.linear_velocity.x -								= -rigidbody.data.max_linear_velocity.x; -						} +					if (rigidbody.data.gravity_scale > 0 && !rigidbody.data.constraints.y) { +						rigidbody.data.linear_velocity.y +							+= (rigidbody.data.mass * rigidbody.data.gravity_scale * gravity +								* dt); +					} +					// Add coefficient rotation +					if (rigidbody.data.angular_velocity_coefficient > 0) { +						rigidbody.data.angular_velocity +							*= std::pow(rigidbody.data.angular_velocity_coefficient, dt); +					} -						if (rigidbody.data.linear_velocity.y -							> rigidbody.data.max_linear_velocity.y) { -							rigidbody.data.linear_velocity.y -								= rigidbody.data.max_linear_velocity.y; -						} else if (rigidbody.data.linear_velocity.y -								   < -rigidbody.data.max_linear_velocity.y) { -							rigidbody.data.linear_velocity.y -								= -rigidbody.data.max_linear_velocity.y; -						} +					// Add coefficient movement horizontal +					if (rigidbody.data.linear_velocity_coefficient.x > 0 +						&& !rigidbody.data.constraints.x) { +						rigidbody.data.linear_velocity.x +							*= std::pow(rigidbody.data.linear_velocity_coefficient.x, dt); +					} -						// Move object -						if (!rigidbody.data.constraints.rotation) { -							transform.rotation += rigidbody.data.angular_velocity; -							transform.rotation = std::fmod(transform.rotation, 360.0); -							if (transform.rotation < 0) { -								transform.rotation += 360.0; -							} -						} -						if (!rigidbody.data.constraints.x) { -							transform.position.x += rigidbody.data.linear_velocity.x; -						} -						if (!rigidbody.data.constraints.y) { -							transform.position.y += rigidbody.data.linear_velocity.y; +					// Add coefficient movement horizontal +					if (rigidbody.data.linear_velocity_coefficient.y > 0 +						&& !rigidbody.data.constraints.y) { +						rigidbody.data.linear_velocity.y +							*= std::pow(rigidbody.data.linear_velocity_coefficient.y, dt); +					} + +					// Max velocity check +					if (rigidbody.data.angular_velocity +						> rigidbody.data.max_angular_velocity) { +						rigidbody.data.angular_velocity = rigidbody.data.max_angular_velocity; +					} else if (rigidbody.data.angular_velocity +							   < -rigidbody.data.max_angular_velocity) { +						rigidbody.data.angular_velocity = -rigidbody.data.max_angular_velocity; +					} + +					// Set max velocity to maximum length +					if (rigidbody.data.linear_velocity.length() +						> rigidbody.data.max_linear_velocity) { +						rigidbody.data.linear_velocity.normalize(); +						rigidbody.data.linear_velocity *= rigidbody.data.max_linear_velocity; +					} + +					// Move object +					if (!rigidbody.data.constraints.rotation) { +						transform.rotation += rigidbody.data.angular_velocity * dt; +						transform.rotation = std::fmod(transform.rotation, 360.0); +						if (transform.rotation < 0) { +							transform.rotation += 360.0;  						}  					} +					if (!rigidbody.data.constraints.x) { +						transform.position.x += rigidbody.data.linear_velocity.x * dt; +					} +					if (!rigidbody.data.constraints.y) { +						transform.position.y += rigidbody.data.linear_velocity.y * dt; +					}  				}  				break;  			case Rigidbody::BodyType::KINEMATIC: diff --git a/src/crepe/system/PhysicsSystem.h b/src/crepe/system/PhysicsSystem.h index 227ab69..5ed624f 100644 --- a/src/crepe/system/PhysicsSystem.h +++ b/src/crepe/system/PhysicsSystem.h @@ -6,7 +6,7 @@ namespace crepe {  /**   * \brief System that controls all physics - *  + *   * This class is a physics system that uses a rigidbody and transform to add physics to a game   * object.   */ @@ -15,10 +15,10 @@ public:  	using System::System;  	/**  	 * \brief updates the physics system. -	 *  +	 *  	 * 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 ad510f5..33218f6 100644 --- a/src/crepe/system/RenderSystem.cpp +++ b/src/crepe/system/RenderSystem.cpp @@ -2,42 +2,60 @@  #include <cassert>  #include <cmath>  #include <functional> -#include <iostream> +#include <optional>  #include <stdexcept>  #include <vector> -#include "../ComponentManager.h" +#include "../api/Camera.h"  #include "../api/ParticleEmitter.h"  #include "../api/Sprite.h" +#include "../api/Text.h"  #include "../api/Transform.h" -#include "../api/Vector2.h" +#include "../facade/Font.h"  #include "../facade/SDLContext.h" +#include "../facade/Texture.h" +#include "../manager/ComponentManager.h" +#include "../manager/ResourceManager.h"  #include "RenderSystem.h" +#include "types.h"  using namespace crepe;  using namespace std; -void RenderSystem::clear_screen() { this->context.clear_screen(); } +void RenderSystem::clear_screen() { +	SDLContext & ctx = this->mediator.sdl_context; +	ctx.clear_screen(); +} -void RenderSystem::present_screen() { this->context.present_screen(); } -void RenderSystem::update_camera() { -	ComponentManager & mgr = this->component_manager; +void RenderSystem::present_screen() { +	SDLContext & ctx = this->mediator.sdl_context; +	ctx.present_screen(); +} +void RenderSystem::update_camera() { +	ComponentManager & mgr = this->mediator.component_manager; +	SDLContext & ctx = this->mediator.sdl_context;  	RefVector<Camera> cameras = mgr.get_components_by_type<Camera>();  	if (cameras.size() == 0) throw std::runtime_error("No cameras in current scene");  	for (Camera & cam : cameras) {  		if (!cam.active) continue; -		this->context.set_camera(cam); -		this->curr_cam_ref = &cam; +		const Transform & transform +			= mgr.get_components_by_id<Transform>(cam.game_object_id).front().get(); +		vec2 new_camera_pos = transform.position + cam.data.postion_offset; +		ctx.update_camera_view(cam, new_camera_pos); +		return;  	} +	throw std::runtime_error("No active cameras in current scene");  }  bool sorting_comparison(const Sprite & a, const Sprite & b) { -	if (a.sorting_in_layer < b.sorting_in_layer) return true; -	if (a.sorting_in_layer == b.sorting_in_layer) return a.order_in_layer < b.order_in_layer; +	if (a.data.sorting_in_layer != b.data.sorting_in_layer) +		return a.data.sorting_in_layer < b.data.sorting_in_layer; +	if (a.data.order_in_layer != b.data.order_in_layer) +		return a.data.order_in_layer < b.data.order_in_layer;  	return false;  } @@ -49,16 +67,18 @@ RefVector<Sprite> RenderSystem::sort(RefVector<Sprite> & objs) const {  	return sorted_objs;  } -void RenderSystem::update() { +void RenderSystem::frame_update() {  	this->clear_screen(); -	this->update_camera();  	this->render();  	this->present_screen();  }  bool RenderSystem::render_particle(const Sprite & sprite, const double & scale) { -	ComponentManager & mgr = this->component_manager; +	ComponentManager & mgr = this->mediator.component_manager; +	SDLContext & ctx = this->mediator.sdl_context; +	ResourceManager & resource_manager = this->mediator.resource_manager; +	Texture & res = resource_manager.get<Texture>(sprite.source);  	vector<reference_wrapper<ParticleEmitter>> emitters  		= mgr.get_components_by_id<ParticleEmitter>(sprite.game_object_id); @@ -66,28 +86,51 @@ bool RenderSystem::render_particle(const Sprite & sprite, const double & scale)  	bool rendering_particles = false;  	for (const ParticleEmitter & em : emitters) { -		if (!(&em.data.sprite == &sprite)) continue; +		if (&em.sprite != &sprite) continue;  		rendering_particles = true;  		if (!em.active) continue; -		for (const Particle & p : em.data.particles) { +		for (const Particle & p : em.particles) {  			if (!p.active) continue; -			this->context.draw_particle(sprite, p.position, p.angle, scale, -										*this->curr_cam_ref); + +			ctx.draw(SDLContext::RenderContext{ +				.sprite = sprite, +				.texture = res, +				.pos = p.position, +				.angle = p.angle, +				.scale = scale, +			});  		}  	}  	return rendering_particles;  }  void RenderSystem::render_normal(const Sprite & sprite, const Transform & tm) { -	this->context.draw(sprite, tm, *this->curr_cam_ref); +	SDLContext & ctx = this->mediator.sdl_context; +	ResourceManager & resource_manager = this->mediator.resource_manager; +	const Texture & res = resource_manager.get<Texture>(sprite.source); + +	ctx.draw(SDLContext::RenderContext{ +		.sprite = sprite, +		.texture = res, +		.pos = tm.position, +		.angle = tm.rotation, +		.scale = tm.scale, +	});  }  void RenderSystem::render() { +	ComponentManager & mgr = this->mediator.component_manager; +	this->update_camera(); -	ComponentManager & mgr = this->component_manager;  	RefVector<Sprite> sprites = mgr.get_components_by_type<Sprite>(); +	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 @@ -100,3 +143,18 @@ void RenderSystem::render() {  		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 30b41cf..656ad5b 100644 --- a/src/crepe/system/RenderSystem.h +++ b/src/crepe/system/RenderSystem.h @@ -1,24 +1,21 @@  #pragma once -#include <functional> -#include <vector> - -#include "facade/SDLContext.h" +#include <cmath>  #include "System.h" -#include <cmath> +#include "types.h"  namespace crepe {  class Camera;  class Sprite; - +class Transform; +class Text;  /** - * \class RenderSystem   * \brief Manages rendering operations for all game objects.   *   * RenderSystem is responsible for rendering, clearing and presenting the screen, and - * managing the active camera.  + * managing the active camera.   */  class RenderSystem : public System {  public: @@ -27,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. @@ -46,42 +43,41 @@ 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 +	 * \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  	 * \return true if particles have been rendered  	 */  	bool render_particle(const Sprite & sprite, const double & scale); -  	/** -	 * \brief renders a sprite with a Transform component on the screen  +	 * \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); +	/** +	 * \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 tm the Transform component that holds the position,rotation and scale  	 */  	void render_normal(const Sprite & sprite, const Transform & tm);  	/**  	 * \brief sort a vector sprite objects with  	 * -	 * \param objs the vector that will do a sorting algorithm on  +	 * \param objs the vector that will do a sorting algorithm on  	 * \return returns a sorted reference vector  	 */  	RefVector<Sprite> sort(RefVector<Sprite> & objs) const;  	/** -	 * \todo Include color handling for sprites.  	 * \todo Add text rendering using SDL_ttf for text components.  	 * \todo Implement a text component and a button component. -	 * \todo Ensure each sprite is checked for active status before rendering. -	 * \todo Sort all layers by order before rendering.  	 * \todo Consider adding text input functionality.  	 */ - -private: -	//! Pointer to the current active camera for rendering -	Camera * curr_cam_ref = nullptr; -	// TODO: needs a better solution - -	SDLContext & context = SDLContext::get_instance();  };  } // 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 20a83f7..93b4853 100644 --- a/src/crepe/system/ScriptSystem.cpp +++ b/src/crepe/system/ScriptSystem.cpp @@ -1,16 +1,27 @@ -#include "../ComponentManager.h"  #include "../api/BehaviorScript.h"  #include "../api/Script.h" +#include "../manager/ComponentManager.h"  #include "ScriptSystem.h"  using namespace std;  using namespace crepe; -void ScriptSystem::update() { -	dbg_trace(); +void ScriptSystem::fixed_update() { +	LoopTimerManager & timer = this->mediator.loop_timer; +	duration_t delta_time = timer.get_scaled_fixed_delta_time(); +	this->update(&Script::fixed_update, delta_time); +} + +void ScriptSystem::frame_update() { +	LoopTimerManager & timer = this->mediator.loop_timer; +	duration_t delta_time = timer.get_delta_time(); +	this->update(&Script::frame_update, delta_time); +} -	ComponentManager & mgr = this->component_manager; +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) { @@ -23,6 +34,7 @@ void ScriptSystem::update() {  			script->init();  			script->initialized = true;  		} -		script->update(); + +		(*script.*update_function)(delta_time);  	}  } diff --git a/src/crepe/system/ScriptSystem.h b/src/crepe/system/ScriptSystem.h index 936e9ca..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 { @@ -8,21 +10,28 @@ 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.cpp b/src/crepe/system/System.cpp index 937a423..ecc740d 100644 --- a/src/crepe/system/System.cpp +++ b/src/crepe/system/System.cpp @@ -1,7 +1,5 @@ -#include "../util/Log.h" -  #include "System.h"  using namespace crepe; -System::System(ComponentManager & mgr) : component_manager(mgr) { dbg_trace(); } +System::System(const Mediator & mediator) : mediator(mediator) {} diff --git a/src/crepe/system/System.h b/src/crepe/system/System.h index 28ea20e..e2ce7eb 100644 --- a/src/crepe/system/System.h +++ b/src/crepe/system/System.h @@ -1,5 +1,7 @@  #pragma once +#include "../manager/Mediator.h" +  namespace crepe {  class ComponentManager; @@ -7,23 +9,24 @@ class ComponentManager;  /**   * \brief Base ECS system class   * - * This class is used as the base for all system classes. Classes derived from - * System must implement the System::update() method and copy Script::Script - * with the `using`-syntax. + * This class is used as the base for all system classes. Classes derived from System must + * implement the System::update() method and copy Script::Script with the `using`-syntax.   */  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(ComponentManager &); +	System(const Mediator & m);  	virtual ~System() = default;  protected: -	ComponentManager & component_manager; +	const Mediator & mediator;  };  } // namespace crepe |