2025-11-13 07:15:41 -06:00
|
|
|
#include "pxl8_vfx.h"
|
|
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
#include <stdlib.h>
|
2025-10-17 17:54:33 -05:00
|
|
|
#include <string.h>
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
#define PXL8_RANDF() ((f32)rand() / (f32)RAND_MAX)
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
struct pxl8_particles {
|
|
|
|
|
pxl8_particle* particles;
|
|
|
|
|
u32 alive_count;
|
|
|
|
|
u32 count;
|
|
|
|
|
u32 max_count;
|
|
|
|
|
|
|
|
|
|
f32 x, y;
|
|
|
|
|
f32 spread_x, spread_y;
|
|
|
|
|
|
|
|
|
|
f32 drag;
|
|
|
|
|
f32 gravity_x, gravity_y;
|
|
|
|
|
f32 turbulence;
|
|
|
|
|
|
|
|
|
|
f32 spawn_rate;
|
|
|
|
|
f32 spawn_timer;
|
|
|
|
|
|
|
|
|
|
void (*render_fn)(pxl8_gfx* gfx, pxl8_particle* p, void* userdata);
|
|
|
|
|
void (*spawn_fn)(pxl8_particle* p, void* userdata);
|
|
|
|
|
void (*update_fn)(pxl8_particle* p, f32 dt, void* userdata);
|
|
|
|
|
void* userdata;
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
void pxl8_vfx_plasma(pxl8_gfx* gfx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) {
|
2025-11-28 14:41:35 -06:00
|
|
|
if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return;
|
2025-10-05 16:25:17 -05:00
|
|
|
|
|
|
|
|
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
|
|
|
|
|
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
|
2025-09-28 13:10:29 -05:00
|
|
|
f32 v1 = sinf(x * scale1 + time);
|
|
|
|
|
f32 v2 = sinf(y * scale1 + time * 0.7f);
|
|
|
|
|
f32 v3 = sinf((x + y) * scale2 + time * 1.3f);
|
|
|
|
|
f32 v4 = sinf(sqrtf(x * x + y * y) * 0.05f + time * 0.5f);
|
|
|
|
|
|
|
|
|
|
f32 v = v1 + v2 + v3 + v4;
|
|
|
|
|
f32 normalized = (1.0f + v / 4.0f) * 0.5f;
|
|
|
|
|
if (normalized < 0.0f) normalized = 0.0f;
|
|
|
|
|
if (normalized > 1.0f) normalized = 1.0f;
|
|
|
|
|
u8 color = palette_offset + (u8)(15.0f * normalized);
|
|
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
pxl8_pixel(gfx, x, y, color);
|
2025-09-28 13:10:29 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f32 time) {
|
|
|
|
|
if (!gfx || !bars) return;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
for (u32 i = 0; i < bar_count; i++) {
|
2025-09-28 13:10:29 -05:00
|
|
|
pxl8_raster_bar* bar = &bars[i];
|
2025-11-28 14:41:35 -06:00
|
|
|
if (bar->height <= 1) continue;
|
|
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
f32 y = bar->base_y + bar->amplitude * sinf(time * bar->speed + bar->phase);
|
|
|
|
|
i32 y_int = (i32)y;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
for (i32 dy = 0; dy < bar->height; dy++) {
|
|
|
|
|
f32 position = (f32)dy / (f32)(bar->height - 1);
|
|
|
|
|
f32 distance_from_center = fabsf(position - 0.5f) * 2.0f;
|
|
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
u8 color_idx;
|
2025-10-04 04:13:48 -05:00
|
|
|
if (distance_from_center < 0.3f) {
|
2025-08-13 15:04:49 -05:00
|
|
|
color_idx = bar->fade_color;
|
2025-10-04 04:13:48 -05:00
|
|
|
} else if (distance_from_center < 0.6f) {
|
|
|
|
|
color_idx = bar->fade_color - 1;
|
|
|
|
|
} else if (distance_from_center < 0.8f) {
|
|
|
|
|
color_idx = bar->color;
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
color_idx = bar->color - 1;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
pxl8_rect_fill(gfx, 0, y_int + dy, pxl8_gfx_get_width(gfx), 1, color_idx);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) {
|
2025-11-28 14:41:35 -06:00
|
|
|
if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
f32 cos_a = cosf(angle);
|
|
|
|
|
f32 sin_a = sinf(angle);
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
u8* temp_buffer = (u8*)malloc(pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx));
|
2025-08-13 15:04:49 -05:00
|
|
|
if (!temp_buffer) return;
|
|
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
memcpy(temp_buffer, pxl8_gfx_get_framebuffer_indexed(gfx), pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx));
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
|
|
|
|
|
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
|
2025-08-13 15:04:49 -05:00
|
|
|
f32 dx = x - cx;
|
|
|
|
|
f32 dy = y - cy;
|
|
|
|
|
|
|
|
|
|
f32 src_x = cx + (dx * cos_a - dy * sin_a) / zoom;
|
|
|
|
|
f32 src_y = cy + (dx * sin_a + dy * cos_a) / zoom;
|
|
|
|
|
|
|
|
|
|
i32 sx = (i32)src_x;
|
|
|
|
|
i32 sy = (i32)src_y;
|
|
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
if (sx >= 0 && sx < pxl8_gfx_get_width(gfx) && sy >= 0 && sy < pxl8_gfx_get_height(gfx)) {
|
2025-11-28 14:41:35 -06:00
|
|
|
pxl8_gfx_get_framebuffer_indexed(gfx)[y * pxl8_gfx_get_width(gfx) + x] = temp_buffer[sy * pxl8_gfx_get_width(gfx) + sx];
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
free(temp_buffer);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist) {
|
2025-11-28 14:41:35 -06:00
|
|
|
if (!gfx || !pxl8_gfx_get_framebuffer_indexed(gfx)) return;
|
2025-10-05 16:25:17 -05:00
|
|
|
|
|
|
|
|
f32 cx = pxl8_gfx_get_width(gfx) / 2.0f;
|
|
|
|
|
f32 cy = pxl8_gfx_get_height(gfx) / 2.0f;
|
|
|
|
|
u32 palette_size = pxl8_gfx_get_palette_size(gfx);
|
|
|
|
|
if (palette_size == 0) palette_size = 256;
|
|
|
|
|
|
|
|
|
|
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
|
|
|
|
|
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
|
2025-08-13 15:04:49 -05:00
|
|
|
f32 dx = x - cx;
|
|
|
|
|
f32 dy = y - cy;
|
|
|
|
|
f32 dist = sqrtf(dx * dx + dy * dy);
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
if (dist > 1.0f) {
|
|
|
|
|
f32 angle = atan2f(dy, dx);
|
|
|
|
|
f32 u = angle * 0.159f + twist * sinf(time * 0.5f);
|
|
|
|
|
f32 v = 256.0f / dist + time * speed;
|
2025-10-05 16:25:17 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
u8 tx = (u8)((i32)(u * 256.0f) & 0xFF);
|
|
|
|
|
u8 ty = (u8)((i32)(v * 256.0f) & 0xFF);
|
2025-10-05 16:25:17 -05:00
|
|
|
u8 pattern = tx ^ ty;
|
|
|
|
|
u8 color = (pattern / 8) % palette_size;
|
|
|
|
|
|
|
|
|
|
pxl8_pixel(gfx, x, y, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping) {
|
|
|
|
|
if (!gfx || !height_map) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-05 16:25:17 -05:00
|
|
|
i32 w = pxl8_gfx_get_width(gfx);
|
|
|
|
|
i32 h = pxl8_gfx_get_height(gfx);
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
static f32* prev_height = NULL;
|
|
|
|
|
if (!prev_height) {
|
2025-10-17 17:54:33 -05:00
|
|
|
prev_height = (f32*)calloc(w * h, sizeof(f32));
|
2025-08-13 15:04:49 -05:00
|
|
|
if (!prev_height) return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (drop_x >= 0 && drop_x < w && drop_y >= 0 && drop_y < h) {
|
|
|
|
|
height_map[drop_y * w + drop_x] = 255.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i32 y = 1; y < h - 1; y++) {
|
|
|
|
|
for (i32 x = 1; x < w - 1; x++) {
|
|
|
|
|
i32 idx = y * w + x;
|
|
|
|
|
f32 sum = height_map[idx - w] + height_map[idx + w] +
|
|
|
|
|
height_map[idx - 1] + height_map[idx + 1];
|
|
|
|
|
f32 avg = sum / 2.0f;
|
|
|
|
|
prev_height[idx] = (avg - prev_height[idx]) * damping;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f32* temp = height_map;
|
|
|
|
|
height_map = prev_height;
|
|
|
|
|
prev_height = temp;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_particles* pxl8_particles_create(u32 max_count) {
|
2025-10-17 17:54:33 -05:00
|
|
|
pxl8_particles* particles = calloc(1, sizeof(pxl8_particles));
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!particles) return NULL;
|
2025-09-28 13:10:29 -05:00
|
|
|
|
2025-10-17 17:54:33 -05:00
|
|
|
particles->particles = calloc(max_count, sizeof(pxl8_particle));
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!particles->particles) {
|
2025-10-17 17:54:33 -05:00
|
|
|
free(particles);
|
2025-10-04 04:13:48 -05:00
|
|
|
return NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
particles->max_count = max_count;
|
|
|
|
|
particles->drag = 0.98f;
|
|
|
|
|
particles->gravity_y = 100.0f;
|
|
|
|
|
particles->spawn_rate = 10.0f;
|
|
|
|
|
|
|
|
|
|
return particles;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_particles_destroy(pxl8_particles* particles) {
|
|
|
|
|
if (!particles) return;
|
2025-10-17 17:54:33 -05:00
|
|
|
free(particles->particles);
|
|
|
|
|
free(particles);
|
2025-10-04 04:13:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_particles_clear(pxl8_particles* particles) {
|
|
|
|
|
if (!particles || !particles->particles) return;
|
|
|
|
|
|
|
|
|
|
for (u32 i = 0; i < particles->max_count; i++) {
|
|
|
|
|
particles->particles[i].life = 0;
|
|
|
|
|
particles->particles[i].flags = 0;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
particles->alive_count = 0;
|
|
|
|
|
particles->spawn_timer = 0;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_particles_emit(pxl8_particles* particles, u32 count) {
|
|
|
|
|
if (!particles || !particles->particles) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u32 i = 0; i < count && particles->alive_count < particles->max_count; i++) {
|
|
|
|
|
for (u32 j = 0; j < particles->max_count; j++) {
|
|
|
|
|
if (particles->particles[j].life <= 0) {
|
|
|
|
|
pxl8_particle* p = &particles->particles[j];
|
2025-08-13 15:04:49 -05:00
|
|
|
p->life = 1.0f;
|
|
|
|
|
p->max_life = 1.0f;
|
2025-11-28 14:41:35 -06:00
|
|
|
p->x = particles->x + ((PXL8_RANDF()) - 0.5f) * particles->spread_x;
|
|
|
|
|
p->y = particles->y + ((PXL8_RANDF()) - 0.5f) * particles->spread_y;
|
2025-08-13 15:04:49 -05:00
|
|
|
p->z = 0;
|
|
|
|
|
p->vx = p->vy = p->vz = 0;
|
2025-10-04 04:13:48 -05:00
|
|
|
p->ax = particles->gravity_x;
|
|
|
|
|
p->ay = particles->gravity_y;
|
2025-08-13 15:04:49 -05:00
|
|
|
p->az = 0;
|
|
|
|
|
p->color = p->start_color = p->end_color = 15;
|
|
|
|
|
p->size = 1.0f;
|
|
|
|
|
p->angle = 0;
|
|
|
|
|
p->spin = 0;
|
|
|
|
|
p->flags = 1;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (particles->spawn_fn) {
|
|
|
|
|
particles->spawn_fn(p, particles->userdata);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
particles->alive_count++;
|
2025-08-13 15:04:49 -05:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_particles_render(pxl8_particles* particles, pxl8_gfx* gfx) {
|
|
|
|
|
if (!particles || !particles->particles || !gfx) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u32 i = 0; i < particles->max_count; i++) {
|
|
|
|
|
pxl8_particle* p = &particles->particles[i];
|
2025-08-13 15:04:49 -05:00
|
|
|
if (p->life > 0 && p->flags) {
|
2025-10-04 04:13:48 -05:00
|
|
|
if (particles->render_fn) {
|
|
|
|
|
particles->render_fn(gfx, p, particles->userdata);
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
|
|
|
|
i32 x = (i32)p->x;
|
|
|
|
|
i32 y = (i32)p->y;
|
2025-10-04 04:13:48 -05:00
|
|
|
if (x >= 0 && x < pxl8_gfx_get_width(gfx) && y >= 0 && y < pxl8_gfx_get_height(gfx)) {
|
|
|
|
|
pxl8_pixel(gfx, x, y, p->color);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_particles_update(pxl8_particles* particles, f32 dt) {
|
|
|
|
|
if (!particles || !particles->particles) return;
|
|
|
|
|
|
|
|
|
|
if (particles->spawn_rate > 0.0f) {
|
|
|
|
|
particles->spawn_timer += dt;
|
|
|
|
|
f32 spawn_interval = 1.0f / particles->spawn_rate;
|
|
|
|
|
u32 max_spawns_per_frame = particles->max_count / 10;
|
|
|
|
|
if (max_spawns_per_frame < 1) max_spawns_per_frame = 1;
|
|
|
|
|
u32 spawn_count = 0;
|
|
|
|
|
while (particles->spawn_timer >= spawn_interval && spawn_count < max_spawns_per_frame) {
|
|
|
|
|
pxl8_particles_emit(particles, 1);
|
|
|
|
|
particles->spawn_timer -= spawn_interval;
|
|
|
|
|
spawn_count++;
|
|
|
|
|
}
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u32 i = 0; i < particles->max_count; i++) {
|
|
|
|
|
pxl8_particle* p = &particles->particles[i];
|
2025-08-13 15:04:49 -05:00
|
|
|
if (p->life > 0) {
|
2025-10-04 04:13:48 -05:00
|
|
|
if (particles->update_fn) {
|
|
|
|
|
particles->update_fn(p, dt, particles->userdata);
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
|
|
|
|
p->vx += p->ax * dt;
|
|
|
|
|
p->vy += p->ay * dt;
|
|
|
|
|
p->vz += p->az * dt;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
p->vx *= particles->drag;
|
|
|
|
|
p->vy *= particles->drag;
|
|
|
|
|
p->vz *= particles->drag;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
p->x += p->vx * dt;
|
|
|
|
|
p->y += p->vy * dt;
|
|
|
|
|
p->z += p->vz * dt;
|
|
|
|
|
|
|
|
|
|
p->angle += p->spin * dt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p->life -= dt / p->max_life;
|
|
|
|
|
if (p->life <= 0) {
|
|
|
|
|
p->flags = 0;
|
2025-10-04 04:13:48 -05:00
|
|
|
particles->alive_count--;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_vfx_explosion(pxl8_particles* particles, i32 x, i32 y, u32 color, f32 force) {
|
|
|
|
|
if (!particles) return;
|
2025-09-28 13:10:29 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
particles->x = x;
|
|
|
|
|
particles->y = y;
|
|
|
|
|
particles->spread_x = particles->spread_y = 2.0f;
|
|
|
|
|
particles->gravity_x = 0;
|
|
|
|
|
particles->gravity_y = 200.0f;
|
|
|
|
|
particles->drag = 0.95f;
|
|
|
|
|
particles->update_fn = NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u32 i = 0; i < 50 && i < particles->max_count; i++) {
|
|
|
|
|
pxl8_particle* p = &particles->particles[i];
|
2025-11-28 14:41:35 -06:00
|
|
|
f32 angle = (PXL8_RANDF()) * 6.28f;
|
|
|
|
|
f32 speed = force * (0.5f + (PXL8_RANDF()) * 0.5f);
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
p->x = x;
|
|
|
|
|
p->y = y;
|
|
|
|
|
p->vx = cosf(angle) * speed;
|
|
|
|
|
p->vy = sinf(angle) * speed;
|
|
|
|
|
p->life = 1.0f;
|
2025-11-28 14:41:35 -06:00
|
|
|
p->max_life = 1.0f + (PXL8_RANDF());
|
2025-08-13 15:04:49 -05:00
|
|
|
p->color = color;
|
|
|
|
|
p->flags = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void fire_spawn(pxl8_particle* p, void* userdata) {
|
|
|
|
|
uintptr_t palette_start = (uintptr_t)userdata;
|
|
|
|
|
p->start_color = palette_start + 6 + (rand() % 3);
|
|
|
|
|
p->end_color = palette_start;
|
|
|
|
|
p->color = p->start_color;
|
2025-11-28 14:41:35 -06:00
|
|
|
p->max_life = 1.5f + (PXL8_RANDF()) * 1.5f;
|
|
|
|
|
p->vy = -80.0f - (PXL8_RANDF()) * 120.0f;
|
|
|
|
|
p->vx = ((PXL8_RANDF()) - 0.5f) * 40.0f;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void fire_update(pxl8_particle* p, f32 dt, void* userdata) {
|
|
|
|
|
(void)userdata;
|
|
|
|
|
p->vx += p->ax * dt;
|
|
|
|
|
p->vy += p->ay * dt;
|
|
|
|
|
p->vz += p->az * dt;
|
|
|
|
|
|
|
|
|
|
p->x += p->vx * dt;
|
|
|
|
|
p->y += p->vy * dt;
|
|
|
|
|
p->z += p->vz * dt;
|
|
|
|
|
|
|
|
|
|
f32 life_ratio = 1.0f - (p->life / p->max_life);
|
|
|
|
|
if (p->start_color >= p->end_color) {
|
|
|
|
|
u8 color_range = p->start_color - p->end_color;
|
|
|
|
|
p->color = p->start_color - (u8)(life_ratio * color_range);
|
|
|
|
|
} else {
|
|
|
|
|
u8 color_range = p->end_color - p->start_color;
|
|
|
|
|
p->color = p->start_color + (u8)(life_ratio * color_range);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_vfx_fire(pxl8_particles* particles, i32 x, i32 y, i32 width, u8 palette_start) {
|
|
|
|
|
if (!particles) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
particles->x = x;
|
|
|
|
|
particles->y = y;
|
|
|
|
|
particles->spread_x = width;
|
|
|
|
|
particles->spread_y = 4.0f;
|
|
|
|
|
particles->gravity_x = 0;
|
|
|
|
|
particles->gravity_y = -100.0f;
|
|
|
|
|
particles->drag = 0.97f;
|
|
|
|
|
particles->spawn_rate = 120.0f;
|
|
|
|
|
particles->spawn_fn = fire_spawn;
|
|
|
|
|
particles->update_fn = fire_update;
|
|
|
|
|
particles->userdata = (void*)(uintptr_t)palette_start;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void rain_spawn(pxl8_particle* p, void* userdata) {
|
|
|
|
|
(void)userdata;
|
2025-09-27 11:03:36 -05:00
|
|
|
p->start_color = 27 + (rand() % 3);
|
|
|
|
|
p->end_color = 29;
|
|
|
|
|
p->color = p->start_color;
|
2025-08-13 15:04:49 -05:00
|
|
|
p->max_life = 2.0f;
|
2025-11-28 14:41:35 -06:00
|
|
|
p->vy = 200.0f + (PXL8_RANDF()) * 100.0f;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_vfx_rain(pxl8_particles* particles, i32 width, f32 wind) {
|
|
|
|
|
if (!particles) return;
|
|
|
|
|
|
|
|
|
|
particles->x = width / 2.0f;
|
|
|
|
|
particles->y = -10;
|
|
|
|
|
particles->spread_x = width;
|
|
|
|
|
particles->spread_y = 0;
|
|
|
|
|
particles->gravity_x = wind;
|
|
|
|
|
particles->gravity_y = 300.0f;
|
|
|
|
|
particles->drag = 1.0f;
|
|
|
|
|
particles->spawn_rate = 100.0f;
|
|
|
|
|
particles->spawn_fn = rain_spawn;
|
|
|
|
|
particles->update_fn = NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void smoke_spawn(pxl8_particle* p, void* userdata) {
|
|
|
|
|
uintptr_t base_color = (uintptr_t)userdata;
|
|
|
|
|
p->start_color = base_color;
|
|
|
|
|
p->end_color = base_color + 4;
|
|
|
|
|
p->color = p->start_color;
|
2025-11-28 14:41:35 -06:00
|
|
|
p->max_life = 3.0f + (PXL8_RANDF()) * 2.0f;
|
|
|
|
|
p->vy = -20.0f - (PXL8_RANDF()) * 30.0f;
|
|
|
|
|
p->vx = ((PXL8_RANDF()) - 0.5f) * 20.0f;
|
|
|
|
|
p->size = 1.0f + (PXL8_RANDF()) * 2.0f;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_vfx_smoke(pxl8_particles* particles, i32 x, i32 y, u8 color) {
|
|
|
|
|
if (!particles) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
particles->x = x;
|
|
|
|
|
particles->y = y;
|
|
|
|
|
particles->spread_x = 5.0f;
|
|
|
|
|
particles->spread_y = 5.0f;
|
|
|
|
|
particles->gravity_x = 0;
|
|
|
|
|
particles->gravity_y = -50.0f;
|
|
|
|
|
particles->drag = 0.96f;
|
|
|
|
|
particles->spawn_rate = 20.0f;
|
|
|
|
|
particles->spawn_fn = smoke_spawn;
|
|
|
|
|
particles->update_fn = NULL;
|
|
|
|
|
particles->userdata = (void*)(uintptr_t)color;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void snow_spawn(pxl8_particle* p, void* userdata) {
|
|
|
|
|
(void)userdata;
|
2025-09-28 13:10:29 -05:00
|
|
|
p->start_color = 8 + (rand() % 3);
|
2025-09-27 11:03:36 -05:00
|
|
|
p->end_color = 10;
|
|
|
|
|
p->color = p->start_color;
|
2025-08-13 15:04:49 -05:00
|
|
|
p->max_life = 4.0f;
|
2025-11-28 14:41:35 -06:00
|
|
|
p->vx = ((PXL8_RANDF()) - 0.5f) * 20.0f;
|
|
|
|
|
p->vy = 30.0f + (PXL8_RANDF()) * 20.0f;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_vfx_snow(pxl8_particles* particles, i32 width, f32 wind) {
|
|
|
|
|
if (!particles) return;
|
|
|
|
|
|
|
|
|
|
particles->x = width / 2.0f;
|
|
|
|
|
particles->y = -10;
|
|
|
|
|
particles->spread_x = width;
|
|
|
|
|
particles->spread_y = 0;
|
|
|
|
|
particles->gravity_x = wind;
|
|
|
|
|
particles->gravity_y = 30.0f;
|
|
|
|
|
particles->drag = 1.0f;
|
|
|
|
|
particles->spawn_rate = 30.0f;
|
|
|
|
|
particles->spawn_fn = snow_spawn;
|
|
|
|
|
particles->update_fn = NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void sparks_spawn(pxl8_particle* p, void* userdata) {
|
|
|
|
|
uintptr_t base_color = (uintptr_t)userdata;
|
|
|
|
|
p->start_color = base_color;
|
|
|
|
|
p->end_color = base_color > 2 ? base_color - 2 : 0;
|
|
|
|
|
p->color = p->start_color;
|
2025-11-28 14:41:35 -06:00
|
|
|
p->max_life = 0.5f + (PXL8_RANDF()) * 1.0f;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-11-28 14:41:35 -06:00
|
|
|
f32 angle = (PXL8_RANDF()) * 6.28f;
|
|
|
|
|
f32 speed = 100.0f + (PXL8_RANDF()) * 200.0f;
|
2025-08-13 15:04:49 -05:00
|
|
|
p->vx = cosf(angle) * speed;
|
|
|
|
|
p->vy = sinf(angle) * speed - 50.0f;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_vfx_sparks(pxl8_particles* particles, i32 x, i32 y, u32 color) {
|
|
|
|
|
if (!particles) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
particles->x = x;
|
|
|
|
|
particles->y = y;
|
|
|
|
|
particles->spread_x = 2.0f;
|
|
|
|
|
particles->spread_y = 2.0f;
|
|
|
|
|
particles->gravity_x = 0;
|
|
|
|
|
particles->gravity_y = 100.0f;
|
|
|
|
|
particles->drag = 0.97f;
|
|
|
|
|
particles->spawn_rate = 40.0f;
|
|
|
|
|
particles->spawn_fn = sparks_spawn;
|
|
|
|
|
particles->update_fn = NULL;
|
|
|
|
|
particles->userdata = (void*)(uintptr_t)color;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_vfx_starfield(pxl8_particles* particles, f32 speed, f32 spread) {
|
|
|
|
|
if (!particles) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
particles->spread_x = particles->spread_y = spread;
|
|
|
|
|
particles->gravity_x = particles->gravity_y = 0;
|
|
|
|
|
particles->drag = 1.0f;
|
|
|
|
|
particles->spawn_rate = 0;
|
|
|
|
|
particles->update_fn = NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u32 i = 0; i < particles->max_count; i++) {
|
|
|
|
|
pxl8_particle* p = &particles->particles[i];
|
2025-11-28 14:41:35 -06:00
|
|
|
p->x = (PXL8_RANDF()) * spread * 2.0f - spread;
|
|
|
|
|
p->y = (PXL8_RANDF()) * spread * 2.0f - spread;
|
|
|
|
|
p->z = (PXL8_RANDF()) * spread;
|
2025-08-13 15:04:49 -05:00
|
|
|
p->vz = -speed;
|
|
|
|
|
p->life = 1000.0f;
|
|
|
|
|
p->max_life = 1000.0f;
|
|
|
|
|
p->color = 8 + (rand() % 8);
|
|
|
|
|
p->flags = 1;
|
|
|
|
|
}
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|