From aadd3f1c3b2f299d9e68335f34a98dad2c730ea6 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Mon, 8 Feb 2021 04:49:41 -0800 Subject: modules/swarm: implement a particles swarm module Just a fun little swarm based loosely on 80s-era boids It would be interesting to make stuff like the # of particles and the weights runtime configurable, or exposed as knobs. Using a Z-buffer for occlusions and perhaps shading by depth might make a significant improvement on the visual quality. It might also be interesting to draw the particles as lines connecting their current position with their previous, instead as pixels. Or fat pixels like stars... --- src/modules/swarm/Makefile.am | 3 + src/modules/swarm/swarm.c | 278 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 src/modules/swarm/Makefile.am create mode 100644 src/modules/swarm/swarm.c (limited to 'src/modules/swarm') diff --git a/src/modules/swarm/Makefile.am b/src/modules/swarm/Makefile.am new file mode 100644 index 0000000..327aee8 --- /dev/null +++ b/src/modules/swarm/Makefile.am @@ -0,0 +1,3 @@ +noinst_LIBRARIES = libswarm.a +libswarm_a_SOURCES = swarm.c +libswarm_a_CPPFLAGS = -I@top_srcdir@/src -I@top_srcdir@/src/libs diff --git a/src/modules/swarm/swarm.c b/src/modules/swarm/swarm.c new file mode 100644 index 0000000..4ebd79e --- /dev/null +++ b/src/modules/swarm/swarm.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2021 - Vito Caputo - + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* this implements a very simplified "boids" inspired particles swarm + * http://www.red3d.com/cwr/boids/ + * https://en.wikipedia.org/wiki/Boids + * https://en.wikipedia.org/wiki/Swarm_intelligence + */ + +#include +#include +#include + +#include "fb.h" +#include "rototiller.h" + +#define SWARM_SIZE (32 * 1024) +#define SWARM_ZCONST 4.f + +typedef struct v3f_t { + float x, y, z; +} v3f_t; + +typedef struct v2f_t { + float x, y; +} v2f_t; + +typedef struct boid_t { + v3f_t position; + v3f_t direction; + float velocity; +} boid_t; + +typedef struct swarm_context_t { + v3f_t color; + float ztweak; + boid_t boids[]; +} swarm_context_t; + + +static inline float randf(float min, float max) +{ + return ((float)rand() / (float)RAND_MAX) * (max - min) + min; +} + + +static inline void v3f_rand(v3f_t *v, float min, float max) +{ + v->x = randf(min, max); + v->y = randf(min, max); + v->z = randf(min, max); +} + + +static inline v3f_t v3f_add(v3f_t a, v3f_t b) +{ + return (v3f_t){ + .x = a.x + b.x, + .y = a.y + b.y, + .z = a.z + b.z, + }; +} + + +static inline v3f_t v3f_sub(v3f_t a, v3f_t b) +{ + return (v3f_t){ + .x = a.x - b.x, + .y = a.y - b.y, + .z = a.z - b.z, + }; +} + + +static inline v3f_t v3f_mult_scalar(v3f_t v, float scalar) +{ + return (v3f_t){ + .x = v.x * scalar, + .y = v.y * scalar, + .z = v.z * scalar, + }; +} + + +static inline float v3f_len(v3f_t v) +{ + return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); +} + + +static inline void v3f_normalize(v3f_t *v) +{ + float l = 1.f / v3f_len(*v); + + v->x *= l; + v->y *= l; + v->z *= l; +} + + +static v3f_t v3f_lerp(v3f_t a, v3f_t b, float t) +{ + return (v3f_t){ + .x = (b.x - a.x) * t + a.x, + .y = (b.y - a.y) * t + a.y, + .z = (b.z - a.z) * t + a.z, + }; +} + + +static void boid_randomize(boid_t *boid) +{ + v3f_rand(&boid->position, -1.f, 1.f); + v3f_rand(&boid->direction, -1.f, 1.f); + v3f_normalize(&boid->direction); + boid->velocity = randf(.05f, .2f); +} + + +/* convert a color into a packed, 32-bit rgb pixel value (taken from libs/ray/ray_color.h) */ +static inline uint32_t color_to_uint32(v3f_t color) { + uint32_t pixel; + + if (color.x > 1.0f) color.x = 1.0f; + if (color.y > 1.0f) color.y = 1.0f; + if (color.z > 1.0f) color.z = 1.0f; + + if (color.x < .0f) color.x = .0f; + if (color.y < .0f) color.y = .0f; + if (color.z < .0f) color.z = .0f; + + pixel = (uint32_t)(color.x * 255.0f); + pixel <<= 8; + pixel |= (uint32_t)(color.y * 255.0f); + pixel <<= 8; + pixel |= (uint32_t)(color.z * 255.0f); + + return pixel; +} + + +static void * swarm_create_context(unsigned ticks, unsigned num_cpus) +{ + swarm_context_t *ctxt; + + ctxt = calloc(1, sizeof(swarm_context_t) + sizeof(*(ctxt->boids)) * SWARM_SIZE); + if (!ctxt) + return NULL; + + for (unsigned i = 0; i < SWARM_SIZE; i++) + boid_randomize(&ctxt->boids[i]); + + return ctxt; +} + + +static void swarm_destroy_context(void *context) +{ + swarm_context_t *ctxt = context; + + free(ctxt); +} + + +static void swarm_update(swarm_context_t *ctxt, unsigned ticks) +{ + v3f_t avg_direction = {}; + float avg_velocity = 0.f; + v3f_t avg_center = {}; + float wleader, wcenter, wdirection; + + { /* [0] = leader */ + float r = (float)ticks * ((cosf((float)ticks * .0001f) * .5f + .5f) * .1f); + + ctxt->boids[0].position.x = cosf(r); + ctxt->boids[0].position.y = sinf(r); + ctxt->boids[0].position.z = cosf(r * 2.f); + } + + /* characterize the current swarm */ + for (unsigned i = 0; i < SWARM_SIZE; i++) { + boid_t *b = &ctxt->boids[i]; + + avg_center = v3f_add(avg_center, b->position); + avg_direction = v3f_add(avg_direction, b->direction); + avg_velocity += b->velocity; + } + + avg_velocity *= (1.f / (float)SWARM_SIZE); + avg_center = v3f_mult_scalar(avg_center, (1.f / (float)SWARM_SIZE)); + avg_direction = v3f_mult_scalar(avg_direction, (1.f / (float)SWARM_SIZE)); + v3f_normalize(&avg_direction); + + /* vary weights */ + wleader = cosf((float)ticks * .001f) * .5f + .5f; + wcenter = cosf((float)ticks * .0005f) * .5f + .5f; + wdirection = sinf((float)ticks * .003f) * .5f + .5f; + + /* update the followers in relation to leader and swarm itself */ + for (unsigned i = 1; i < SWARM_SIZE; i++) { + boid_t *b = &ctxt->boids[i]; + v3f_t to_leader = v3f_sub(ctxt->boids[0].position, b->position); + v3f_t to_center = v3f_sub(avg_center, b->position); + + v3f_normalize(&to_leader); + b->direction = v3f_lerp(b->direction, to_leader, wleader * .1f); + v3f_normalize(&b->direction); + b->direction = v3f_lerp(b->direction, to_center, wcenter * .1f); + v3f_normalize(&b->direction); + b->direction = v3f_lerp(b->direction, avg_direction, wdirection * .05f); + v3f_normalize(&b->direction); + + b->position = v3f_add(b->position, v3f_mult_scalar(b->direction, b->velocity)); + } + + /* color the swarm according to the current weights */ + ctxt->color.x = wleader; + ctxt->color.y = wcenter; + ctxt->color.z = wdirection; + + /* this zooms out a bit when the swarm loosens up, gauged by low weights */ + ctxt->ztweak = (1.8f - v3f_len(ctxt->color)) * 4.f; +} + + +static void swarm_render_fragment(void *context, unsigned ticks, unsigned cpu, fb_fragment_t *fragment) +{ + swarm_context_t *ctxt = context; + + swarm_update(ctxt, ticks); + + fb_fragment_zero(fragment); + + { + float fw = fragment->frame_width, fh = fragment->frame_height; + uint32_t color = color_to_uint32(ctxt->color); + + fw *= .5f; + fh *= .5f; + + for (unsigned i = 0; i < SWARM_SIZE; i++) { + boid_t *b = &ctxt->boids[i]; + v2f_t nc; + + nc.x = b->position.x / (b->position.z + SWARM_ZCONST + ctxt->ztweak); + nc.y = b->position.y / (b->position.z + SWARM_ZCONST + ctxt->ztweak); + + nc.x = nc.x * fw + fw; + nc.y = nc.y * fh + fh; + + fb_fragment_put_pixel_checked(fragment, nc.x, nc.y, color); + } + } +} + + +rototiller_module_t swarm_module = { + .create_context = swarm_create_context, + .destroy_context = swarm_destroy_context, + .render_fragment = swarm_render_fragment, + .name = "swarm", + .description = "\"Boids\"-inspired particle swarm in 3D", + .author = "Vito Caputo ", + .license = "GPLv3", +}; -- cgit v1.2.3