From d69adb5666fd6f73edbc6d410afcdf23c24e7c6b Mon Sep 17 00:00:00 2001 From: Jaro Date: Fri, 8 Nov 2024 19:20:05 +0100 Subject: particle updated --- src/crepe/Particle.cpp | 25 ++++-- src/crepe/Particle.h | 24 +++--- src/crepe/api/ParticleEmitter.cpp | 35 ++------ src/crepe/api/ParticleEmitter.h | 72 ++++++++++------ src/crepe/system/ParticleSystem.cpp | 159 ++++++++++++++++++++++++++++-------- src/crepe/system/ParticleSystem.h | 10 ++- src/example/particles.cpp | 46 +++++++++++ 7 files changed, 262 insertions(+), 109 deletions(-) create mode 100644 src/example/particles.cpp diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp index 4810e80..d2b1880 100644 --- a/src/crepe/Particle.cpp +++ b/src/crepe/Particle.cpp @@ -1,20 +1,29 @@ #include "Particle.h" -using namespace crepe; +#include "api/Transform.h" -Particle::Particle() { this->active = false; } +using namespace crepe; -void Particle::reset(float lifespan, Position position, Position velocity) { +void Particle::reset(uint32_t lifespan, Vector2 position, Vector2 velocity, double angle) { this->time_in_life = 0; this->lifespan = lifespan; this->position = position; this->velocity = velocity; this->active = true; + this->angle = angle; +} + +void Particle::update() { + time_in_life++; + if (time_in_life >= lifespan) + { + this->active = false; + return; + } + velocity += force_over_time; + position += velocity; } -void Particle::update(float deltaTime) { - time_in_life += deltaTime; - position.x += velocity.x * deltaTime; - position.y += velocity.y * deltaTime; - if (time_in_life >= lifespan) this->active = false; +void Particle::stop_movement() { + this->velocity = {0,0}; } diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h index 21e691d..f52196c 100644 --- a/src/crepe/Particle.h +++ b/src/crepe/Particle.h @@ -1,22 +1,24 @@ #pragma once -#include "Position.h" +#include "api/Transform.h" namespace crepe { class Particle { public: - Position position; - // FIXME: `Position` is an awkward name for a 2D vector. See FIXME comment in - // api/Transform.h for fix proposal. - Position velocity; - float lifespan; - bool active; + Vector2 position; + Vector2 velocity; + Vector2 force_over_time; + uint32_t lifespan; + bool active = false; + uint32_t time_in_life = 0; + double angle; - Particle(); - void reset(float lifespan, Position position, Position velocity); - void update(float deltaTime); - float time_in_life; + Particle() = default; + void reset(uint32_t lifespan, Vector2 position, Vector2 velocity,double angle); + void update(); + void stop_movement(); + }; } // namespace crepe diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp index 3b2e2f2..f585a81 100644 --- a/src/crepe/api/ParticleEmitter.cpp +++ b/src/crepe/api/ParticleEmitter.cpp @@ -1,36 +1,19 @@ -#include -#include - #include "Particle.h" #include "ParticleEmitter.h" using namespace crepe; -ParticleEmitter::ParticleEmitter(game_object_id_t id, uint32_t max_particles, - uint32_t emission_rate, uint32_t speed, - uint32_t speed_offset, uint32_t angle, - uint32_t angleOffset, float begin_lifespan, - float end_lifespan) - : Component(id), max_particles(max_particles), emission_rate(emission_rate), - speed(speed), speed_offset(speed_offset), position{0, 0}, - begin_lifespan(begin_lifespan), end_lifespan(end_lifespan) { - std::srand( - static_cast(std::time(nullptr))); // initialize random seed - std::cout << "Create emitter" << std::endl; - // FIXME: Why do these expressions start with `360 +`, only to be `% 360`'d - // right after? This does not make any sense to me. - min_angle = (360 + angle - (angleOffset % 360)) % 360; - max_angle = (360 + angle + (angleOffset % 360)) % 360; - position.x = 400; // FIXME: what are these magic values? - position.y = 400; - for (size_t i = 0; i < max_particles; i++) { - this->particles.emplace_back(); - } +ParticleEmitter::ParticleEmitter(uint32_t game_object_id, const ParticleEmitterData& data) : Component(game_object_id),data(data) { + for (size_t i = 0; i < this->data.max_particles; i++) { + this->data.particles.emplace_back(); + } } + ParticleEmitter::~ParticleEmitter() { - std::vector::iterator it = this->particles.begin(); - while (it != this->particles.end()) { - it = this->particles.erase(it); + std::vector::iterator it = this->data.particles.begin(); + while (it != this->data.particles.end()) { + it = this->data.particles.erase(it); } } + diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index 5939723..f931e8c 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -5,38 +5,58 @@ #include "Component.h" #include "Particle.h" +#include "Transform.h" + +class Sprite; namespace crepe { class ParticleEmitter : public Component { public: - ParticleEmitter(game_object_id_t id, uint32_t max_particles, - uint32_t emission_rate, uint32_t speed, - uint32_t speed_offset, uint32_t angle, uint32_t angleOffset, - float begin_lifespan, float end_lifespan); - ~ParticleEmitter(); - - //! position of the emitter - Position position; - //! maximum number of particles - uint32_t max_particles; - //! rate of particle emission - uint32_t emission_rate; - //! base speed of the particles - uint32_t speed; - //! offset for random speed variation - uint32_t speed_offset; - //! min angle of particle emission - uint32_t min_angle; - //! max angle of particle emission - uint32_t max_angle; - //! begin Lifespan of particle (only visual) - float begin_lifespan; - //! begin Lifespan of particle - float end_lifespan; + struct ParticleBoundary{ + //! boundary width (midpoint is emitter location) + double boundary_width = 0.0; + //! boundary height (midpoint is emitter location) + double boundary_height = 0.0; + //! boundary offset from particle emitter location + Vector2 boundary_offset; + //! reset on exit or stop velocity and set max postion + bool reset_on_exit = false; + }; - //! collection of particles - std::vector particles; + struct ParticleEmitterData{ + //! position of the emitter + Vector2 position; + //! maximum number of particles + uint32_t max_particles = 0; + //! rate of particle emission per update (Lowest value = 0.001 any lower is ignored) + double emission_rate = 0; + //! min speed of the particles + double min_speed = 0; + //! min speed of the particles + double max_speed = 0; + //! min angle of particle emission + double min_angle = 0; + //! max angle of particle emission + double max_angle = 0; + //! begin Lifespan of particle (only visual) + double begin_lifespan = 0.0; + //! end Lifespan of particle + double end_lifespan = 0.0; + //! force over time (physics) + Vector2 force_over_time; + //! particle boundary + ParticleBoundary boundary; + //! collection of particles + std::vector particles; + //! sprite reference + const Sprite* sprite; + }; +public: + ParticleEmitter(uint32_t game_object_id, const ParticleEmitterData& data); + ~ParticleEmitter(); +public: + ParticleEmitterData data; }; } // namespace crepe diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 397b586..23534a3 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -1,62 +1,149 @@ #include #include +#include -#include "../ComponentManager.h" -#include "../api/ParticleEmitter.h" +#include "api/ParticleEmitter.h" +#include "api/Vector2.h" +#include "ComponentManager.h" #include "ParticleSystem.h" using namespace crepe; -ParticleSystem::ParticleSystem() : elapsed_time(0.0f) {} +ParticleSystem::ParticleSystem() {} void ParticleSystem::update() { + + // Get all emitters ComponentManager & mgr = ComponentManager::get_instance(); std::vector> emitters = mgr.get_components_by_type(); - float delta_time = 0.10; + for (ParticleEmitter & emitter : emitters) { - float update_amount = 1 / static_cast(emitter.emission_rate); - for (float i = 0; i < delta_time; i += update_amount) { - emit_particle(emitter); + + // Get transform linked to emitter + const Transform& transform = mgr.get_components_by_id(emitter.GAME_OBJECT_ID).front().get(); + + // Check if within boundary + check_bounds(emitter,transform); + + // 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); } - for (size_t j = 0; j < emitter.particles.size(); j++) { - if (emitter.particles[j].active) { - emitter.particles[j].update(delta_time); + + // Update all particles + for (size_t j = 0; j < emitter.data.particles.size(); j++) { + if (emitter.data.particles[j].active) { + emitter.data.particles[j].update(); } } } + update_count++; + if(update_count == MAX_UPDATE_COUNT) update_count = 0; } -void ParticleSystem::emit_particle(ParticleEmitter & emitter) { - Position initial_position = {emitter.position.x, emitter.position.y}; - float random_angle = 0.0f; - if (emitter.max_angle < emitter.min_angle) { - random_angle = ((emitter.min_angle - + (std::rand() - % (static_cast(emitter.max_angle + 360 - - emitter.min_angle + 1)))) - % 360); +void ParticleSystem::emit_particle(ParticleEmitter & emitter,const Transform& transform) { + constexpr double DEG_TO_RAD = M_PI / 180.0; + + Vector2 initial_position = emitter.data.position + transform.position; + double min_angle = emitter.data.min_angle; + double max_angle = emitter.data.max_angle; + double random_angle; + + if (min_angle <= max_angle) { + // Standard range (e.g., 10 to 20 degrees) + double angle_offset = max_angle - min_angle; + random_angle = min_angle + static_cast(std::rand() % static_cast(angle_offset)); } else { - random_angle = emitter.min_angle - + (std::rand() - % (static_cast(emitter.max_angle - - emitter.min_angle + 1))); + // Wrap-around range (e.g., 350 to 10 degrees) + double angle_offset = (360 - min_angle) + max_angle; + random_angle = min_angle + static_cast(std::rand() % static_cast(angle_offset)); + + // Wrap around to keep random_angle within 0-360 degrees + if (random_angle >= 360) { + random_angle -= 360; + } } - float angle_in_radians = random_angle * (M_PI / 180.0f); - float random_speed_offset = (static_cast(std::rand()) / RAND_MAX) - * (2 * emitter.speed_offset) - - emitter.speed_offset; - float velocity_x - = (emitter.speed + random_speed_offset) * std::cos(angle_in_radians); - float velocity_y - = (emitter.speed + random_speed_offset) * std::sin(angle_in_radians); - Position initial_velocity = {velocity_x, velocity_y}; - for (size_t i = 0; i < emitter.particles.size(); i++) { - if (!emitter.particles[i].active) { - emitter.particles[i].reset(emitter.end_lifespan, initial_position, - initial_velocity); + + // Generate a random speed between min_speed and max_speed + double speed_offset = emitter.data.max_speed - emitter.data.min_speed; + double random_speed = emitter.data.min_speed + static_cast(std::rand() % static_cast(speed_offset)); + + // Convert random_angle to radians + double angle_radians = random_angle * DEG_TO_RAD; + + Vector2 velocity = { + random_speed * std::cos(angle_radians), + random_speed * std::sin(angle_radians) + }; + + + for (size_t i = 0; i < emitter.data.particles.size(); i++) { + if (!emitter.data.particles[i].active) { + emitter.data.particles[i].reset(emitter.data.end_lifespan, initial_position,velocity,random_angle); break; } } } + +int ParticleSystem::calculate_update(int count, double emission) { + + //get interger part of the emission + double integer_part = std::floor(emission); + + // Get the fractional part of the emission + double fractional_part = emission - integer_part; + + // Convert the fractional part to a denominator value + int denominator = static_cast(1.0 / fractional_part); + + // For emissions like 0.01, 0.1, 0.5, etc., calculate the update frequency + if (fractional_part > 0) { + // Calculate how often the update should be triggered based on the fractional part + if (count % denominator == 0) { + return 1; + } else { + return 0; + } + } + + // For integer emissions, return the emission directly + return static_cast(emission); +} + +void ParticleSystem::check_bounds(ParticleEmitter & emitter,const Transform& transform) +{ + Vector2 offset = emitter.data.boundary.boundary_offset + transform.position + emitter.data.position; + double half_width = emitter.data.boundary.boundary_width / 2.0; + double half_height = emitter.data.boundary.boundary_height / 2.0; + + // Define boundary edges + 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; + + std::vector& particles = emitter.data.particles; + for (Particle& particle : particles) + { + const Vector2& position = particle.position; + + // Check if particle is within bounds + bool within_bounds = (position.x >= left && position.x <= right && position.y >= top && position.y <= bottom); + if (!within_bounds) + { + if (emitter.data.boundary.reset_on_exit) + { + particle.active = false; + } + else + { + particle.velocity = {0, 0}; + //todo add that particle goes back to boundary + } + } + } +} diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index 3ac1d3f..3b9cb54 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -10,9 +10,15 @@ public: void update(); private: - void emit_particle(ParticleEmitter & emitter); //emits a new particle + void emit_particle(ParticleEmitter & emitter,const Transform& transform); + int calculate_update(int count, double emission); + void check_bounds(ParticleEmitter & emitter,const Transform& transform); - float elapsed_time; //elapsed time since the last emission +private: + //! counter to count updates to determine how many times emit_particle is called. + uint32_t update_count = 0; + //! determines the lowest amount of emissionrate (1000 = 0.001 = 1 particle per 1000 updates). + const uint32_t MAX_UPDATE_COUNT = 100; }; } // namespace crepe diff --git a/src/example/particles.cpp b/src/example/particles.cpp new file mode 100644 index 0000000..a56ec5c --- /dev/null +++ b/src/example/particles.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace crepe; +using namespace std; + +int main(int argc, char * argv[]) { + + Color color(0, 0, 0, 0); + GameObject *game_object = new GameObject(0, "Name", "Tag", Vector2{0,0},0,0); + game_object->add_component(ParticleEmitter::ParticleEmitterData{ + .position = {0,0}, + .max_particles = 0, + .emission_rate = 0, + .min_speed = 0, + .max_speed = 0, + .min_angle = 0, + .max_angle = 0, + .begin_lifespan = 0, + .end_lifespan = 0, + .force_over_time = 0, + .boundary{ + .boundary_width = 0, + .boundary_height = 0, + .boundary_offset = {0,0}, + .reset_on_exit = false, + }, + .sprite = nullptr, + }); + + return 0; +} -- cgit v1.2.3 From 4e6f10f2d6ed593a21b985ccabe305a9cd6212cc Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Fri, 8 Nov 2024 21:37:28 +0000 Subject: fixed build --- src/crepe/api/Vector2.cpp | 6 +++--- src/crepe/api/Vector2.h | 10 +++++----- src/crepe/system/ParticleSystem.cpp | 2 +- src/example/CMakeLists.txt | 1 + src/example/particles.cpp | 5 ++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/crepe/api/Vector2.cpp b/src/crepe/api/Vector2.cpp index 09bb59b..09b3fa3 100644 --- a/src/crepe/api/Vector2.cpp +++ b/src/crepe/api/Vector2.cpp @@ -3,7 +3,7 @@ namespace crepe { // Constructor with initial values -Vector2::Vector2(float x, float y) : x(x), y(y) {} +Vector2::Vector2(double x, double y) : x(x), y(y) {} // Subtracts another vector from this vector and returns the result. Vector2 Vector2::operator-(const Vector2 & other) const { @@ -16,7 +16,7 @@ Vector2 Vector2::operator+(const Vector2 & other) const { } // Multiplies this vector by a scalar and returns the result. -Vector2 Vector2::operator*(float scalar) const { +Vector2 Vector2::operator*(double scalar) const { return {x * scalar, y * scalar}; } @@ -35,7 +35,7 @@ Vector2 & Vector2::operator+=(const Vector2 & other) { } // Adds a scalar value to both components of this vector and updates this vector. -Vector2 & Vector2::operator+=(float other) { +Vector2 & Vector2::operator+=(double other) { x += other; y += other; return *this; diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h index 741951b..5a57484 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -6,15 +6,15 @@ namespace crepe { class Vector2 { public: //! X component of the vector - float x; + double x; //! Y component of the vector - float y; + double y; //! Default constructor Vector2() = default; //! Constructor with initial values - Vector2(float x, float y); + Vector2(double x, double y); //! Subtracts another vector from this vector and returns the result. Vector2 operator-(const Vector2 & other) const; @@ -23,7 +23,7 @@ public: Vector2 operator+(const Vector2 & other) const; //! Multiplies this vector by a scalar and returns the result. - Vector2 operator*(float scalar) const; + Vector2 operator*(double scalar) const; //! Multiplies this vector by another vector element-wise and updates this vector. Vector2 & operator*=(const Vector2 & other); @@ -32,7 +32,7 @@ public: Vector2 & operator+=(const Vector2 & other); //! Adds a scalar value to both components of this vector and updates this vector. - Vector2 & operator+=(float other); + Vector2 & operator+=(double other); //! Returns the negation of this vector. Vector2 operator-() const; diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 23534a3..b2fe829 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -22,7 +22,7 @@ void ParticleSystem::update() { for (ParticleEmitter & emitter : emitters) { // Get transform linked to emitter - const Transform& transform = mgr.get_components_by_id(emitter.GAME_OBJECT_ID).front().get(); + const Transform& transform = mgr.get_components_by_id(emitter.game_object_id).front().get(); // Check if within boundary check_bounds(emitter,transform); diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 36f9d4d..722ffea 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -28,4 +28,5 @@ add_example(proxy) add_example(db) add_example(ecs) add_example(scene_manager) +add_example(particles) diff --git a/src/example/particles.cpp b/src/example/particles.cpp index a56ec5c..bcff48f 100644 --- a/src/example/particles.cpp +++ b/src/example/particles.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -32,11 +31,11 @@ int main(int argc, char * argv[]) { .max_angle = 0, .begin_lifespan = 0, .end_lifespan = 0, - .force_over_time = 0, + .force_over_time = Vector2{0,0}, .boundary{ .boundary_width = 0, .boundary_height = 0, - .boundary_offset = {0,0}, + .boundary_offset = Vector2{0,0}, .reset_on_exit = false, }, .sprite = nullptr, -- cgit v1.2.3 From b668abb3a0671c1233bb30d038a5a53018aa8620 Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 15:25:13 +0100 Subject: added unit test for particles --- src/crepe/Particle.h | 43 ++++++++++++++++++- src/crepe/api/ParticleEmitter.cpp | 2 +- src/crepe/api/ParticleEmitter.h | 45 +++++++++++++++---- src/crepe/system/ParticleSystem.cpp | 6 +-- src/example/particles.cpp | 1 - src/test/CMakeLists.txt | 3 +- src/test/ParticleTest.cpp | 86 +++++++++++++++++++++++++++++++++++++ 7 files changed, 169 insertions(+), 17 deletions(-) create mode 100644 src/test/ParticleTest.cpp diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h index f52196c..77c3ea3 100644 --- a/src/crepe/Particle.h +++ b/src/crepe/Particle.h @@ -1,22 +1,61 @@ #pragma once -#include "api/Transform.h" +#include + +#include "api/Vector2.h" namespace crepe { +/** + * \brief Represents a particle in the particle emitter. + * + * This class stores information about a single particle, including its position, + * velocity, lifespan, and other properties. Particles can be updated over time + * to simulate movement and can also be reset or stopped. + */ class Particle { public: + //! Position of the particle in 2D space. Vector2 position; + //! Velocity vector indicating the speed and direction of the particle. Vector2 velocity; + //! Accumulated force affecting the particle over time. Vector2 force_over_time; + //! Total lifespan of the particle in milliseconds. uint32_t lifespan; + //! Active state of the particle; true if it is in use, false otherwise. bool active = false; + //! The time the particle has been alive, in milliseconds. uint32_t time_in_life = 0; + //! The angle at which the particle is oriented or moving. double angle; Particle() = default; - void reset(uint32_t lifespan, Vector2 position, Vector2 velocity,double angle); + /** + * \brief Resets the particle with new properties. + * + * This method initializes the particle with a specific lifespan, position, + * velocity, and angle, marking it as active and resetting its life counter. + * + * \param lifespan The lifespan of the particle in amount of updates. + * \param position The starting position of the particle. + * \param velocity The initial velocity of the particle. + * \param angle The angle of the particle's trajectory or orientation. + */ + void reset(uint32_t lifespan, Vector2 position, Vector2 velocity, double angle); + /** + * \brief Updates the particle's state. + * + * Advances the particle's position based on its velocity and applies accumulated forces. + * Deactivates the particle if its lifespan has expired. + */ void update(); + /** + * \brief Stops the particle's movement. + * + * Sets the particle's velocity to zero, effectively halting any further + * movement. + */ void stop_movement(); }; diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp index f585a81..000bf30 100644 --- a/src/crepe/api/ParticleEmitter.cpp +++ b/src/crepe/api/ParticleEmitter.cpp @@ -3,7 +3,7 @@ using namespace crepe; -ParticleEmitter::ParticleEmitter(uint32_t game_object_id, const ParticleEmitterData& data) : Component(game_object_id),data(data) { +ParticleEmitter::ParticleEmitter(uint32_t game_object_id, const Data& data) : Component(game_object_id),data(data) { for (size_t i = 0; i < this->data.max_particles; i++) { this->data.particles.emplace_back(); } diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index f931e8c..2cda1bb 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -11,24 +11,44 @@ class Sprite; namespace crepe { + +/** + * \brief Data holder for particle emission parameters. + * + * The ParticleEmitter class stores configuration data for particle properties, + * defining the characteristics and boundaries of particle emissions. + */ + class ParticleEmitter : public Component { public: - struct ParticleBoundary{ + /** + * \brief Defines the boundary within which particles are constrained. + * + * This structure specifies the boundary's size and offset, as well as the + * behavior of particles upon reaching the boundary limits. + */ + struct Boundary{ //! boundary width (midpoint is emitter location) - double boundary_width = 0.0; + double width = 0.0; //! boundary height (midpoint is emitter location) - double boundary_height = 0.0; + double height = 0.0; //! boundary offset from particle emitter location - Vector2 boundary_offset; + Vector2 offset; //! reset on exit or stop velocity and set max postion bool reset_on_exit = false; }; - struct ParticleEmitterData{ + /** + * \brief Holds parameters that control particle emission. + * + * Contains settings for the emitter’s position, particle speed, angle, lifespan, + * boundary, and the sprite used for rendering particles. + */ + struct Data{ //! position of the emitter Vector2 position; //! maximum number of particles - uint32_t max_particles = 0; + const uint32_t max_particles = 0; //! rate of particle emission per update (Lowest value = 0.001 any lower is ignored) double emission_rate = 0; //! min speed of the particles @@ -46,17 +66,24 @@ public: //! force over time (physics) Vector2 force_over_time; //! particle boundary - ParticleBoundary boundary; + Boundary boundary; //! collection of particles std::vector particles; //! sprite reference const Sprite* sprite; }; public: - ParticleEmitter(uint32_t game_object_id, const ParticleEmitterData& data); + /** + * \brief Constructs a ParticleEmitter data holder with specified settings. + * + * \param game_object_id Identifier for the game object using this emitter. + * \param data Configuration data defining particle properties. + */ + ParticleEmitter(uint32_t game_object_id, const Data& data); ~ParticleEmitter(); public: - ParticleEmitterData data; + //! Configuration data for particle emission settings. + Data data; }; } // namespace crepe diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index b2fe829..85321f0 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -116,9 +116,9 @@ int ParticleSystem::calculate_update(int count, double emission) { void ParticleSystem::check_bounds(ParticleEmitter & emitter,const Transform& transform) { - Vector2 offset = emitter.data.boundary.boundary_offset + transform.position + emitter.data.position; - double half_width = emitter.data.boundary.boundary_width / 2.0; - double half_height = emitter.data.boundary.boundary_height / 2.0; + 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; // Define boundary edges const double left = offset.x - half_width; diff --git a/src/example/particles.cpp b/src/example/particles.cpp index bcff48f..01e33cf 100644 --- a/src/example/particles.cpp +++ b/src/example/particles.cpp @@ -19,7 +19,6 @@ using namespace std; int main(int argc, char * argv[]) { - Color color(0, 0, 0, 0); GameObject *game_object = new GameObject(0, "Name", "Tag", Vector2{0,0},0,0); game_object->add_component(ParticleEmitter::ParticleEmitterData{ .position = {0,0}, diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 0e4eaed..acab388 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,6 +1,7 @@ target_sources(test_main PUBLIC dummy.cpp # audio.cpp - PhysicsTest.cpp + # PhysicsTest.cpp + ParticleTest.cpp ) diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp new file mode 100644 index 0000000..aa63ccf --- /dev/null +++ b/src/test/ParticleTest.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace std; +using namespace std::chrono_literals; +using namespace crepe; + +class ParticlesTest : public ::testing::Test { +protected: + GameObject * game_object; + ParticleSystem particle_system; + void SetUp() override { + ComponentManager & mgr = ComponentManager::get_instance(); + std::vector> transforms + = mgr.get_components_by_id(0); + if (transforms.empty()) { + game_object = new GameObject(0, "", "", Vector2{0, 0}, 0, 0); + game_object->add_component(ParticleEmitter::Data{ + .position = {0,0}, + .max_particles = 1, + .emission_rate = 0, + .min_speed = 0, + .max_speed = 0, + .min_angle = 0, + .max_angle = 0, + .begin_lifespan = 0, + .end_lifespan = 0, + .force_over_time = Vector2{0,0}, + .boundary{ + .width = 0, + .height = 0, + .offset = Vector2{0,0}, + .reset_on_exit = false, + }, + .sprite = nullptr, + }); + } + transforms = mgr.get_components_by_id(0); + Transform & transform = transforms.front().get(); + transform.position.x = 0.0; + transform.position.y = 0.0; + transform.rotation = 0.0; + std::vector> rigidbodies + = mgr.get_components_by_id(0); + Rigidbody & rigidbody = rigidbodies.front().get(); + rigidbody.data.angular_velocity = 0; + rigidbody.data.linear_velocity.x = 0; + rigidbody.data.linear_velocity.y = 0; + } +}; + +TEST_F(ParticlesTest, spawnParticle) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = ComponentManager::get_instance(); + ParticleEmitter & emitter = mgr.get_components_by_id(0).front().get(); + emitter.data.end_lifespan = 5; + emitter.data.boundary.height = 100; + emitter.data.boundary.width = 100; + emitter.data.max_speed = 1; + emitter.data.max_angle = 1; + particle_system.update(); + //check if nothing happend + EXPECT_EQ(emitter.data.particles.size(), 0); + emitter.data.emission_rate = 1; + particle_system.update(); + //check if particle spawned + EXPECT_EQ(emitter.data.particles.size(), 1); + for (auto& particle : emitter.data.particles) { + // Check velocity range + EXPECT_GE(particle.velocity.x, emitter.data.min_speed); // Speed should be greater than or equal to min_speed + EXPECT_LE(particle.velocity.x, emitter.data.max_speed); // Speed should be less than or equal to max_speed + EXPECT_GE(particle.velocity.y, emitter.data.min_speed); // Speed should be greater than or equal to min_speed + EXPECT_LE(particle.velocity.y, emitter.data.max_speed); // Speed should be less than or equal to max_speed + + // Check angle range + EXPECT_GE(particle.angle, emitter.data.min_angle); // Angle should be greater than or equal to min_angle + EXPECT_LE(particle.angle, emitter.data.max_angle); // Angle should be less than or equal to max_angle + } +} -- cgit v1.2.3 From 556eb319c9e5c3f6b90e9533eadcdd5682a5d347 Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 15:39:58 +0100 Subject: updated test --- src/test/ParticleTest.cpp | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index aa63ccf..368a1f6 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -24,7 +24,7 @@ protected: game_object = new GameObject(0, "", "", Vector2{0, 0}, 0, 0); game_object->add_component(ParticleEmitter::Data{ .position = {0,0}, - .max_particles = 1, + .max_particles = 100, .emission_rate = 0, .min_speed = 0, .max_speed = 0, @@ -47,12 +47,23 @@ protected: transform.position.x = 0.0; transform.position.y = 0.0; transform.rotation = 0.0; - std::vector> rigidbodies - = mgr.get_components_by_id(0); - Rigidbody & rigidbody = rigidbodies.front().get(); - rigidbody.data.angular_velocity = 0; - rigidbody.data.linear_velocity.x = 0; - rigidbody.data.linear_velocity.y = 0; + std::vector> rigidbodies + = mgr.get_components_by_id(0); + ParticleEmitter & emitter = rigidbodies.front().get(); + emitter.data.position = {0, 0}; + emitter.data.emission_rate = 0; + emitter.data.min_speed = 0; + emitter.data.max_speed = 0; + emitter.data.min_angle = 0; + emitter.data.max_angle = 0; + emitter.data.begin_lifespan = 0; + emitter.data.end_lifespan = 0; + emitter.data.force_over_time = Vector2{0, 0}; + emitter.data.boundary = {0, 0, Vector2{0, 0}, false}; + emitter.data.sprite = nullptr; + for (auto& particle : emitter.data.particles) { + particle.active = false; + } } }; @@ -67,11 +78,18 @@ TEST_F(ParticlesTest, spawnParticle) { emitter.data.max_angle = 1; particle_system.update(); //check if nothing happend - EXPECT_EQ(emitter.data.particles.size(), 0); + EXPECT_EQ(emitter.data.particles[0].active, 0); emitter.data.emission_rate = 1; + //check particle spawnes + particle_system.update(); + EXPECT_EQ(emitter.data.particles[0].active, 1); + particle_system.update(); + EXPECT_EQ(emitter.data.particles[1].active, 1); particle_system.update(); - //check if particle spawned - EXPECT_EQ(emitter.data.particles.size(), 1); + EXPECT_EQ(emitter.data.particles[2].active, 1); + particle_system.update(); + EXPECT_EQ(emitter.data.particles[3].active, 1); + for (auto& particle : emitter.data.particles) { // Check velocity range EXPECT_GE(particle.velocity.x, emitter.data.min_speed); // Speed should be greater than or equal to min_speed @@ -83,4 +101,5 @@ TEST_F(ParticlesTest, spawnParticle) { EXPECT_GE(particle.angle, emitter.data.min_angle); // Angle should be greater than or equal to min_angle EXPECT_LE(particle.angle, emitter.data.max_angle); // Angle should be less than or equal to max_angle } + } -- cgit v1.2.3 From b48773d19961be44bf9edd7a326dd85f304828f4 Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 16:08:22 +0100 Subject: added tests and fixed bugs in system --- src/crepe/system/ParticleSystem.cpp | 17 ++++++++++---- src/test/ParticleTest.cpp | 44 ++++++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 85321f0..48fe1d7 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -52,8 +52,10 @@ void ParticleSystem::emit_particle(ParticleEmitter & emitter,const Transform& tr double min_angle = emitter.data.min_angle; double max_angle = emitter.data.max_angle; double random_angle; - - if (min_angle <= max_angle) { + if(min_angle == max_angle){ + random_angle = min_angle; + } + else if (min_angle <= max_angle) { // Standard range (e.g., 10 to 20 degrees) double angle_offset = max_angle - min_angle; random_angle = min_angle + static_cast(std::rand() % static_cast(angle_offset)); @@ -70,8 +72,15 @@ void ParticleSystem::emit_particle(ParticleEmitter & emitter,const Transform& tr // Generate a random speed between min_speed and max_speed double speed_offset = emitter.data.max_speed - emitter.data.min_speed; - double random_speed = emitter.data.min_speed + static_cast(std::rand() % static_cast(speed_offset)); - + double random_speed = 0.0; + if(emitter.data.max_speed == emitter.data.min_speed) + { + random_speed = emitter.data.min_speed; + } + else { + random_speed = emitter.data.min_speed + static_cast(std::rand() % static_cast(speed_offset)); + } + // Convert random_angle to radians double angle_radians = random_angle * DEG_TO_RAD; diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index 368a1f6..1b949e3 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -74,8 +74,10 @@ TEST_F(ParticlesTest, spawnParticle) { emitter.data.end_lifespan = 5; emitter.data.boundary.height = 100; emitter.data.boundary.width = 100; - emitter.data.max_speed = 1; - emitter.data.max_angle = 1; + emitter.data.max_speed = 0.1; + emitter.data.max_angle = 0.1; + emitter.data.max_speed = 10; + emitter.data.max_angle = 10; particle_system.update(); //check if nothing happend EXPECT_EQ(emitter.data.particles[0].active, 0); @@ -89,7 +91,7 @@ TEST_F(ParticlesTest, spawnParticle) { EXPECT_EQ(emitter.data.particles[2].active, 1); particle_system.update(); EXPECT_EQ(emitter.data.particles[3].active, 1); - + for (auto& particle : emitter.data.particles) { // Check velocity range EXPECT_GE(particle.velocity.x, emitter.data.min_speed); // Speed should be greater than or equal to min_speed @@ -103,3 +105,39 @@ TEST_F(ParticlesTest, spawnParticle) { } } + +TEST_F(ParticlesTest, moveParticleHorizontal) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = ComponentManager::get_instance(); + ParticleEmitter & emitter = mgr.get_components_by_id(0).front().get(); + emitter.data.end_lifespan = 100; + emitter.data.boundary.height = 100; + emitter.data.boundary.width = 100; + emitter.data.min_speed = 1; + emitter.data.max_speed = 1; + emitter.data.max_angle = 0; + emitter.data.emission_rate = 1; + for (int a = 1; a < emitter.data.boundary.width/2; a++) { + particle_system.update(); + EXPECT_EQ(emitter.data.particles[0].position.x,a); + } +} + + +TEST_F(ParticlesTest, moveParticleVertical) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = ComponentManager::get_instance(); + ParticleEmitter & emitter = mgr.get_components_by_id(0).front().get(); + emitter.data.end_lifespan = 100; + emitter.data.boundary.height = 100; + emitter.data.boundary.width = 100; + emitter.data.min_speed = 1; + emitter.data.max_speed = 1; + emitter.data.min_angle = 90; + emitter.data.max_angle = 90; + emitter.data.emission_rate = 1; + for (int a = 1; a < emitter.data.boundary.width/2; a++) { + particle_system.update(); + EXPECT_EQ(emitter.data.particles[0].position.y,a); + } +} -- cgit v1.2.3 From 89b06f2c342a7f5e528dd37d96c5a7ffb90cd16c Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 18:01:49 +0100 Subject: added boundary tests and added functionality --- src/crepe/system/ParticleSystem.cpp | 101 +++++++++++++++++++----------------- src/test/ParticleTest.cpp | 54 +++++++++++++++++-- 2 files changed, 102 insertions(+), 53 deletions(-) diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 48fe1d7..e6dc670 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -24,8 +24,6 @@ void ParticleSystem::update() { // Get transform linked to emitter const Transform& transform = mgr.get_components_by_id(emitter.game_object_id).front().get(); - // Check if within boundary - check_bounds(emitter,transform); // Emit particles based on emission_rate int updates = calculate_update(this->update_count,emitter.data.emission_rate); @@ -40,7 +38,11 @@ void ParticleSystem::update() { emitter.data.particles[j].update(); } } + + // Check if within boundary + check_bounds(emitter,transform); } + update_count++; if(update_count == MAX_UPDATE_COUNT) update_count = 0; } @@ -100,59 +102,62 @@ void ParticleSystem::emit_particle(ParticleEmitter & emitter,const Transform& tr int ParticleSystem::calculate_update(int count, double emission) { - //get interger part of the emission - double integer_part = std::floor(emission); + //get interger part of the emission + double integer_part = std::floor(emission); - // Get the fractional part of the emission - double fractional_part = emission - integer_part; + // Get the fractional part of the emission + double fractional_part = emission - integer_part; - // Convert the fractional part to a denominator value - int denominator = static_cast(1.0 / fractional_part); + // Convert the fractional part to a denominator value + int denominator = static_cast(1.0 / fractional_part); - // For emissions like 0.01, 0.1, 0.5, etc., calculate the update frequency - if (fractional_part > 0) { - // Calculate how often the update should be triggered based on the fractional part - if (count % denominator == 0) { - return 1; - } else { - return 0; - } - } - - // For integer emissions, return the emission directly - return static_cast(emission); + // For emissions like 0.01, 0.1, 0.5, etc., calculate the update frequency + if (fractional_part > 0) { + // Calculate how often the update should be triggered based on the fractional part + if (count % denominator == 0) { + return 1; + } else { + return 0; + } + } + + // For integer emissions, return the emission directly + return static_cast(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; - - // Define boundary edges - 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; - - std::vector& particles = emitter.data.particles; - for (Particle& particle : particles) + 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; + + // Define boundary edges + 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; + + std::vector& particles = emitter.data.particles; + for (Particle& particle : particles) + { + const Vector2& position = particle.position; + + // Check if particle is within bounds + bool within_bounds = (position.x >= left && position.x <= right && position.y >= top && position.y <= bottom); + if (!within_bounds) { - const Vector2& position = particle.position; - - // Check if particle is within bounds - bool within_bounds = (position.x >= left && position.x <= right && position.y >= top && position.y <= bottom); - if (!within_bounds) - { - if (emitter.data.boundary.reset_on_exit) - { - particle.active = false; - } - else - { - particle.velocity = {0, 0}; - //todo add that particle goes back to boundary - } - } + if (emitter.data.boundary.reset_on_exit) + { + particle.active = false; + } + else + { + particle.velocity = {0, 0}; + if (particle.position.x < left) particle.position.x = left; + else if (particle.position.x > right) particle.position.x = right; + if (particle.position.y < bottom) particle.position.y = bottom; + else if (particle.position.y > top) particle.position.y = top; + } } + } } diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index 1b949e3..8c6a7e7 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -1,3 +1,4 @@ +#include "api/Vector2.h" #include #include #include @@ -80,17 +81,17 @@ TEST_F(ParticlesTest, spawnParticle) { emitter.data.max_angle = 10; particle_system.update(); //check if nothing happend - EXPECT_EQ(emitter.data.particles[0].active, 0); + EXPECT_EQ(emitter.data.particles[0].active, false); emitter.data.emission_rate = 1; //check particle spawnes particle_system.update(); - EXPECT_EQ(emitter.data.particles[0].active, 1); + EXPECT_EQ(emitter.data.particles[0].active, true); particle_system.update(); - EXPECT_EQ(emitter.data.particles[1].active, 1); + EXPECT_EQ(emitter.data.particles[1].active, true); particle_system.update(); - EXPECT_EQ(emitter.data.particles[2].active, 1); + EXPECT_EQ(emitter.data.particles[2].active, true); particle_system.update(); - EXPECT_EQ(emitter.data.particles[3].active, 1); + EXPECT_EQ(emitter.data.particles[3].active, true); for (auto& particle : emitter.data.particles) { // Check velocity range @@ -141,3 +142,46 @@ TEST_F(ParticlesTest, moveParticleVertical) { EXPECT_EQ(emitter.data.particles[0].position.y,a); } } + +TEST_F(ParticlesTest, boundaryParticleReset) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = ComponentManager::get_instance(); + ParticleEmitter & emitter = mgr.get_components_by_id(0).front().get(); + emitter.data.end_lifespan = 100; + emitter.data.boundary.height = 10; + emitter.data.boundary.width = 10; + emitter.data.boundary.reset_on_exit = true; + emitter.data.min_speed = 1; + emitter.data.max_speed = 1; + emitter.data.min_angle = 90; + emitter.data.max_angle = 90; + emitter.data.emission_rate = 1; + for (int a = 0; a < emitter.data.boundary.width/2+1; a++) { + particle_system.update(); + } + EXPECT_EQ(emitter.data.particles[0].active,false); +} + +TEST_F(ParticlesTest, boundaryParticleStop) { + Config::get_instance().physics.gravity = 1; + ComponentManager & mgr = ComponentManager::get_instance(); + ParticleEmitter & emitter = mgr.get_components_by_id(0).front().get(); + emitter.data.end_lifespan = 100; + emitter.data.boundary.height = 10; + emitter.data.boundary.width = 10; + emitter.data.boundary.reset_on_exit = false; + emitter.data.min_speed = 1; + emitter.data.max_speed = 1; + emitter.data.min_angle = 90; + emitter.data.max_angle = 90; + emitter.data.emission_rate = 1; + for (int a = 0; a < emitter.data.boundary.width/2+1; a++) { + particle_system.update(); + } + const double TOLERANCE = 0.01; + EXPECT_NEAR(emitter.data.particles[0].velocity.x, 0, TOLERANCE); + EXPECT_NEAR(emitter.data.particles[0].velocity.y, 0, TOLERANCE); + if(emitter.data.particles[0].velocity.x != 0) EXPECT_NEAR(std::abs(emitter.data.particles[0].position.x), emitter.data.boundary.height / 2, TOLERANCE); + if(emitter.data.particles[0].velocity.y != 0) EXPECT_NEAR(std::abs(emitter.data.particles[0].position.y), emitter.data.boundary.width / 2, TOLERANCE); +} + -- cgit v1.2.3 From 760351abd1a6609a8fe0de1269ac69ea275f4250 Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 18:11:57 +0100 Subject: improved readablity code --- src/crepe/Particle.h | 1 + src/crepe/system/ParticleSystem.cpp | 157 ++++++++++++++---------------------- src/crepe/system/ParticleSystem.h | 61 ++++++++++++-- 3 files changed, 115 insertions(+), 104 deletions(-) diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h index 77c3ea3..88a015d 100644 --- a/src/crepe/Particle.h +++ b/src/crepe/Particle.h @@ -14,6 +14,7 @@ namespace crepe { * to simulate movement and can also be reset or stopped. */ class Particle { + // TODO: add friend particleSsytem and rendersystem. Unit test will fail. public: //! Position of the particle in 2D space. Vector2 position; diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index e6dc670..a0b1e55 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -13,151 +13,112 @@ using namespace crepe; ParticleSystem::ParticleSystem() {} void ParticleSystem::update() { - // Get all emitters ComponentManager & mgr = ComponentManager::get_instance(); - std::vector> emitters - = mgr.get_components_by_type(); + std::vector> emitters = mgr.get_components_by_type(); for (ParticleEmitter & emitter : emitters) { - - // Get transform linked to emitter + // Get transform linked to emitter const Transform& transform = mgr.get_components_by_id(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); + int updates = calculate_update(this->update_count, emitter.data.emission_rate); + for (size_t i = 0; i < updates; i++) { + emit_particle(emitter, transform); } - + // Update all particles - for (size_t j = 0; j < emitter.data.particles.size(); j++) { - if (emitter.data.particles[j].active) { - emitter.data.particles[j].update(); + for (Particle& particle : emitter.data.particles) { + if (particle.active) { + particle.update(); } } - // Check if within boundary - check_bounds(emitter,transform); + // Check if within boundary + check_bounds(emitter, transform); } - update_count++; - if(update_count == MAX_UPDATE_COUNT) update_count = 0; + update_count = (update_count + 1) % MAX_UPDATE_COUNT; } -void ParticleSystem::emit_particle(ParticleEmitter & emitter,const Transform& transform) { +void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform& transform) { constexpr double DEG_TO_RAD = M_PI / 180.0; Vector2 initial_position = emitter.data.position + transform.position; - double min_angle = emitter.data.min_angle; - double max_angle = emitter.data.max_angle; - double random_angle; - if(min_angle == max_angle){ - random_angle = min_angle; - } - else if (min_angle <= max_angle) { - // Standard range (e.g., 10 to 20 degrees) - double angle_offset = max_angle - min_angle; - random_angle = min_angle + static_cast(std::rand() % static_cast(angle_offset)); - } else { - // Wrap-around range (e.g., 350 to 10 degrees) - double angle_offset = (360 - min_angle) + max_angle; - random_angle = min_angle + static_cast(std::rand() % static_cast(angle_offset)); - - // Wrap around to keep random_angle within 0-360 degrees - if (random_angle >= 360) { - random_angle -= 360; - } - } + double random_angle = generate_random_angle(emitter.data.min_angle, emitter.data.max_angle); - // Generate a random speed between min_speed and max_speed - double speed_offset = emitter.data.max_speed - emitter.data.min_speed; - double random_speed = 0.0; - if(emitter.data.max_speed == emitter.data.min_speed) - { - random_speed = emitter.data.min_speed; - } - else { - random_speed = emitter.data.min_speed + static_cast(std::rand() % static_cast(speed_offset)); - } - - // Convert random_angle to radians + double random_speed = generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); double angle_radians = random_angle * DEG_TO_RAD; Vector2 velocity = { - random_speed * std::cos(angle_radians), - random_speed * std::sin(angle_radians) - }; + random_speed * std::cos(angle_radians), + random_speed * std::sin(angle_radians) + }; - - for (size_t i = 0; i < emitter.data.particles.size(); i++) { - if (!emitter.data.particles[i].active) { - emitter.data.particles[i].reset(emitter.data.end_lifespan, initial_position,velocity,random_angle); + for (Particle& particle : emitter.data.particles) { + if (!particle.active) { + particle.reset(emitter.data.end_lifespan, initial_position, velocity, random_angle); break; } } } -int ParticleSystem::calculate_update(int count, double emission) { - - //get interger part of the emission +int ParticleSystem::calculate_update(int count, double emission) const { double integer_part = std::floor(emission); - - // Get the fractional part of the emission double fractional_part = emission - integer_part; - // Convert the fractional part to a denominator value - int denominator = static_cast(1.0 / fractional_part); - - // For emissions like 0.01, 0.1, 0.5, etc., calculate the update frequency if (fractional_part > 0) { - // Calculate how often the update should be triggered based on the fractional part - if (count % denominator == 0) { - return 1; - } else { - return 0; - } + int denominator = static_cast(1.0 / fractional_part); + return (count % denominator == 0) ? 1 : 0; } - - // For integer emissions, return the emission directly + return static_cast(emission); } -void ParticleSystem::check_bounds(ParticleEmitter & emitter,const Transform& transform) -{ +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; - // Define boundary edges - 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; + 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; - std::vector& particles = emitter.data.particles; - for (Particle& particle : particles) - { + 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); - // Check if particle is within bounds - bool within_bounds = (position.x >= left && position.x <= right && position.y >= top && position.y <= bottom); - if (!within_bounds) - { - if (emitter.data.boundary.reset_on_exit) - { + if (!within_bounds) { + if (emitter.data.boundary.reset_on_exit) { particle.active = false; - } - else - { + } else { particle.velocity = {0, 0}; - if (particle.position.x < left) particle.position.x = left; - else if (particle.position.x > right) particle.position.x = right; - if (particle.position.y < bottom) particle.position.y = bottom; - else if (particle.position.y > top) particle.position.y = top; + 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 { + if (min_angle == max_angle) { + return min_angle; + } else if (min_angle < max_angle) { + return min_angle + static_cast(std::rand() % static_cast(max_angle - min_angle)); + } else { + double angle_offset = (360 - min_angle) + max_angle; + double random_angle = min_angle + static_cast(std::rand() % static_cast(angle_offset)); + return (random_angle >= 360) ? random_angle - 360 : random_angle; + } +} + +double ParticleSystem::generate_random_speed(double min_speed, double max_speed) const { + if (min_speed == max_speed) { + return min_speed; + } else { + return min_speed + static_cast(std::rand() % static_cast(max_speed - min_speed)); + } +} diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index 3b9cb54..df89ed9 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -4,21 +4,70 @@ namespace crepe { +/** + * \brief ParticleSystem class responsible for managing particle emission, updates, and bounds checking. + */ class ParticleSystem { public: + /** + * \brief Default constructor. + */ ParticleSystem(); + + /** + * \brief Updates all particle emitters by emitting particles, updating particle states, and checking bounds. + */ void update(); private: - void emit_particle(ParticleEmitter & emitter,const Transform& transform); - int calculate_update(int count, double emission); - void check_bounds(ParticleEmitter & emitter,const Transform& transform); + /** + * \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. + */ + void check_bounds(ParticleEmitter & emitter, const Transform& transform); + + /** + * \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; + + /** + * \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. + //! Counter to count updates to determine how many times emit_particle is called. uint32_t update_count = 0; - //! determines the lowest amount of emissionrate (1000 = 0.001 = 1 particle per 1000 updates). - const uint32_t MAX_UPDATE_COUNT = 100; + //! Determines the lowest amount of emission rate (1000 = 0.001 = 1 particle per 1000 updates). + static constexpr uint32_t MAX_UPDATE_COUNT = 100; }; } // namespace crepe -- cgit v1.2.3 From 08d48aabac838a641ef918da92d9827b214e5da6 Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 18:14:57 +0100 Subject: added comments and code standard --- src/crepe/Particle.cpp | 21 +++++++++++++-------- src/crepe/system/ParticleSystem.cpp | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp index d2b1880..cb2ef0d 100644 --- a/src/crepe/Particle.cpp +++ b/src/crepe/Particle.cpp @@ -5,25 +5,30 @@ using namespace crepe; void Particle::reset(uint32_t lifespan, Vector2 position, Vector2 velocity, double angle) { + // Initialize the particle state this->time_in_life = 0; this->lifespan = lifespan; this->position = position; this->velocity = velocity; - this->active = true; this->angle = angle; + this->active = true; + // Reset force accumulation + this->force_over_time = {0, 0}; } void Particle::update() { - time_in_life++; - if (time_in_life >= lifespan) - { + // Deactivate particle if it has exceeded its lifespan + if (++time_in_life >= lifespan) { this->active = false; return; - } - velocity += force_over_time; - position += velocity; + } + + // Update velocity based on accumulated force and update position + this->velocity += force_over_time; + this->position += velocity; } void Particle::stop_movement() { - this->velocity = {0,0}; + // Reset velocity to halt movement + this->velocity = {0, 0}; } diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index a0b1e55..33027f8 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -38,7 +38,7 @@ void ParticleSystem::update() { check_bounds(emitter, transform); } - update_count = (update_count + 1) % MAX_UPDATE_COUNT; + this->update_count = (this->update_count + 1) % this->MAX_UPDATE_COUNT; } void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform& transform) { -- cgit v1.2.3 From 0006e959643f0150acd230b174287a85d4cb3255 Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 18:30:10 +0100 Subject: added forward declaration and const reference sprite --- src/crepe/api/ParticleEmitter.h | 6 +++--- src/crepe/system/ParticleSystem.cpp | 1 + src/crepe/system/ParticleSystem.h | 4 ++-- src/test/ParticleTest.cpp | 22 ++++++++++++++++------ 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index 2cda1bb..e81ed3b 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -5,9 +5,9 @@ #include "Component.h" #include "Particle.h" -#include "Transform.h" +#include "Sprite.h" -class Sprite; +// class Sprite; namespace crepe { @@ -70,7 +70,7 @@ public: //! collection of particles std::vector particles; //! sprite reference - const Sprite* sprite; + const Sprite& sprite; }; public: /** diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index 33027f8..ad6699b 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -4,6 +4,7 @@ #include "api/ParticleEmitter.h" #include "api/Vector2.h" +#include "api/Transform.h" #include "ComponentManager.h" #include "ParticleSystem.h" diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index df89ed9..023a886 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -1,9 +1,9 @@ #pragma once -#include "../api/ParticleEmitter.h" namespace crepe { - +class ParticleEmitter; +class Transform; /** * \brief ParticleSystem class responsible for managing particle emission, updates, and bounds checking. */ diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index 8c6a7e7..7883eca 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -5,25 +5,36 @@ #include #include #include +#include +#include #include #include - +#include using namespace std; using namespace std::chrono_literals; using namespace crepe; + + class ParticlesTest : public ::testing::Test { protected: - GameObject * game_object; ParticleSystem particle_system; void SetUp() override { ComponentManager & mgr = ComponentManager::get_instance(); std::vector> transforms = mgr.get_components_by_id(0); if (transforms.empty()) { - game_object = new GameObject(0, "", "", Vector2{0, 0}, 0, 0); - game_object->add_component(ParticleEmitter::Data{ + + + GameObject game_object(0, "", "", Vector2{0, 0}, 0, 0); + + Color color(0, 0, 0, 0); + Sprite test_sprite = game_object.add_component( + make_shared("../asset/texture/img.png"), color, + FlipSettings{true, true}); + + game_object.add_component(ParticleEmitter::Data{ .position = {0,0}, .max_particles = 100, .emission_rate = 0, @@ -40,7 +51,7 @@ protected: .offset = Vector2{0,0}, .reset_on_exit = false, }, - .sprite = nullptr, + .sprite = test_sprite, }); } transforms = mgr.get_components_by_id(0); @@ -61,7 +72,6 @@ protected: emitter.data.end_lifespan = 0; emitter.data.force_over_time = Vector2{0, 0}; emitter.data.boundary = {0, 0, Vector2{0, 0}, false}; - emitter.data.sprite = nullptr; for (auto& particle : emitter.data.particles) { particle.active = false; } -- cgit v1.2.3 From 46716724df7697fa789329a62f7a5444ceed5585 Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 19:18:58 +0100 Subject: changed includes --- src/crepe/Particle.cpp | 2 -- src/crepe/api/ParticleEmitter.cpp | 3 ++- src/crepe/api/ParticleEmitter.h | 1 + src/crepe/system/ParticleSystem.h | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp index cb2ef0d..3777db0 100644 --- a/src/crepe/Particle.cpp +++ b/src/crepe/Particle.cpp @@ -1,7 +1,5 @@ #include "Particle.h" -#include "api/Transform.h" - using namespace crepe; void Particle::reset(uint32_t lifespan, Vector2 position, Vector2 velocity, double angle) { diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp index 000bf30..1703026 100644 --- a/src/crepe/api/ParticleEmitter.cpp +++ b/src/crepe/api/ParticleEmitter.cpp @@ -1,5 +1,6 @@ -#include "Particle.h" #include "ParticleEmitter.h" +#include "Particle.h" + using namespace crepe; diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index e81ed3b..037effe 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -6,6 +6,7 @@ #include "Component.h" #include "Particle.h" #include "Sprite.h" +#include "Vector2.h" // class Sprite; diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index 023a886..a74ea79 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -1,5 +1,6 @@ #pragma once +#include namespace crepe { class ParticleEmitter; -- cgit v1.2.3 From 6c8b565b2f312c25ed0c64cab1a75729d705f19d Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 19:22:42 +0100 Subject: make empty example particle --- src/example/particles.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/example/particles.cpp b/src/example/particles.cpp index 01e33cf..407df4e 100644 --- a/src/example/particles.cpp +++ b/src/example/particles.cpp @@ -1,8 +1,5 @@ #include -#include -#include #include -#include #include #include @@ -18,11 +15,14 @@ using namespace crepe; using namespace std; int main(int argc, char * argv[]) { - - GameObject *game_object = new GameObject(0, "Name", "Tag", Vector2{0,0},0,0); - game_object->add_component(ParticleEmitter::ParticleEmitterData{ + GameObject game_object(0, "", "", Vector2{0, 0}, 0, 0); + Color color(0, 0, 0, 0); + Sprite test_sprite = game_object.add_component( + make_shared("../asset/texture/img.png"), color, + FlipSettings{true, true}); + game_object.add_component(ParticleEmitter::Data{ .position = {0,0}, - .max_particles = 0, + .max_particles = 100, .emission_rate = 0, .min_speed = 0, .max_speed = 0, @@ -32,12 +32,12 @@ int main(int argc, char * argv[]) { .end_lifespan = 0, .force_over_time = Vector2{0,0}, .boundary{ - .boundary_width = 0, - .boundary_height = 0, - .boundary_offset = Vector2{0,0}, + .width = 0, + .height = 0, + .offset = Vector2{0,0}, .reset_on_exit = false, }, - .sprite = nullptr, + .sprite = test_sprite, }); return 0; -- cgit v1.2.3 From 608a8783d3bbe5845ff87861369fe1287fcbc792 Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 19:23:09 +0100 Subject: updates makefile --- src/makefile | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/makefile b/src/makefile index b9c44c8..59298fd 100644 --- a/src/makefile +++ b/src/makefile @@ -26,8 +26,8 @@ MAX += crepe/ComponentManager.h MAX += crepe/ComponentManager.hpp MAX += crepe/api/Metadata.cpp MAX += crepe/api/Metadata.h -TODO += crepe/Particle.cpp -TODO += crepe/Particle.h +JARO += crepe/Particle.cpp +JARO += crepe/Particle.h TODO += crepe/Position.h TODO += crepe/api/AssetManager.cpp TODO += crepe/api/AssetManager.h @@ -44,8 +44,8 @@ LOEK += crepe/api/Config.h MAX += crepe/api/GameObject.cpp MAX += crepe/api/GameObject.h MAX += crepe/api/GameObject.hpp -TODO += crepe/api/ParticleEmitter.cpp -TODO += crepe/api/ParticleEmitter.h +JARO += crepe/api/ParticleEmitter.cpp +JARO += crepe/api/ParticleEmitter.h TODO += crepe/api/Vector2.h TODO += crepe/api/Vector2.cpp JARO += crepe/api/Rigidbody.cpp @@ -67,8 +67,8 @@ LOEK += crepe/facade/SoundContext.cpp LOEK += crepe/facade/SoundContext.h TODO += crepe/system/CollisionSystem.cpp TODO += crepe/system/CollisionSystem.h -TODO += crepe/system/ParticleSystem.cpp -TODO += crepe/system/ParticleSystem.h +JARO += crepe/system/ParticleSystem.cpp +JARO += crepe/system/ParticleSystem.h JARO += crepe/system/PhysicsSystem.cpp JARO += crepe/system/PhysicsSystem.h TODO += crepe/system/RenderSystem.cpp @@ -87,13 +87,14 @@ LOEK += example/audio_internal.cpp TODO += example/components_internal.cpp MAX += example/ecs.cpp LOEK += example/log.cpp -TODO += example/particle.cpp +JARO += example/particle.cpp JARO += example/physics.cpp TODO += example/rendering.cpp LOEK += example/script.cpp LOEK += test/audio.cpp LOEK += test/dummy.cpp JARO += test/PhysicsTest.cpp +JARO += test/ParticleTest.cpp FMT := $(JARO) #<<< CHANGE THIS TO YOUR NAME FOR STEP 2 format: FORCE -- cgit v1.2.3 From e42d0877592aa1e88afbe0bc65822cd53a82205d Mon Sep 17 00:00:00 2001 From: JAROWMR Date: Sun, 10 Nov 2024 19:34:15 +0100 Subject: clang format and tidy --- src/crepe/Particle.cpp | 3 +- src/crepe/Particle.h | 5 +- src/crepe/api/ParticleEmitter.cpp | 12 ++--- src/crepe/api/ParticleEmitter.h | 13 ++--- src/crepe/system/ParticleSystem.cpp | 72 +++++++++++++++---------- src/crepe/system/ParticleSystem.h | 4 +- src/example/particles.cpp | 17 +++--- src/makefile | 2 +- src/test/ParticleTest.cpp | 103 ++++++++++++++++++++---------------- 9 files changed, 129 insertions(+), 102 deletions(-) diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp index 3777db0..ab55f37 100644 --- a/src/crepe/Particle.cpp +++ b/src/crepe/Particle.cpp @@ -2,7 +2,8 @@ using namespace crepe; -void Particle::reset(uint32_t lifespan, Vector2 position, Vector2 velocity, double angle) { +void Particle::reset(uint32_t lifespan, Vector2 position, Vector2 velocity, + double angle) { // Initialize the particle state this->time_in_life = 0; this->lifespan = lifespan; diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h index 88a015d..06431bb 100644 --- a/src/crepe/Particle.h +++ b/src/crepe/Particle.h @@ -15,6 +15,7 @@ namespace crepe { */ class Particle { // TODO: add friend particleSsytem and rendersystem. Unit test will fail. + public: //! Position of the particle in 2D space. Vector2 position; @@ -43,7 +44,8 @@ public: * \param velocity The initial velocity of the particle. * \param angle The angle of the particle's trajectory or orientation. */ - void reset(uint32_t lifespan, Vector2 position, Vector2 velocity, double angle); + void reset(uint32_t lifespan, Vector2 position, Vector2 velocity, + double angle); /** * \brief Updates the particle's state. * @@ -58,7 +60,6 @@ public: * movement. */ void stop_movement(); - }; } // namespace crepe diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp index 1703026..e7f298c 100644 --- a/src/crepe/api/ParticleEmitter.cpp +++ b/src/crepe/api/ParticleEmitter.cpp @@ -1,20 +1,18 @@ #include "ParticleEmitter.h" #include "Particle.h" - using namespace crepe; -ParticleEmitter::ParticleEmitter(uint32_t game_object_id, const Data& data) : Component(game_object_id),data(data) { - for (size_t i = 0; i < this->data.max_particles; i++) { - this->data.particles.emplace_back(); - } +ParticleEmitter::ParticleEmitter(uint32_t game_object_id, const Data & data) + : Component(game_object_id), data(data) { + for (size_t i = 0; i < this->data.max_particles; i++) { + this->data.particles.emplace_back(); + } } - ParticleEmitter::~ParticleEmitter() { std::vector::iterator it = this->data.particles.begin(); while (it != this->data.particles.end()) { it = this->data.particles.erase(it); } } - diff --git a/src/crepe/api/ParticleEmitter.h b/src/crepe/api/ParticleEmitter.h index 037effe..83a1588 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -12,7 +12,6 @@ namespace crepe { - /** * \brief Data holder for particle emission parameters. * @@ -28,7 +27,7 @@ public: * This structure specifies the boundary's size and offset, as well as the * behavior of particles upon reaching the boundary limits. */ - struct Boundary{ + struct Boundary { //! boundary width (midpoint is emitter location) double width = 0.0; //! boundary height (midpoint is emitter location) @@ -45,7 +44,7 @@ public: * Contains settings for the emitter’s position, particle speed, angle, lifespan, * boundary, and the sprite used for rendering particles. */ - struct Data{ + struct Data { //! position of the emitter Vector2 position; //! maximum number of particles @@ -71,8 +70,9 @@ public: //! collection of particles std::vector particles; //! sprite reference - const Sprite& sprite; - }; + const Sprite & sprite; + }; + public: /** * \brief Constructs a ParticleEmitter data holder with specified settings. @@ -80,8 +80,9 @@ public: * \param game_object_id Identifier for the game object using this emitter. * \param data Configuration data defining particle properties. */ - ParticleEmitter(uint32_t game_object_id, const Data& data); + ParticleEmitter(uint32_t game_object_id, const Data & data); ~ParticleEmitter(); + public: //! Configuration data for particle emission settings. Data data; diff --git a/src/crepe/system/ParticleSystem.cpp b/src/crepe/system/ParticleSystem.cpp index ad6699b..4a25b47 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -1,10 +1,10 @@ #include -#include #include +#include #include "api/ParticleEmitter.h" -#include "api/Vector2.h" #include "api/Transform.h" +#include "api/Vector2.h" #include "ComponentManager.h" #include "ParticleSystem.h" @@ -16,20 +16,25 @@ ParticleSystem::ParticleSystem() {} void ParticleSystem::update() { // Get all emitters ComponentManager & mgr = ComponentManager::get_instance(); - std::vector> emitters = mgr.get_components_by_type(); + std::vector> emitters + = mgr.get_components_by_type(); for (ParticleEmitter & emitter : emitters) { // Get transform linked to emitter - const Transform& transform = mgr.get_components_by_id(emitter.game_object_id).front().get(); + const Transform & transform + = mgr.get_components_by_id(emitter.game_object_id) + .front() + .get(); // Emit particles based on emission_rate - int updates = calculate_update(this->update_count, emitter.data.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); } // Update all particles - for (Particle& particle : emitter.data.particles) { + for (Particle & particle : emitter.data.particles) { if (particle.active) { particle.update(); } @@ -42,23 +47,25 @@ void ParticleSystem::update() { this->update_count = (this->update_count + 1) % this->MAX_UPDATE_COUNT; } -void ParticleSystem::emit_particle(ParticleEmitter & emitter, const Transform& transform) { +void ParticleSystem::emit_particle(ParticleEmitter & emitter, + const Transform & transform) { constexpr double 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); + double random_angle + = 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 random_speed + = generate_random_speed(emitter.data.min_speed, emitter.data.max_speed); double angle_radians = random_angle * DEG_TO_RAD; - Vector2 velocity = { - random_speed * std::cos(angle_radians), - random_speed * std::sin(angle_radians) - }; + Vector2 velocity = {random_speed * std::cos(angle_radians), + random_speed * std::sin(angle_radians)}; - for (Particle& particle : emitter.data.particles) { + for (Particle & particle : emitter.data.particles) { if (!particle.active) { - particle.reset(emitter.data.end_lifespan, initial_position, velocity, random_angle); + particle.reset(emitter.data.end_lifespan, initial_position, + velocity, random_angle); break; } } @@ -69,15 +76,17 @@ int ParticleSystem::calculate_update(int count, double emission) const { double fractional_part = emission - integer_part; if (fractional_part > 0) { - int denominator = static_cast(1.0 / fractional_part); - return (count % denominator == 0) ? 1 : 0; + int denominator = static_cast(1.0 / fractional_part); + return (count % denominator == 0) ? 1 : 0; } return static_cast(emission); } -void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform& transform) { - Vector2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position; +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; @@ -86,9 +95,10 @@ void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform& tr 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); + 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); if (!within_bounds) { if (emitter.data.boundary.reset_on_exit) { @@ -104,22 +114,30 @@ void ParticleSystem::check_bounds(ParticleEmitter & emitter, const Transform& tr } } -double ParticleSystem::generate_random_angle(double min_angle, double max_angle) const { +double ParticleSystem::generate_random_angle(double min_angle, + double max_angle) const { if (min_angle == max_angle) { return min_angle; } else if (min_angle < max_angle) { - return min_angle + static_cast(std::rand() % static_cast(max_angle - min_angle)); + return min_angle + + static_cast(std::rand() + % static_cast(max_angle - min_angle)); } else { double angle_offset = (360 - min_angle) + max_angle; - double random_angle = min_angle + static_cast(std::rand() % static_cast(angle_offset)); + double random_angle = min_angle + + static_cast( + std::rand() % static_cast(angle_offset)); return (random_angle >= 360) ? random_angle - 360 : random_angle; } } -double ParticleSystem::generate_random_speed(double min_speed, double max_speed) const { +double ParticleSystem::generate_random_speed(double min_speed, + double max_speed) const { if (min_speed == max_speed) { return min_speed; } else { - return min_speed + static_cast(std::rand() % static_cast(max_speed - min_speed)); + return min_speed + + static_cast(std::rand() + % static_cast(max_speed - min_speed)); } } diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index a74ea79..3155df1 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -27,7 +27,7 @@ private: * \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); + 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. @@ -44,7 +44,7 @@ private: * \param emitter Reference to the ParticleEmitter. * \param transform Const reference to the Transform component associated with the emitter. */ - void check_bounds(ParticleEmitter & emitter, const Transform& transform); + void check_bounds(ParticleEmitter & emitter, const Transform & transform); /** * \brief Generates a random angle for particle emission within the specified range. diff --git a/src/example/particles.cpp b/src/example/particles.cpp index 407df4e..6eab046 100644 --- a/src/example/particles.cpp +++ b/src/example/particles.cpp @@ -2,14 +2,13 @@ #include #include +#include #include -#include -#include #include +#include #include #include -#include - +#include using namespace crepe; using namespace std; @@ -18,10 +17,10 @@ int main(int argc, char * argv[]) { GameObject game_object(0, "", "", Vector2{0, 0}, 0, 0); Color color(0, 0, 0, 0); Sprite test_sprite = game_object.add_component( - make_shared("../asset/texture/img.png"), color, - FlipSettings{true, true}); + make_shared("../asset/texture/img.png"), color, + FlipSettings{true, true}); game_object.add_component(ParticleEmitter::Data{ - .position = {0,0}, + .position = {0, 0}, .max_particles = 100, .emission_rate = 0, .min_speed = 0, @@ -30,11 +29,11 @@ int main(int argc, char * argv[]) { .max_angle = 0, .begin_lifespan = 0, .end_lifespan = 0, - .force_over_time = Vector2{0,0}, + .force_over_time = Vector2{0, 0}, .boundary{ .width = 0, .height = 0, - .offset = Vector2{0,0}, + .offset = Vector2{0, 0}, .reset_on_exit = false, }, .sprite = test_sprite, diff --git a/src/makefile b/src/makefile index 59298fd..356179e 100644 --- a/src/makefile +++ b/src/makefile @@ -87,7 +87,7 @@ LOEK += example/audio_internal.cpp TODO += example/components_internal.cpp MAX += example/ecs.cpp LOEK += example/log.cpp -JARO += example/particle.cpp +JARO += example/particles.cpp JARO += example/physics.cpp TODO += example/rendering.cpp LOEK += example/script.cpp diff --git a/src/test/ParticleTest.cpp b/src/test/ParticleTest.cpp index 7883eca..6fe3133 100644 --- a/src/test/ParticleTest.cpp +++ b/src/test/ParticleTest.cpp @@ -1,22 +1,20 @@ #include "api/Vector2.h" -#include #include +#include #include #include +#include #include +#include #include -#include -#include #include #include -#include +#include using namespace std; using namespace std::chrono_literals; using namespace crepe; - - class ParticlesTest : public ::testing::Test { protected: ParticleSystem particle_system; @@ -25,17 +23,16 @@ protected: std::vector> transforms = mgr.get_components_by_id(0); if (transforms.empty()) { - - + GameObject game_object(0, "", "", Vector2{0, 0}, 0, 0); Color color(0, 0, 0, 0); Sprite test_sprite = game_object.add_component( - make_shared("../asset/texture/img.png"), color, - FlipSettings{true, true}); + make_shared("../asset/texture/img.png"), color, + FlipSettings{true, true}); game_object.add_component(ParticleEmitter::Data{ - .position = {0,0}, + .position = {0, 0}, .max_particles = 100, .emission_rate = 0, .min_speed = 0, @@ -44,11 +41,11 @@ protected: .max_angle = 0, .begin_lifespan = 0, .end_lifespan = 0, - .force_over_time = Vector2{0,0}, + .force_over_time = Vector2{0, 0}, .boundary{ .width = 0, .height = 0, - .offset = Vector2{0,0}, + .offset = Vector2{0, 0}, .reset_on_exit = false, }, .sprite = test_sprite, @@ -72,7 +69,7 @@ protected: emitter.data.end_lifespan = 0; emitter.data.force_over_time = Vector2{0, 0}; emitter.data.boundary = {0, 0, Vector2{0, 0}, false}; - for (auto& particle : emitter.data.particles) { + for (auto & particle : emitter.data.particles) { particle.active = false; } } @@ -81,7 +78,8 @@ protected: TEST_F(ParticlesTest, spawnParticle) { Config::get_instance().physics.gravity = 1; ComponentManager & mgr = ComponentManager::get_instance(); - ParticleEmitter & emitter = mgr.get_components_by_id(0).front().get(); + ParticleEmitter & emitter + = mgr.get_components_by_id(0).front().get(); emitter.data.end_lifespan = 5; emitter.data.boundary.height = 100; emitter.data.boundary.width = 100; @@ -89,7 +87,7 @@ TEST_F(ParticlesTest, spawnParticle) { emitter.data.max_angle = 0.1; emitter.data.max_speed = 10; emitter.data.max_angle = 10; - particle_system.update(); + particle_system.update(); //check if nothing happend EXPECT_EQ(emitter.data.particles[0].active, false); emitter.data.emission_rate = 1; @@ -103,24 +101,30 @@ TEST_F(ParticlesTest, spawnParticle) { particle_system.update(); EXPECT_EQ(emitter.data.particles[3].active, true); - for (auto& particle : emitter.data.particles) { - // Check velocity range - EXPECT_GE(particle.velocity.x, emitter.data.min_speed); // Speed should be greater than or equal to min_speed - EXPECT_LE(particle.velocity.x, emitter.data.max_speed); // Speed should be less than or equal to max_speed - EXPECT_GE(particle.velocity.y, emitter.data.min_speed); // Speed should be greater than or equal to min_speed - EXPECT_LE(particle.velocity.y, emitter.data.max_speed); // Speed should be less than or equal to max_speed - - // Check angle range - EXPECT_GE(particle.angle, emitter.data.min_angle); // Angle should be greater than or equal to min_angle - EXPECT_LE(particle.angle, emitter.data.max_angle); // Angle should be less than or equal to max_angle - } - + for (auto & particle : emitter.data.particles) { + // Check velocity range + EXPECT_GE(particle.velocity.x, emitter.data.min_speed); + // Speed should be greater than or equal to min_speed + EXPECT_LE(particle.velocity.x, emitter.data.max_speed); + // Speed should be less than or equal to max_speed + EXPECT_GE(particle.velocity.y, emitter.data.min_speed); + // Speed should be greater than or equal to min_speed + EXPECT_LE(particle.velocity.y, emitter.data.max_speed); + // Speed should be less than or equal to max_speed + + // Check angle range + EXPECT_GE(particle.angle, emitter.data.min_angle); + // Angle should be greater than or equal to min_angle + EXPECT_LE(particle.angle, emitter.data.max_angle); + // Angle should be less than or equal to max_angle + } } TEST_F(ParticlesTest, moveParticleHorizontal) { Config::get_instance().physics.gravity = 1; ComponentManager & mgr = ComponentManager::get_instance(); - ParticleEmitter & emitter = mgr.get_components_by_id(0).front().get(); + ParticleEmitter & emitter + = mgr.get_components_by_id(0).front().get(); emitter.data.end_lifespan = 100; emitter.data.boundary.height = 100; emitter.data.boundary.width = 100; @@ -128,17 +132,17 @@ TEST_F(ParticlesTest, moveParticleHorizontal) { emitter.data.max_speed = 1; emitter.data.max_angle = 0; emitter.data.emission_rate = 1; - for (int a = 1; a < emitter.data.boundary.width/2; a++) { - particle_system.update(); - EXPECT_EQ(emitter.data.particles[0].position.x,a); + for (int a = 1; a < emitter.data.boundary.width / 2; a++) { + particle_system.update(); + EXPECT_EQ(emitter.data.particles[0].position.x, a); } } - TEST_F(ParticlesTest, moveParticleVertical) { Config::get_instance().physics.gravity = 1; ComponentManager & mgr = ComponentManager::get_instance(); - ParticleEmitter & emitter = mgr.get_components_by_id(0).front().get(); + ParticleEmitter & emitter + = mgr.get_components_by_id(0).front().get(); emitter.data.end_lifespan = 100; emitter.data.boundary.height = 100; emitter.data.boundary.width = 100; @@ -147,16 +151,17 @@ TEST_F(ParticlesTest, moveParticleVertical) { emitter.data.min_angle = 90; emitter.data.max_angle = 90; emitter.data.emission_rate = 1; - for (int a = 1; a < emitter.data.boundary.width/2; a++) { - particle_system.update(); - EXPECT_EQ(emitter.data.particles[0].position.y,a); + for (int a = 1; a < emitter.data.boundary.width / 2; a++) { + particle_system.update(); + EXPECT_EQ(emitter.data.particles[0].position.y, a); } } TEST_F(ParticlesTest, boundaryParticleReset) { Config::get_instance().physics.gravity = 1; ComponentManager & mgr = ComponentManager::get_instance(); - ParticleEmitter & emitter = mgr.get_components_by_id(0).front().get(); + ParticleEmitter & emitter + = mgr.get_components_by_id(0).front().get(); emitter.data.end_lifespan = 100; emitter.data.boundary.height = 10; emitter.data.boundary.width = 10; @@ -166,16 +171,17 @@ TEST_F(ParticlesTest, boundaryParticleReset) { emitter.data.min_angle = 90; emitter.data.max_angle = 90; emitter.data.emission_rate = 1; - for (int a = 0; a < emitter.data.boundary.width/2+1; a++) { - particle_system.update(); + for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) { + particle_system.update(); } - EXPECT_EQ(emitter.data.particles[0].active,false); + EXPECT_EQ(emitter.data.particles[0].active, false); } TEST_F(ParticlesTest, boundaryParticleStop) { Config::get_instance().physics.gravity = 1; ComponentManager & mgr = ComponentManager::get_instance(); - ParticleEmitter & emitter = mgr.get_components_by_id(0).front().get(); + ParticleEmitter & emitter + = mgr.get_components_by_id(0).front().get(); emitter.data.end_lifespan = 100; emitter.data.boundary.height = 10; emitter.data.boundary.width = 10; @@ -185,13 +191,16 @@ TEST_F(ParticlesTest, boundaryParticleStop) { emitter.data.min_angle = 90; emitter.data.max_angle = 90; emitter.data.emission_rate = 1; - for (int a = 0; a < emitter.data.boundary.width/2+1; a++) { - particle_system.update(); + for (int a = 0; a < emitter.data.boundary.width / 2 + 1; a++) { + particle_system.update(); } const double TOLERANCE = 0.01; EXPECT_NEAR(emitter.data.particles[0].velocity.x, 0, TOLERANCE); EXPECT_NEAR(emitter.data.particles[0].velocity.y, 0, TOLERANCE); - if(emitter.data.particles[0].velocity.x != 0) EXPECT_NEAR(std::abs(emitter.data.particles[0].position.x), emitter.data.boundary.height / 2, TOLERANCE); - if(emitter.data.particles[0].velocity.y != 0) EXPECT_NEAR(std::abs(emitter.data.particles[0].position.y), emitter.data.boundary.width / 2, TOLERANCE); + if (emitter.data.particles[0].velocity.x != 0) + EXPECT_NEAR(std::abs(emitter.data.particles[0].position.x), + emitter.data.boundary.height / 2, TOLERANCE); + if (emitter.data.particles[0].velocity.y != 0) + EXPECT_NEAR(std::abs(emitter.data.particles[0].position.y), + emitter.data.boundary.width / 2, TOLERANCE); } - -- cgit v1.2.3