#include #include #include #include "pxl8_macros.h" #include "pxl8_vfx.h" void pxl8_vfx_plasma(pxl8_gfx_ctx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) { if (!ctx || !ctx->framebuffer) return; for (i32 y = 0; y < ctx->framebuffer_height; y++) { for (i32 x = 0; x < ctx->framebuffer_width; x++) { 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); pxl8_pixel(ctx, x, y, color); } } } void pxl8_vfx_raster_bars(pxl8_gfx_ctx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time) { if (!ctx || !bars) return; for (u32 i = 0; i < bar_count; i++) { pxl8_raster_bar* bar = &bars[i]; f32 y = bar->base_y + bar->amplitude * sinf(time * bar->speed + bar->phase); i32 y_int = (i32)y; for (i32 dy = 0; dy <= bar->height; dy++) { f32 position = (f32)dy / (f32)bar->height; f32 gradient = 1.0f - 2.0f * fabsf(position - 0.5f); u8 color_idx; if (gradient > 0.8f) { color_idx = bar->fade_color; } else { u8 range = bar->fade_color - bar->color; color_idx = bar->color + (u8)(gradient * range); } pxl8_rect_fill(ctx, 0, y_int + dy, ctx->framebuffer_width, 1, color_idx); } } } void pxl8_vfx_rotozoom(pxl8_gfx_ctx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) { if (!ctx || !ctx->framebuffer) return; f32 cos_a = cosf(angle); f32 sin_a = sinf(angle); u8* temp_buffer = (u8*)SDL_malloc(ctx->framebuffer_width * ctx->framebuffer_height); if (!temp_buffer) return; SDL_memcpy(temp_buffer, ctx->framebuffer, ctx->framebuffer_width * ctx->framebuffer_height); for (i32 y = 0; y < ctx->framebuffer_height; y++) { for (i32 x = 0; x < ctx->framebuffer_width; x++) { 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; if (sx >= 0 && sx < ctx->framebuffer_width && sy >= 0 && sy < ctx->framebuffer_height) { ctx->framebuffer[y * ctx->framebuffer_width + x] = temp_buffer[sy * ctx->framebuffer_width + sx]; } } } SDL_free(temp_buffer); } void pxl8_vfx_tunnel(pxl8_gfx_ctx* ctx, f32 time, f32 speed, f32 twist) { if (!ctx || !ctx->framebuffer) return; f32 cx = ctx->framebuffer_width / 2.0f; f32 cy = ctx->framebuffer_height / 2.0f; for (i32 y = 0; y < ctx->framebuffer_height; y++) { for (i32 x = 0; x < ctx->framebuffer_width; x++) { f32 dx = x - cx; f32 dy = y - cy; f32 dist = sqrtf(dx * dx + dy * dy); 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; u8 tx = (u8)((i32)(u * 256.0f) & 0xFF); u8 ty = (u8)((i32)(v * 256.0f) & 0xFF); u8 color = tx ^ ty; pxl8_pixel(ctx, x, y, color); } } } } void pxl8_vfx_water_ripple(pxl8_gfx_ctx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping) { if (!ctx || !height_map) return; i32 w = ctx->framebuffer_width; i32 h = ctx->framebuffer_height; static f32* prev_height = NULL; if (!prev_height) { prev_height = (f32*)SDL_calloc(w * h, sizeof(f32)); 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; } void pxl8_vfx_particles_clear(pxl8_particle_system* sys) { if (!sys || !sys->particles) return; for (u32 i = 0; i < sys->max_count; i++) { sys->particles[i].life = 0; sys->particles[i].flags = 0; } sys->alive_count = 0; sys->spawn_timer = 0; } void pxl8_vfx_particles_free(pxl8_particle_system* sys) { if (!sys) return; if (sys->particles) { SDL_free(sys->particles); sys->particles = NULL; } sys->count = 0; sys->max_count = 0; sys->alive_count = 0; } void pxl8_vfx_particles_emit(pxl8_particle_system* sys, u32 count) { if (!sys || !sys->particles) return; for (u32 i = 0; i < count && sys->alive_count < sys->max_count; i++) { for (u32 j = 0; j < sys->max_count; j++) { if (sys->particles[j].life <= 0) { pxl8_particle* p = &sys->particles[j]; p->life = 1.0f; p->max_life = 1.0f; p->x = sys->x + (((f32)rand() / RAND_MAX) - 0.5f) * sys->spread_x; p->y = sys->y + (((f32)rand() / RAND_MAX) - 0.5f) * sys->spread_y; p->z = 0; p->vx = p->vy = p->vz = 0; p->ax = sys->gravity_x; p->ay = sys->gravity_y; 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; if (sys->spawn_fn) { sys->spawn_fn(p, sys->userdata); } sys->alive_count++; break; } } } } void pxl8_vfx_particles_init(pxl8_particle_system* sys, u32 max_count) { if (!sys) return; SDL_memset(sys, 0, sizeof(pxl8_particle_system)); sys->particles = (pxl8_particle*)SDL_calloc(max_count, sizeof(pxl8_particle)); if (!sys->particles) return; sys->max_count = max_count; sys->count = 0; sys->alive_count = 0; sys->spawn_rate = 10.0f; sys->spawn_timer = 0; sys->drag = 0.98f; sys->gravity_y = 100.0f; } void pxl8_vfx_particles_render(pxl8_particle_system* sys, pxl8_gfx_ctx* ctx) { if (!sys || !sys->particles || !ctx) return; for (u32 i = 0; i < sys->max_count; i++) { pxl8_particle* p = &sys->particles[i]; if (p->life > 0 && p->flags) { if (sys->render_fn) { sys->render_fn(ctx, p, sys->userdata); } else { i32 x = (i32)p->x; i32 y = (i32)p->y; if (x >= 0 && x < ctx->framebuffer_width && y >= 0 && y < ctx->framebuffer_height) { pxl8_pixel(ctx, x, y, p->color); } } } } } void pxl8_vfx_particles_update(pxl8_particle_system* sys, f32 dt) { if (!sys || !sys->particles) return; sys->spawn_timer += dt; f32 spawn_interval = 1.0f / sys->spawn_rate; while (sys->spawn_timer >= spawn_interval) { pxl8_vfx_particles_emit(sys, 1); sys->spawn_timer -= spawn_interval; } for (u32 i = 0; i < sys->max_count; i++) { pxl8_particle* p = &sys->particles[i]; if (p->life > 0) { if (sys->update_fn) { sys->update_fn(p, dt, sys->userdata); } else { p->vx += p->ax * dt; p->vy += p->ay * dt; p->vz += p->az * dt; p->vx *= sys->drag; p->vy *= sys->drag; p->vz *= sys->drag; 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; sys->alive_count--; } } } } void pxl8_vfx_explosion(pxl8_particle_system* sys, i32 x, i32 y, u32 color, f32 force) { if (!sys) return; sys->x = x; sys->y = y; sys->spread_x = sys->spread_y = 2.0f; sys->gravity_x = 0; sys->gravity_y = 200.0f; sys->drag = 0.95f; sys->update_fn = NULL; for (u32 i = 0; i < 50 && i < sys->max_count; i++) { pxl8_particle* p = &sys->particles[i]; f32 angle = ((f32)rand() / RAND_MAX) * 6.28f; f32 speed = force * (0.5f + ((f32)rand() / RAND_MAX) * 0.5f); p->x = x; p->y = y; p->vx = cosf(angle) * speed; p->vy = sinf(angle) * speed; p->life = 1.0f; p->max_life = 1.0f + ((f32)rand() / RAND_MAX); 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; p->max_life = 1.5f + ((f32)rand() / RAND_MAX) * 1.5f; p->vy = -80.0f - ((f32)rand() / RAND_MAX) * 120.0f; p->vx = (((f32)rand() / RAND_MAX) - 0.5f) * 40.0f; } 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); } } void pxl8_vfx_fire(pxl8_particle_system* sys, i32 x, i32 y, i32 width, u8 palette_start) { if (!sys) return; sys->x = x; sys->y = y; sys->spread_x = width; sys->spread_y = 4.0f; sys->gravity_x = 0; sys->gravity_y = -100.0f; sys->drag = 0.97f; sys->spawn_rate = 120.0f; sys->spawn_fn = fire_spawn; sys->update_fn = fire_update; sys->userdata = (void*)(uintptr_t)palette_start; } static void rain_spawn(pxl8_particle* p, void* userdata) { (void)userdata; p->start_color = 27 + (rand() % 3); p->end_color = 29; p->color = p->start_color; p->max_life = 2.0f; p->vy = 200.0f + ((f32)rand() / RAND_MAX) * 100.0f; } void pxl8_vfx_rain(pxl8_particle_system* sys, i32 width, f32 wind) { if (!sys) return; sys->x = width / 2.0f; sys->y = -10; sys->spread_x = width; sys->spread_y = 0; sys->gravity_x = wind; sys->gravity_y = 300.0f; sys->drag = 1.0f; sys->spawn_rate = 100.0f; sys->spawn_fn = rain_spawn; sys->update_fn = NULL; } 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; p->max_life = 3.0f + ((f32)rand() / RAND_MAX) * 2.0f; p->vy = -20.0f - ((f32)rand() / RAND_MAX) * 30.0f; p->vx = (((f32)rand() / RAND_MAX) - 0.5f) * 20.0f; p->size = 1.0f + ((f32)rand() / RAND_MAX) * 2.0f; } void pxl8_vfx_smoke(pxl8_particle_system* sys, i32 x, i32 y, u8 color) { if (!sys) return; sys->x = x; sys->y = y; sys->spread_x = 5.0f; sys->spread_y = 5.0f; sys->gravity_x = 0; sys->gravity_y = -50.0f; sys->drag = 0.96f; sys->spawn_rate = 20.0f; sys->spawn_fn = smoke_spawn; sys->update_fn = NULL; sys->userdata = (void*)(uintptr_t)color; } static void snow_spawn(pxl8_particle* p, void* userdata) { (void)userdata; p->start_color = 8 + (rand() % 3); p->end_color = 10; p->color = p->start_color; p->max_life = 4.0f; p->vx = (((f32)rand() / RAND_MAX) - 0.5f) * 20.0f; p->vy = 30.0f + ((f32)rand() / RAND_MAX) * 20.0f; } void pxl8_vfx_snow(pxl8_particle_system* sys, i32 width, f32 wind) { if (!sys) return; sys->x = width / 2.0f; sys->y = -10; sys->spread_x = width; sys->spread_y = 0; sys->gravity_x = wind; sys->gravity_y = 30.0f; sys->drag = 1.0f; sys->spawn_rate = 30.0f; sys->spawn_fn = snow_spawn; sys->update_fn = NULL; } 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; p->max_life = 0.5f + ((f32)rand() / RAND_MAX) * 1.0f; f32 angle = ((f32)rand() / RAND_MAX) * 6.28f; f32 speed = 100.0f + ((f32)rand() / RAND_MAX) * 200.0f; p->vx = cosf(angle) * speed; p->vy = sinf(angle) * speed - 50.0f; } void pxl8_vfx_sparks(pxl8_particle_system* sys, i32 x, i32 y, u32 color) { if (!sys) return; sys->x = x; sys->y = y; sys->spread_x = 2.0f; sys->spread_y = 2.0f; sys->gravity_x = 0; sys->gravity_y = 100.0f; sys->drag = 0.97f; sys->spawn_rate = 40.0f; sys->spawn_fn = sparks_spawn; sys->update_fn = NULL; sys->userdata = (void*)(uintptr_t)color; } void pxl8_vfx_starfield(pxl8_particle_system* sys, f32 speed, f32 spread) { if (!sys) return; sys->spread_x = sys->spread_y = spread; sys->gravity_x = sys->gravity_y = 0; sys->drag = 1.0f; sys->spawn_rate = 0; sys->update_fn = NULL; for (u32 i = 0; i < sys->max_count; i++) { pxl8_particle* p = &sys->particles[i]; p->x = ((f32)rand() / RAND_MAX) * spread * 2.0f - spread; p->y = ((f32)rand() / RAND_MAX) * spread * 2.0f - spread; p->z = ((f32)rand() / RAND_MAX) * spread; p->vz = -speed; p->life = 1000.0f; p->max_life = 1000.0f; p->color = 8 + (rand() % 8); p->flags = 1; } }