summaryrefslogtreecommitdiff
path: root/kon/konparticles.c
diff options
context:
space:
mode:
Diffstat (limited to 'kon/konparticles.c')
-rw-r--r--kon/konparticles.c310
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);
+}