diff options
Diffstat (limited to 'kon/konparticles.c')
| -rw-r--r-- | kon/konparticles.c | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/kon/konparticles.c b/kon/konparticles.c new file mode 100644 index 0000000..6d377ea --- /dev/null +++ b/kon/konparticles.c @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: MIT */ +/* + * konparticles.c + * + * Copyright (c) 2026 KonAki + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "konparticles.h" + +#include <math.h> +#include <string.h> + +/*** random numbers ***/ + +static uint32_t next(kon_particles_t *ps) +{ + uint32_t x = ps->seed; + + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + ps->seed = x; + return x; +} + +/* 0 up to but not including 1 */ +static float unit(kon_particles_t *ps) +{ + return (float)(next(ps) >> 8) * (1.0f / 16777216.0f); +} + +static float between(kon_particles_t *ps, float a, float b) +{ + return a + (b - a) * unit(ps); +} + +/* a number that cannot upset the maths below */ +static float number(float v) +{ + return v == v && v - v == 0.0f ? v : 0.0f; +} + +/*** particles ***/ + +void kon_particlesInit(kon_particles_t *ps, kon_particle_t *pool, int capacity, uint32_t seed) +{ + kon_particles_t zero = {0}; + int i; + + *ps = zero; + ps->pool = pool; + ps->capacity = pool && capacity > 0 ? capacity : 0; + for (i = 0; i < ps->capacity; i++) memset(&pool[i], 0, sizeof pool[i]); + ps->seed = seed ? seed : 0x9E3779B9u; + ps->speedMin = ps->speedMax = 20.0f; + ps->lifeMin = ps->lifeMax = 1.0f; + ps->size0 = ps->size1 = 1.0f; + ps->color0 = ps->color1 = 0xFFE4DDF0u; + ps->blend = KON_BLEND_NORMAL; +} + +static int spawn(kon_particles_t *ps) +{ + int i; + + for (i = 0; i < ps->capacity; i++) { + kon_particle_t *p = &ps->pool[i]; + float angle, speed, life; + + if (p->life > 0.0f) continue; + life = between(ps, number(ps->lifeMin), number(ps->lifeMax)); + if (!(life > 0.0f)) return 0; + angle = number(ps->angle) + between(ps, -number(ps->spread), number(ps->spread)); + speed = between(ps, number(ps->speedMin), number(ps->speedMax)); + p->x = number(ps->x) + unit(ps) * number(ps->width); + p->y = number(ps->y) + unit(ps) * number(ps->height); + p->vx = cosf(angle) * speed; + p->vy = sinf(angle) * speed; + p->age = 0.0f; + p->life = life; + p->size0 = number(ps->size0); + p->size1 = number(ps->size1); + p->color0 = ps->color0; + p->color1 = ps->color1; + return 1; + } + return 0; +} + +int kon_particlesBurst(kon_particles_t *ps, int count) +{ + int made = 0; + + while (made < count && spawn(ps)) made++; + return made; +} + +void kon_particlesUpdate(kon_particles_t *ps, float dt) +{ + int i; + + if (!(dt > 0.0f)) return; + if (dt > 0.25f) dt = 0.25f; + + for (i = 0; i < ps->capacity; i++) { + kon_particle_t *p = &ps->pool[i]; + + if (!(p->life > 0.0f)) continue; + p->vx += number(ps->ax) * dt; + p->vy += number(ps->ay) * dt; + p->x += p->vx * dt; + p->y += p->vy * dt; + p->age += dt; + if (p->age >= p->life) p->life = 0.0f; + if (ps->area.w > 0 && ps->area.h > 0 && (p->x < (float)ps->area.x || p->y < (float)ps->area.y || + p->x >= (float)(ps->area.x + ps->area.w) || p->y >= (float)(ps->area.y + ps->area.h))) + p->life = 0.0f; + } + + if (number(ps->rate) > 0.0f) { + ps->carry += number(ps->rate) * dt; + while (ps->carry >= 1.0f) { + ps->carry -= 1.0f; + if (!spawn(ps)) { + ps->carry = 0.0f; + break; + } + } + } +} + +int kon_particlesCount(const kon_particles_t *ps) +{ + int i, n = 0; + + for (i = 0; i < ps->capacity; i++) { + if (ps->pool[i].life > 0.0f) n++; + } + return n; +} + +void kon_particlesClear(kon_particles_t *ps) +{ + int i; + + for (i = 0; i < ps->capacity; i++) ps->pool[i].life = 0.0f; + ps->carry = 0.0f; +} + +/*** drawing ***/ + +static uint32_t mix(uint32_t a, uint32_t b, float t) +{ + uint32_t out = 0; + int shift; + + for (shift = 0; shift < 32; shift += 8) { + float ca = (float)((a >> shift) & 0xFF), cb = (float)((b >> shift) & 0xFF); + int v = (int)(ca + (cb - ca) * t + 0.5f); + + if (v < 0) v = 0; + if (v > 255) v = 255; + out |= (uint32_t)v << shift; + } + return out; +} + +void kon_particlesDraw(const kon_particles_t *ps, kon_framebuffer_t *fb, int offsetX, int offsetY) +{ + static uint32_t white = 0xFFFFFFFFu; + kon_framebuffer_t speck; + const kon_image *image = ps->image; + kon_rect_t src = {0, 0, 1, 1}; + int i; + + if (!fb) return; + speck.width = speck.height = 1; + speck.data = &white; + speck.depth = NULL; + if (image) { + src = ps->src; + if (src.w <= 0 || src.h <= 0) src = KON_RECT(0, 0, image->width, image->height); + if (src.w <= 0 || src.h <= 0) return; + } else { + image = &speck; + } + + for (i = 0; i < ps->capacity; i++) { + const kon_particle_t *p = &ps->pool[i]; + float t, size, length, angle = 0.0f; + + if (!(p->life > 0.0f)) continue; + t = p->age / p->life; + if (t > 1.0f) t = 1.0f; + size = p->size0 + (p->size1 - p->size0) * t; + if (!(size > 1.0f)) size = 1.0f; + length = size; + if (!ps->image && number(ps->stretch) > 0.0f) { + float speed = sqrtf(p->vx * p->vx + p->vy * p->vy); + + if (speed > 0.0f) { + length = speed * ps->stretch; + if (length < size) length = size; + angle = atan2f(p->vy, p->vx); + } + } + kon_drawImageBlend(fb, image, src, p->x + (float)offsetX + 0.5f, p->y + (float)offsetY + 0.5f, + (float)src.w * 0.5f, (float)src.h * 0.5f, + ps->image ? size / (float)src.w : length, ps->image ? size / (float)src.w : size, angle, + KON_FILTER_NEAREST, mix(p->color0, p->color1, t), ps->blend); + } +} + +/*** weather ***/ + +static void weatherBase(kon_particles_t *ps, int viewWidth, int viewHeight, int power) +{ + if (viewWidth < 0) viewWidth = 0; + if (viewHeight < 0) viewHeight = 0; + if (viewWidth > 16384) viewWidth = 16384; + if (viewHeight > 16384) viewHeight = 16384; + if (power < 1) power = 1; + if (power > 9) power = 9; + ps->rate = (float)power * (float)viewWidth * (float)viewHeight / 1800.0f; + ps->image = NULL; + ps->blend = KON_BLEND_NORMAL; + ps->ax = ps->ay = 0.0f; + ps->height = 0.0f; + ps->area = KON_RECT(-96, -64, viewWidth + 192, viewHeight + 128); +} + +void kon_weatherRain(kon_particles_t *ps, int viewWidth, int viewHeight, int power) +{ + viewWidth = viewWidth < 0 ? 0 : viewWidth > 16384 ? 16384 : viewWidth; + viewHeight = viewHeight < 0 ? 0 : viewHeight > 16384 ? 16384 : viewHeight; + float speed = 420.0f, margin = 24.0f; + + weatherBase(ps, viewWidth, viewHeight, power); + ps->x = -margin; + ps->width = (float)viewWidth + margin * 2.0f; + ps->y = -16.0f; + ps->angle = 1.35f; /* leaning a little with the wind */ + ps->spread = 0.03f; + ps->speedMin = speed * 0.9f; + ps->speedMax = speed * 1.1f; + ps->lifeMin = ((float)viewHeight + 32.0f) / (speed * 1.1f); + ps->lifeMax = ((float)viewHeight + 32.0f) / (speed * 0.9f); + ps->size0 = ps->size1 = 1.0f; + ps->color0 = ps->color1 = 0xA0B8C8E0u; + ps->stretch = 0.035f; +} + +void kon_weatherSnow(kon_particles_t *ps, int viewWidth, int viewHeight, int power) +{ + viewWidth = viewWidth < 0 ? 0 : viewWidth > 16384 ? 16384 : viewWidth; + viewHeight = viewHeight < 0 ? 0 : viewHeight > 16384 ? 16384 : viewHeight; + float speed = 36.0f, margin = 24.0f; + + weatherBase(ps, viewWidth, viewHeight, power); + ps->rate *= 0.5f; + ps->x = -margin; + ps->width = (float)viewWidth + margin * 2.0f; + ps->y = -8.0f; + ps->angle = 1.5708f; + ps->spread = 0.5f; + ps->speedMin = speed * 0.6f; + ps->speedMax = speed * 1.4f; + ps->lifeMin = ((float)viewHeight + 16.0f) / (speed * 1.4f); + ps->lifeMax = ((float)viewHeight + 16.0f) / (speed * 0.6f); + ps->size0 = 2.0f; + ps->size1 = 1.0f; + ps->color0 = 0xF0E4DDF0u; + ps->color1 = 0xC0E4DDF0u; + ps->stretch = 0.0f; +} + +void kon_weatherStorm(kon_particles_t *ps, int viewWidth, int viewHeight, int power) +{ + viewWidth = viewWidth < 0 ? 0 : viewWidth > 16384 ? 16384 : viewWidth; + viewHeight = viewHeight < 0 ? 0 : viewHeight > 16384 ? 16384 : viewHeight; + kon_weatherRain(ps, viewWidth, viewHeight, power); + ps->rate *= 1.6f; + ps->angle = 1.15f; + ps->speedMin = 520.0f; + ps->speedMax = 640.0f; + ps->lifeMin = ((float)viewHeight + 32.0f) / 640.0f; + ps->lifeMax = ((float)viewHeight + 32.0f) / 520.0f; + ps->x = -(float)viewHeight * 0.5f; + ps->width = (float)viewWidth + (float)viewHeight; + ps->stretch = 0.045f; + ps->area = KON_RECT(-viewHeight - 32, -64, viewWidth + viewHeight * 2 + 64, viewHeight + 128); +} |
