diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7850e6d..8d30b7c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,7 @@ SET(SRC_LIST libsuperderpy.c mainloop.c maths.c + particle.c shader.c timeline.c tween.c diff --git a/src/libsuperderpy.h b/src/libsuperderpy.h index fa367a1..68c3d6e 100644 --- a/src/libsuperderpy.h +++ b/src/libsuperderpy.h @@ -64,6 +64,7 @@ struct GamestateResources; #include "gamestate.h" #include "mainloop.h" #include "maths.h" +#include "particle.h" #include "shader.h" #include "timeline.h" #include "tween.h" @@ -74,8 +75,6 @@ struct GamestateResources; #define LIBSUPERDERPY_BITMAP_HASHMAP_BUCKETS 16 -struct Gamestate; - struct Viewport { int width; /*!< Width of the drawing canvas. */ int height; /*!< Height of the drawing canvas. */ diff --git a/src/particle.c b/src/particle.c new file mode 100644 index 0000000..0d84f87 --- /dev/null +++ b/src/particle.c @@ -0,0 +1,221 @@ +/*! \file particle.c + * \brief Particle engine. + */ +/* + * Copyright (c) Sebastian Krzyszkowiak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "internal.h" + +// TODO: think about pre-allocating particle data + +struct GravityParticleData* GravityParticleData(double dx, double dy, double gravity, double friction) { + struct GravityParticleData* data = calloc(1, sizeof(struct GravityParticleData)); + data->dx = dx; + data->dy = dy; + data->gravity = gravity; + data->friction = friction; + return data; +} + +bool GravityParticle(struct Game* game, struct ParticleState* particle, double delta, void* d) { + struct GravityParticleData* data = d; + if (!particle) { + free(data); + return false; + } + data->dx *= (1.0 - (data->friction * delta / (1 / 60.0))); + data->dy += data->gravity * delta / (1 / 60.0); + particle->x += data->dx * delta / (1 / 60.0); + particle->y += data->dy * delta / (1 / 60.0); + return true; +} + +struct LinearParticleData* LinearParticleData(double dx, double dy) { + struct LinearParticleData* data = calloc(1, sizeof(struct LinearParticleData)); + data->dx = dx; + data->dy = dy; + return data; +} + +bool LinearParticle(struct Game* game, struct ParticleState* particle, double delta, void* d) { + struct LinearParticleData* data = d; + if (!particle) { + free(data); + return false; + } + particle->x += data->dx * delta / (1 / 60.0); + particle->y += data->dy * delta / (1 / 60.0); + return true; +} + +struct FaderParticleData* FaderParticleData(double delay, double speed, ParticleFunc* func, void* d, void (*destructor)(void*)) { + struct FaderParticleData* data = calloc(1, sizeof(struct FaderParticleData)); + data->delay = delay; + data->data = d; + data->destructor = destructor; + data->fade = 0.0; + data->func = func; + data->speed = speed; + data->time = 0.0; + return data; +} + +void FaderParticleDestructor(void* data) { + struct FaderParticleData* d = data; + if (d->destructor) { + d->destructor(d->data); + } + free(d); +} + +bool FaderParticle(struct Game* game, struct ParticleState* particle, double delta, void* d) { + struct FaderParticleData* data = d; + + if (!particle) { + FaderParticleDestructor(data); + return false; + } + + data->time += delta; + + float r, g, b, a; + al_unmap_rgba_f(particle->tint, &r, &g, &b, &a); + r /= 1.0 - data->fade; + g /= 1.0 - data->fade; + b /= 1.0 - data->fade; + a /= 1.0 - data->fade; + particle->tint = al_map_rgba_f(r, g, b, a); + + if (!data->func(game, particle, delta, data->data)) { + data->fade = 1.0; + } + if (data->time > data->delay) { + data->fade += data->speed * delta / (1 / 60.0); + } + + r *= 1.0 - data->fade; + g *= 1.0 - data->fade; + b *= 1.0 - data->fade; + a *= 1.0 - data->fade; + particle->tint = al_map_rgba_f(r, g, b, a); + + if (data->fade >= 1.0) { + FaderParticleDestructor(data); + return false; + } + + return true; +} + +struct ParticleState SpawnParticleIn(float x, float y) { + return (struct ParticleState){.x = x, .y = y, .scaleX = 1.0, .scaleY = 1.0, .angle = 0.0, .tint = al_map_rgba(255, 255, 255, 255)}; +} + +struct ParticleState SpawnParticleBetween(float x1, float y1, float x2, float y2) { + float x = x1 + (x2 - x1) * rand() / RAND_MAX, y = y1 + (y2 - y1) * rand() / RAND_MAX; + return SpawnParticleIn(x, y); +} + +struct ParticleBucket* CreateParticleBucket(struct Game* game, int size, bool growing) { + struct ParticleBucket* bucket = calloc(1, sizeof(struct ParticleBucket)); + bucket->growing = growing; + bucket->size = size; + bucket->particles = calloc(size, sizeof(struct Particle)); + for (int i = 0; i < size; i++) { + bucket->particles[i].character = CreateCharacter(game, NULL); + bucket->particles[i].character->shared = true; + } + return bucket; +} + +void UpdateParticles(struct Game* game, struct ParticleBucket* bucket, double delta) { + int actives = 0; + for (int i = 0; i < bucket->size; i++) { + if (bucket->particles[i].active) { + if (bucket->particles[i].func(game, &bucket->particles[i].state, delta, bucket->particles[i].data)) { + actives++; + + SetCharacterPositionF(game, bucket->particles[i].character, bucket->particles[i].state.x, bucket->particles[i].state.y, bucket->particles[i].state.angle); + AnimateCharacter(game, bucket->particles[i].character, delta, 1.0); + bucket->particles[i].character->scaleX = bucket->particles[i].state.scaleX; + bucket->particles[i].character->scaleY = bucket->particles[i].state.scaleY; + bucket->particles[i].character->tint = bucket->particles[i].state.tint; + } else { + bucket->particles[i].active = false; + bucket->active--; + } + } + if (actives >= bucket->active) { + return; + } + } +} + +void DrawParticles(struct Game* game, struct ParticleBucket* bucket) { + bool was_held = al_is_bitmap_drawing_held(); + al_hold_bitmap_drawing(true); + for (int i = 0; i < bucket->size; i++) { + if (bucket->particles[i].active) { + DrawCharacter(game, bucket->particles[i].character); + } + } + al_hold_bitmap_drawing(was_held); +} + +void EmitParticle(struct Game* game, struct ParticleBucket* bucket, struct Character* archetype, ParticleFunc* func, struct ParticleState state, void* data) { + if (bucket->size == bucket->active) { + if (bucket->growing) { + PrintConsole(game, "ERROR: Growing ParticleBucket is not implemented yet! Increase its size (current: %d)", bucket->size); + } else { + PrintConsole(game, "ERROR: ParticleBucket is full, increase its size (current: %d)", bucket->size); + } + return; + } + while (bucket->particles[bucket->last].active) { + bucket->last++; + if (bucket->last == bucket->size) { + bucket->last = 0; + } + } + + bucket->particles[bucket->last].active = true; + bucket->particles[bucket->last].func = func; + bucket->particles[bucket->last].state = state; + bucket->particles[bucket->last].data = data; + + bucket->particles[bucket->last].character->spritesheets = archetype->spritesheets; + bucket->particles[bucket->last].character->spritesheet = archetype->spritesheet; + // TODO: move more state and avoid SelectSpritesheet; maybe add a function like CopyCharacter? + SelectSpritesheet(game, bucket->particles[bucket->last].character, bucket->particles[bucket->last].character->spritesheet->name); + + bucket->active++; + bucket->last++; + if (bucket->last == bucket->size) { + bucket->last = 0; + } +} + +void DestroyParticleBucket(struct Game* game, struct ParticleBucket* bucket) { + for (int i = 0; i < bucket->size; i++) { + if (bucket->particles[i].active) { + bucket->particles[i].func(game, NULL, 0.0, bucket->particles[i].data); + } + DestroyCharacter(game, bucket->particles[i].character); + } + free(bucket->particles); + free(bucket); +} diff --git a/src/particle.h b/src/particle.h new file mode 100644 index 0000000..f0190ca --- /dev/null +++ b/src/particle.h @@ -0,0 +1,92 @@ +/*! \file particle.h + * \brief Particle engine. + */ +/* + * Copyright (c) Sebastian Krzyszkowiak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSUPERDERPY_PARTICLE_H +#define LIBSUPERDERPY_PARTICLE_H + +#include "libsuperderpy.h" + +struct Particle; + +struct ParticleState { + double x, y; + double scaleX, scaleY; + double angle; + ALLEGRO_COLOR tint; +}; + +typedef bool ParticleFunc(struct Game* game, struct ParticleState* particle, double delta, void* data); + +struct Particle { + struct Character* character; + bool active; + ParticleFunc* func; + struct ParticleState state; + void* data; +}; + +struct ParticleBucket { + int size; + int last; + int active; + bool growing; + struct Particle* particles; +}; + +struct GravityParticleData { + double dx, dy; + double gravity; + double friction; +}; + +struct GravityParticleData* GravityParticleData(double dx, double dy, double gravity, double friction); +bool GravityParticle(struct Game* game, struct ParticleState* particle, double delta, void* d); + +struct LinearParticleData { + double dx, dy; +}; + +struct LinearParticleData* LinearParticleData(double dx, double dy); +bool LinearParticle(struct Game* game, struct ParticleState* particle, double delta, void* d); + +struct FaderParticleData { + ParticleFunc* func; + void* data; + void (*destructor)(void* data); + double delay; + double speed; + double time; + double fade; +}; + +void FaderParticleDestructor(void* data); +struct FaderParticleData* FaderParticleData(double delay, double speed, ParticleFunc* func, void* d, void (*destructor)(void*)); +bool FaderParticle(struct Game* game, struct ParticleState* particle, double delta, void* d); + +struct ParticleState SpawnParticleIn(float x, float y); +struct ParticleState SpawnParticleBetween(float x1, float y1, float x2, float y2); + +struct ParticleBucket* CreateParticleBucket(struct Game* game, int size, bool growing); +void UpdateParticles(struct Game* game, struct ParticleBucket* bucket, double delta); +void DrawParticles(struct Game* game, struct ParticleBucket* bucket); +void EmitParticle(struct Game* game, struct ParticleBucket* bucket, struct Character* archetype, ParticleFunc* func, struct ParticleState state, void* data); +void DestroyParticleBucket(struct Game* game, struct ParticleBucket* bucket); + +#endif diff --git a/src/timeline.h b/src/timeline.h index f7b62a8..9f93f0e 100644 --- a/src/timeline.h +++ b/src/timeline.h @@ -25,7 +25,7 @@ struct TM_Action; typedef bool TM_ActionCallback(struct Game*, struct GamestateResources*, struct TM_Action*); -#define TM_NUMARGS(...) (sizeof((void* []){__VA_ARGS__}) / sizeof(void*)) +#define TM_NUMARGS(...) (sizeof((void*[]){__VA_ARGS__}) / sizeof(void*)) /*! \brief State of the TM_Action. */ enum TM_ActionState {