/* 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 #include /*** 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); }