diff options
author | heavydemon21 <nielsstunnebrink1@gmail.com> | 2024-11-11 19:48:18 +0100 |
---|---|---|
committer | heavydemon21 <nielsstunnebrink1@gmail.com> | 2024-11-11 19:48:18 +0100 |
commit | 141c4831be82654a692d03fdb0fbf87fa27dea88 (patch) | |
tree | 02bc3294cf44e3f962595d05bbdfcc0484d0d9f3 /src/crepe | |
parent | ca878336da5cdd120a929e6ce84f565ce5365248 (diff) | |
parent | e42d0877592aa1e88afbe0bc65822cd53a82205d (diff) |
Merge branch 'jaro/particle-system-master' into niels/RenderingParticle
Diffstat (limited to 'src/crepe')
-rw-r--r-- | src/crepe/Particle.cpp | 29 | ||||
-rw-r--r-- | src/crepe/Particle.h | 65 | ||||
-rw-r--r-- | src/crepe/api/ParticleEmitter.cpp | 34 | ||||
-rw-r--r-- | src/crepe/api/ParticleEmitter.h | 99 | ||||
-rw-r--r-- | src/crepe/api/Vector2.cpp | 6 | ||||
-rw-r--r-- | src/crepe/api/Vector2.h | 10 | ||||
-rw-r--r-- | src/crepe/system/ParticleSystem.cpp | 157 | ||||
-rw-r--r-- | src/crepe/system/ParticleSystem.h | 64 |
8 files changed, 344 insertions, 120 deletions
diff --git a/src/crepe/Particle.cpp b/src/crepe/Particle.cpp index 4810e80..ab55f37 100644 --- a/src/crepe/Particle.cpp +++ b/src/crepe/Particle.cpp @@ -2,19 +2,32 @@ using namespace crepe; -Particle::Particle() { this->active = false; } - -void Particle::reset(float lifespan, Position position, Position velocity) { +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->angle = angle; this->active = true; + // Reset force accumulation + this->force_over_time = {0, 0}; +} + +void Particle::update() { + // Deactivate particle if it has exceeded its lifespan + if (++time_in_life >= lifespan) { + this->active = false; + return; + } + + // Update velocity based on accumulated force and update position + this->velocity += force_over_time; + this->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() { + // Reset velocity to halt movement + this->velocity = {0, 0}; } diff --git a/src/crepe/Particle.h b/src/crepe/Particle.h index 21e691d..06431bb 100644 --- a/src/crepe/Particle.h +++ b/src/crepe/Particle.h @@ -1,22 +1,65 @@ #pragma once -#include "Position.h" +#include <cstdint> + +#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 { + // TODO: add friend particleSsytem and rendersystem. Unit test will fail. + 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; + //! 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(); - void reset(float lifespan, Position position, Position velocity); - void update(float deltaTime); - float time_in_life; + Particle() = default; + /** + * \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(); }; } // namespace crepe diff --git a/src/crepe/api/ParticleEmitter.cpp b/src/crepe/api/ParticleEmitter.cpp index 3b2e2f2..e7f298c 100644 --- a/src/crepe/api/ParticleEmitter.cpp +++ b/src/crepe/api/ParticleEmitter.cpp @@ -1,36 +1,18 @@ -#include <ctime> -#include <iostream> - -#include "Particle.h" #include "ParticleEmitter.h" +#include "Particle.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<uint32_t>(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 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<Particle>::iterator it = this->particles.begin(); - while (it != this->particles.end()) { - it = this->particles.erase(it); + std::vector<Particle>::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..83a1588 100644 --- a/src/crepe/api/ParticleEmitter.h +++ b/src/crepe/api/ParticleEmitter.h @@ -5,38 +5,87 @@ #include "Component.h" #include "Particle.h" +#include "Sprite.h" +#include "Vector2.h" + +// 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: - 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); + /** + * \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 width = 0.0; + //! boundary height (midpoint is emitter location) + double height = 0.0; + //! boundary offset from particle emitter location + Vector2 offset; + //! reset on exit or stop velocity and set max postion + bool reset_on_exit = false; + }; + + /** + * \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 + 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 + 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 + Boundary boundary; + //! collection of particles + std::vector<Particle> particles; + //! sprite reference + const Sprite & sprite; + }; + +public: + /** + * \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(); - //! 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; - - //! collection of particles - std::vector<Particle> particles; +public: + //! Configuration data for particle emission settings. + Data data; }; } // namespace crepe 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 397b586..4a25b47 100644 --- a/src/crepe/system/ParticleSystem.cpp +++ b/src/crepe/system/ParticleSystem.cpp @@ -1,62 +1,143 @@ #include <cmath> +#include <cstdlib> #include <ctime> -#include "../ComponentManager.h" -#include "../api/ParticleEmitter.h" +#include "api/ParticleEmitter.h" +#include "api/Transform.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<std::reference_wrapper<ParticleEmitter>> emitters = mgr.get_components_by_type<ParticleEmitter>(); - float delta_time = 0.10; + for (ParticleEmitter & emitter : emitters) { - float update_amount = 1 / static_cast<float>(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<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); } - 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 (Particle & particle : emitter.data.particles) { + if (particle.active) { + particle.update(); } } + + // Check if within boundary + check_bounds(emitter, transform); } + + this->update_count = (this->update_count + 1) % this->MAX_UPDATE_COUNT; } -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<uint32_t>(emitter.max_angle + 360 - - emitter.min_angle + 1)))) - % 360); - } else { - random_angle = emitter.min_angle - + (std::rand() - % (static_cast<uint32_t>(emitter.max_angle - - emitter.min_angle + 1))); - } - float angle_in_radians = random_angle * (M_PI / 180.0f); - float random_speed_offset = (static_cast<float>(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); +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_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)}; + + 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) 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); + + 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; + } + } + } +} + +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<double>(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)); + 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<double>(std::rand() + % static_cast<int>(max_speed - min_speed)); + } +} diff --git a/src/crepe/system/ParticleSystem.h b/src/crepe/system/ParticleSystem.h index 3ac1d3f..3155df1 100644 --- a/src/crepe/system/ParticleSystem.h +++ b/src/crepe/system/ParticleSystem.h @@ -1,18 +1,74 @@ #pragma once -#include "../api/ParticleEmitter.h" +#include <cstdint> namespace crepe { - +class ParticleEmitter; +class Transform; +/** + * \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); //emits a new particle + /** + * \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); - float elapsed_time; //elapsed time since the last emission + /** + * \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. + uint32_t update_count = 0; + //! 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 |