summaryrefslogtreecommitdiff
path: root/src/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/Makefile.am2
-rw-r--r--src/modules/swarm/Makefile.am3
-rw-r--r--src/modules/swarm/swarm.c278
3 files changed, 282 insertions, 1 deletions
diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am
index d2c4636..28fc7fd 100644
--- a/src/modules/Makefile.am
+++ b/src/modules/Makefile.am
@@ -1 +1 @@
-SUBDIRS = compose drizzle flui2d julia meta2d montage pixbounce plasma plato ray roto rtv snow sparkler spiro stars submit swab
+SUBDIRS = compose drizzle flui2d julia meta2d montage pixbounce plasma plato ray roto rtv snow sparkler spiro stars submit swab 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 - <vcaputo@pengaru.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/* 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 <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+
+#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 <vcaputo@pengaru.com>",
+ .license = "GPLv3",
+};
© All Rights Reserved