diff --git a/CMakeLists.txt b/CMakeLists.txt index 104321b..d4712ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,19 @@ target_link_libraries(assets_test ${GAME_LIBS} ) +add_executable(particle_test + particle_test.c +) +target_include_directories(particle_test + PRIVATE + ${CMAKE_CURRENT_LIST_DIR} +) +target_compile_options(particle_test PRIVATE -fsanitize=address -gdwarf-4) +target_link_options(particle_test PRIVATE -fsanitize=address -gdwarf-4) +target_link_libraries(particle_test + ${GAME_LIBS} +) + if (BUILD_TESTING) find_package(cmocka 1.1.0 REQUIRED) add_subdirectory(tests) diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index e533b2f..2b2b221 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(lib_engine STATIC rres.c mempool.c entManager.c + particle_sys.c ${LIBZSTD_DIR}/lib/libzstd.a ) target_include_directories(lib_engine diff --git a/engine/engine.h b/engine/engine.h index 0c937b4..b9f3d3c 100644 --- a/engine/engine.h +++ b/engine/engine.h @@ -4,6 +4,7 @@ #include "collisions.h" #include "sc/array/sc_array.h" #include "assets.h" +#include "particle_sys.h" typedef struct Scene Scene_t; @@ -21,6 +22,7 @@ typedef struct GameEngine { unsigned int curr_scene; Assets_t assets; SFXList_t sfx_list; + ParticleSystem_t part_sys; // Maintain own queue to handle key presses struct sc_queue_32 key_buffer; } GameEngine_t; diff --git a/engine/particle_sys.c b/engine/particle_sys.c index 3e05d73..b670de4 100644 --- a/engine/particle_sys.c +++ b/engine/particle_sys.c @@ -1,30 +1,124 @@ #include "particle_sys.h" - #include +#include +#include + +// TEMPORARY VARIABLE: NEED TO FIND A WAY TO DEAL WITH THIS +#define DELTA_T 0.017 void init_particle_system(ParticleSystem_t* system) { memset(system, 0, sizeof(ParticleSystem_t)); sc_queue_init(&system->free_list); - for ( uint32_t i = 0; i < MAX_PARTICLE_EMITTER; ++i) + for ( uint32_t i = 1; i <= MAX_PARTICLE_EMITTER; ++i) { sc_queue_add_last(&system->free_list, i); } + system->tail_idx = 0; } -void add_particle_emitter(ParticleSystem_t* system, ParticleEmitter_t* emitter) +void add_particle_emitter(ParticleSystem_t* system, const ParticleEmitter_t* in_emitter) { if (sc_queue_empty(&system->free_list)) return; uint32_t idx = sc_queue_del_first(&system->free_list); system->emitter_list[system->tail_idx].next = idx; system->tail_idx = idx; system->emitter_list[idx].next = 0; - system->emitters[idx] = *emitter; + system->emitters[idx] = *in_emitter; + system->emitters[idx].active = true; + if (system->emitters[idx].n_particles > MAX_PARTICLES) + { + system->emitters[idx].n_particles = MAX_PARTICLES; + } + ParticleEmitter_t* emitter = system->emitters + idx; + // Generate particles based on type + for (uint32_t i = 0; i < emitter->n_particles; ++i) + { + uint32_t lifetime = (emitter->config.particle_lifetime[1] - emitter->config.particle_lifetime[0]); + emitter->particles[i].timer = emitter->config.particle_lifetime[0]; + emitter->particles[i].timer += rand() % lifetime; + emitter->particles[i].alive = true; + + float angle = emitter->config.launch_range[1] - emitter->config.launch_range[0]; + angle *= (float)rand() / (float)RAND_MAX; + angle += emitter->config.launch_range[0]; + if(angle > 360) angle -= 360; + if(angle < -360) angle += 360; + angle *= PI / 180; + + float speed = emitter->config.speed_range[1] - emitter->config.speed_range[0]; + speed *= (float)rand() / (float)RAND_MAX; + speed += emitter->config.speed_range[0]; + + emitter->particles[i].velocity.x = speed * cos(angle); + emitter->particles[i].velocity.y = speed * sin(angle); + emitter->particles[i].position = emitter->position; + } } void update_particle_system(ParticleSystem_t* system) { + uint32_t emitter_idx = system->emitter_list[0].next; + uint32_t last_idx = 0; + + while (emitter_idx != 0) + { + uint32_t next_idx = system->emitter_list[emitter_idx].next; + ParticleEmitter_t* emitter = system->emitters + emitter_idx; + uint32_t inactive_count = 0; + for (uint32_t i = 0; i < emitter->n_particles; ++i) + { + if (emitter->particles[i].alive) + { + if (emitter->config.update_func != NULL) + { + emitter->config.update_func(emitter->particles + i, emitter->config.user_data); + } + + // Lifetime update + if (emitter->particles[i].timer > 0) emitter->particles[i].timer--; + if (emitter->particles[i].timer == 0) + { + emitter->particles[i].alive = false; + inactive_count++; + } + } + else + { + inactive_count++; + } + } + if (inactive_count == emitter->n_particles) + { + emitter->active = false; + system->emitter_list[last_idx].next = system->emitter_list[emitter_idx].next; + system->emitter_list[emitter_idx].next = 0; + if (system->tail_idx == emitter_idx) + { + system->tail_idx = last_idx; + } + sc_queue_add_last(&system->free_list, emitter_idx); + emitter_idx = last_idx; + } + last_idx = emitter_idx; + emitter_idx = next_idx; + } } void draw_particle_system(ParticleSystem_t* system) { + uint32_t emitter_idx = system->emitter_list[0].next; + + while (emitter_idx != 0) + { + ParticleEmitter_t* emitter = system->emitters + emitter_idx; + + for (uint32_t i = 0; i < emitter->n_particles; ++i) + { + if (emitter->particles[i].alive) + { + DrawCircleV(emitter->particles[i].position, 5, BLACK); + } + } + emitter_idx = system->emitter_list[emitter_idx].next; + } } void deinit_particle_system(ParticleSystem_t* system) { diff --git a/engine/particle_sys.h b/engine/particle_sys.h index 2f3b987..1e5d1ee 100644 --- a/engine/particle_sys.h +++ b/engine/particle_sys.h @@ -18,24 +18,27 @@ typedef struct Particle Vector2 velocity; Vector2 accleration; float rotation; - bool alive; + float size; uint32_t timer; + bool alive; }Particle_t; +typedef void (*particle_update_func_t)(Particle_t* part, void* user_data); + typedef struct EmitterConfig { - Vector2 launch_range; - Vector2 speed_range; - Vector2 position; - uint32_t particle_lifetime; - Vector2 gravity; - Vector2 friction_coeff; + float launch_range[2]; + float speed_range[2]; + uint32_t particle_lifetime[2]; PartEmitterType_t type; + void* user_data; + particle_update_func_t update_func; }EmitterConfig_t; typedef struct ParticleEmitter { - EmitterConfig_t* config; + EmitterConfig_t config; + Vector2 position; Particle_t particles[MAX_PARTICLES]; uint32_t n_particles; uint32_t timer; @@ -51,8 +54,7 @@ typedef struct IndexList typedef struct ParticleSystem { - EmitterConfig_t emitter_configs[MAX_PARTICLE_CONF]; - ParticleEmitter_t emitters[MAX_PARTICLE_EMITTER]; + ParticleEmitter_t emitters[MAX_PARTICLE_EMITTER + 1]; IndexList_t emitter_list[MAX_PARTICLE_EMITTER + 1]; struct sc_queue_64 free_list; uint32_t tail_idx; @@ -61,7 +63,7 @@ typedef struct ParticleSystem }ParticleSystem_t; void init_particle_system(ParticleSystem_t* system); -void add_particle_emitter(ParticleSystem_t* system, ParticleEmitter_t* emitter); +void add_particle_emitter(ParticleSystem_t* system, const ParticleEmitter_t* in_emitter); void update_particle_system(ParticleSystem_t* system); void draw_particle_system(ParticleSystem_t* system); void deinit_particle_system(ParticleSystem_t* system); diff --git a/particle_test.c b/particle_test.c index 3b8ca9a..f53daac 100644 --- a/particle_test.c +++ b/particle_test.c @@ -2,16 +2,80 @@ #include #include #include "raylib.h" +#include "raymath.h" +#include "constants.h" +static const Vector2 GRAVITY = {0, GRAV_ACCEL}; +void simple_particle_system_update(Particle_t* part, void* user_data) +{ + + float delta_time = DELTA_T; // TODO: Will need to think about delta time handling + part->velocity = + Vector2Add( + part->velocity, + Vector2Scale(GRAVITY, delta_time) + ); + + float mag = Vector2Length(part->velocity); + part->velocity = Vector2Scale( + Vector2Normalize(part->velocity), + (mag > PLAYER_MAX_SPEED)? PLAYER_MAX_SPEED:mag + ); + // 3 dp precision + if (fabs(part->velocity.x) < 1e-3) part->velocity.x = 0; + if (fabs(part->velocity.y) < 1e-3) part->velocity.y = 0; + + part->position = Vector2Add( + part->position, + Vector2Scale(part->velocity, delta_time) + ); + + // Level boundary collision + { + + if( + part->position.x + part->size < 0 || part->position.x - part->size > 1280 + || part->position.y + part->size < 0 || part->position.y - part->size > 640 + ) + { + part->timer = 0; + } + } +} int main(void) { InitWindow(1280, 640, "raylib"); SetTargetFPS(60); - static ParticleSystem_t part_sys = {0 + static ParticleSystem_t part_sys = {0}; + + init_particle_system(&part_sys); + + EmitterConfig_t conf ={ + .launch_range = {0, 360}, + .speed_range = {400, 2000}, + .particle_lifetime = {30, 110}, + .update_func = &simple_particle_system_update }; + ParticleEmitter_t emitter = { + .config = conf, + .n_particles = MAX_PARTICLES, + .one_shot = true, + }; + + bool key_press = false; while(!WindowShouldClose()) { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + key_press = true; + } + else if (key_press && IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) + { + emitter.position = GetMousePosition(); + add_particle_emitter(&part_sys, &emitter); + key_press = false; + } update_particle_system(&part_sys); BeginDrawing();