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 |