summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2019-11-25 23:46:27 -0800
committerVito Caputo <vcaputo@pengaru.com>2019-11-25 23:48:36 -0800
commita1bcd46971f74e4fb6ac3b0242829601e390572f (patch)
tree4ef341408c6aee0a052e7bb1d96fcad54ac191bf
parentb738e7e70f1853ab2af94266c0b7b0b1a821cfad (diff)
meta2d: add a classic 2D metaballs module
-rw-r--r--README1
-rw-r--r--configure.ac1
-rw-r--r--src/Makefile.am2
-rw-r--r--src/modules/Makefile.am2
-rw-r--r--src/modules/meta2d/Makefile.am3
-rw-r--r--src/modules/meta2d/meta2d.c235
-rw-r--r--src/modules/meta2d/v2f.h352
-rw-r--r--src/modules/meta2d/v3f.h356
-rw-r--r--src/rototiller.c2
9 files changed, 952 insertions, 2 deletions
diff --git a/README b/README
index 3d04bde..e807770 100644
--- a/README
+++ b/README
@@ -1,6 +1,7 @@
rototiller is, rendered entirely in software:
flui2d: a 2D fluid dynamics simulation
julia: a morphing Julia set (fractal)
+ meta2d: a 2D metaballs effect
montage: a montage of modules meta-module
pixbounce: a 2D bouncing pixmap demonstration
plasma: an oldskool "plasma" effect
diff --git a/configure.ac b/configure.ac
index 0b389e2..091b6e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -43,6 +43,7 @@ AC_CONFIG_FILES([
src/modules/Makefile
src/modules/flui2d/Makefile
src/modules/julia/Makefile
+ src/modules/meta2d/Makefile
src/modules/montage/Makefile
src/modules/pixbounce/Makefile
src/modules/plasma/Makefile
diff --git a/src/Makefile.am b/src/Makefile.am
index 2501c95..5446acd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,4 +4,4 @@ rototiller_SOURCES = fb.c fb.h fps.c fps.h rototiller.c rototiller.h sdl_fb.c se
if ENABLE_DRM
rototiller_SOURCES += drm_fb.c
endif
-rototiller_LDADD = modules/flui2d/libflui2d.a modules/julia/libjulia.a modules/montage/libmontage.a modules/pixbounce/libpixbounce.a modules/plasma/libplasma.a modules/ray/libray.a modules/roto/libroto.a modules/rtv/librtv.a modules/snow/libsnow.a modules/sparkler/libsparkler.a modules/stars/libstars.a modules/submit/libsubmit.a modules/swab/libswab.a libs/grid/libgrid.a libs/ray/libray.a libs/txt/libtxt.a libs/ascii/libascii.a libs/din/libdin.a -lm
+rototiller_LDADD = modules/flui2d/libflui2d.a modules/julia/libjulia.a modules/meta2d/libmeta2d.a modules/montage/libmontage.a modules/pixbounce/libpixbounce.a modules/plasma/libplasma.a modules/ray/libray.a modules/roto/libroto.a modules/rtv/librtv.a modules/snow/libsnow.a modules/sparkler/libsparkler.a modules/stars/libstars.a modules/submit/libsubmit.a modules/swab/libswab.a libs/grid/libgrid.a libs/ray/libray.a libs/txt/libtxt.a libs/ascii/libascii.a libs/din/libdin.a -lm
diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am
index 1622814..a4ff9e7 100644
--- a/src/modules/Makefile.am
+++ b/src/modules/Makefile.am
@@ -1 +1 @@
-SUBDIRS = flui2d julia montage pixbounce plasma ray roto rtv snow sparkler stars submit swab
+SUBDIRS = flui2d julia meta2d montage pixbounce plasma ray roto rtv snow sparkler stars submit swab
diff --git a/src/modules/meta2d/Makefile.am b/src/modules/meta2d/Makefile.am
new file mode 100644
index 0000000..b1394d1
--- /dev/null
+++ b/src/modules/meta2d/Makefile.am
@@ -0,0 +1,3 @@
+noinst_LIBRARIES = libmeta2d.a
+libmeta2d_a_SOURCES = meta2d.c v2f.h v3f.h
+libmeta2d_a_CPPFLAGS = -I@top_srcdir@/src -I@top_srcdir@/src/libs
diff --git a/src/modules/meta2d/meta2d.c b/src/modules/meta2d/meta2d.c
new file mode 100644
index 0000000..08cd15a
--- /dev/null
+++ b/src/modules/meta2d/meta2d.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2019 - 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/>.
+ */
+
+/* https://en.wikipedia.org/wiki/Metaballs */
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "din/din.h"
+#include "fb.h"
+#include "rototiller.h"
+
+#include "v2f.h"
+#include "v3f.h"
+
+#define META2D_NUM_BALLS 10
+
+typedef struct meta2d_ball_t {
+ v2f_t position;
+ float radius;
+ v3f_t color;
+} meta2d_ball_t;
+
+typedef struct meta2d_context_t {
+ unsigned n;
+ din_t *din_a, *din_b;
+ float din_t;
+ unsigned n_cpus;
+ meta2d_ball_t balls[META2D_NUM_BALLS];
+} meta2d_context_t;
+
+
+/* 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 * meta2d_create_context(unsigned num_cpus)
+{
+ meta2d_context_t *ctxt;
+
+ ctxt = calloc(1, sizeof(meta2d_context_t));
+
+ /* perlin noise is used for some organic-ish random movement of the balls */
+ ctxt->din_a = din_new(10, 10, META2D_NUM_BALLS + 2);
+ ctxt->din_b = din_new(10, 10, META2D_NUM_BALLS + 2);
+
+ srand(getpid());
+ ctxt->n_cpus = num_cpus;
+
+ for (int i = 0; i < META2D_NUM_BALLS; i++) {
+ meta2d_ball_t *ball = &ctxt->balls[i];
+
+ v2f_rand(&ball->position, &(v2f_t){-.7f, -.7f}, &(v2f_t){.7f, .7f});
+ ball->radius = rand() / (float)RAND_MAX * .2f + .05f;
+ v3f_rand(&ball->color, &(v3f_t){0.f, 0.f, 0.f}, &(v3f_t){1.f, 1.f, 1.f});
+ }
+
+ return ctxt;
+}
+
+
+static void meta2d_destroy_context(void *context)
+{
+ meta2d_context_t *ctxt = context;
+
+ din_free(ctxt->din_a);
+ din_free(ctxt->din_b);
+ free(ctxt);
+}
+
+
+static int meta2d_fragmenter(void *context, const fb_fragment_t *fragment, unsigned number, fb_fragment_t *res_fragment)
+{
+ meta2d_context_t *ctxt = context;
+
+ return fb_fragment_slice_single(fragment, ctxt->n_cpus, number, res_fragment);
+}
+
+
+static void meta2d_prepare_frame(void *context, unsigned n_cpus, fb_fragment_t *fragment, rototiller_fragmenter_t *res_fragmenter)
+{
+ meta2d_context_t *ctxt = context;
+
+ *res_fragmenter = meta2d_fragmenter;
+
+ /* move the balls around */
+ for (int i = 0; i < META2D_NUM_BALLS; i++) {
+ meta2d_ball_t *ball = &ctxt->balls[i];
+ float rad;
+
+ /* Perlin noise indexed by position for x,y and i for z
+ * is used just for moving the metaballs around.
+ *
+ * Two noise fields are used with their values interpolated,
+ * starting with the din_a being 100% of the movement,
+ * with every frame migrating closer to din_b being 100%.
+ *
+ * Once din_b contributes 100%, it becomes din_a, and the old
+ * din_a becomes din_b which gets randomized, and the % resets
+ * to 0.
+ *
+ * This allows an organic continuous evolution of the field
+ * over time, at double the sampling cost since we're sampling
+ * two noise fields and interpolating them. Since this is
+ * just per-ball every frame, it's probably OK. Not like it's
+ * every pixel.
+ */
+
+ /* ad-hoc lerp of the two dins */
+ rad = din(ctxt->din_a, &(v3f_t){
+ .x = ball->position.x,
+ .y = ball->position.y,
+ .z = (float)i * (1.f / (float)META2D_NUM_BALLS)}
+ ) * (1.f - ctxt->din_t);
+
+ rad += din(ctxt->din_b, &(v3f_t){
+ .x = ball->position.x,
+ .y = ball->position.y,
+ .z = (float)i * (1.f / (float)META2D_NUM_BALLS)}
+ ) * ctxt->din_t;
+
+ /* Perlin noise doesn't produce anything close to a uniform random distribution
+ * of -1..+1, so it can't just be mapped directly to 2*PI with all angles getting
+ * roughly equal occurrences in the long-run. For now I just *10.f and it seems
+ * to be OK.
+ */
+ rad *= 10.f * 2.f * M_PI;
+ v2f_add(&ball->position,
+ &ball->position,
+ &(v2f_t){
+ .x = cosf(rad) * .003f, /* small steps */
+ .y = sinf(rad) * .003f,
+ });
+
+ v2f_clamp(&ball->position,
+ &ball->position,
+ &(v2f_t){-.8f, -.8f}, /* keep the balls mostly on-screen */
+ &(v2f_t){.8f, .8f}
+ );
+ }
+
+ /* when din_t reaches 1 swap a<->b, reset din_t, randomize b */
+ ctxt->din_t += .01f;
+ if (ctxt->din_t >= 1.f) {
+ din_t *tmp;
+
+ tmp = ctxt->din_a;
+ ctxt->din_a = ctxt->din_b;
+ ctxt->din_b = tmp;
+
+ din_randomize(ctxt->din_b);
+ ctxt->din_t = 0.f;
+ }
+}
+
+
+static void meta2d_render_fragment(void *context, unsigned cpu, fb_fragment_t *fragment)
+{
+ meta2d_context_t *ctxt = context;
+ float xf = 2.f / (float)fragment->frame_width;
+ float yf = 2.f / (float)fragment->frame_height;
+ v2f_t coord;
+
+ for (int y = fragment->y; y < fragment->y + fragment->height; y++) {
+ coord.y = yf * (float)y - 1.f;
+
+ for (int x = fragment->x; x < fragment->x + fragment->width; x++) {
+ v3f_t color = {};
+ uint32_t pixel;
+ float t = 0;
+
+ coord.x = xf * (float)x - 1.f;
+
+ for (int i = 0; i < META2D_NUM_BALLS; i++) {
+ meta2d_ball_t *ball = &ctxt->balls[i];
+
+ float f;
+
+ f = ball->radius * ball->radius / v2f_distance_sq(&coord, &ball->position);
+ v3f_add(&color, &color, v3f_mult_scalar(&(v3f_t){}, &ball->color, f));
+ t += f;
+ }
+
+ /* these thresholds define the thickness of the ribbon */
+ if (t < .7f || t > .8f)
+ color = (v3f_t){};
+
+ pixel = color_to_uint32(color);
+ fb_fragment_put_pixel_unchecked(fragment, x, y, pixel);
+ }
+ }
+}
+
+
+rototiller_module_t meta2d_module = {
+ .create_context = meta2d_create_context,
+ .destroy_context = meta2d_destroy_context,
+ .prepare_frame = meta2d_prepare_frame,
+ .render_fragment = meta2d_render_fragment,
+ .name = "meta2d",
+ .description = "Classic 2D metaballs (threaded)",
+ .author = "Vito Caputo <vcaputo@pengaru.com>",
+ .license = "GPLv3",
+};
diff --git a/src/modules/meta2d/v2f.h b/src/modules/meta2d/v2f.h
new file mode 100644
index 0000000..8f51ee0
--- /dev/null
+++ b/src/modules/meta2d/v2f.h
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2018-2019 - 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/>.
+ */
+
+/*
+ * 2D vector operations header
+ *
+ * Variants prefixed with _ return a result vector struct by value.
+ *
+ * Variants missing the _ prefix for vector result operations return a pointer
+ * and must be either supplied the result memory as the first "res" argument
+ * which is then returned after being populated with the result's value, or
+ * NULL to allocate space for the result and that pointer is returned after being
+ * populated with its value. When supplying NULL result pointers the functions
+ * must allocate memory and thus may return NULL on OOM, so callers should
+ * check for NULL returns when supplying NULL for "res".
+ *
+ * Example:
+ * v2f_t foo, *foop;
+ *
+ * foo = _v2f_mult(&(v2f_t){1.f,1.f}, &(v2f_t){2.f,2.f});
+ *
+ * is equivalent to:
+ *
+ * v2f_mult(&foo, &(v2f_t){1.f,1.f}, &(v2f_t){2.f,2.f});
+ *
+ * or dynamically allocated:
+ *
+ * foop = v2f_mult(NULL, &(v2f_t){1.f,1.f}, &(v2f_t){2.f,2.f});
+ * free(foop);
+ *
+ * is equivalent to:
+ *
+ * foop = malloc(sizeof(v2f_t));
+ * v2f_mult(foop, &(v2f_t){1.f,1.f}, &(v2f_t){2.f,2.f});
+ * free(foop);
+ */
+
+#ifndef _V2F_H
+#define _V2F_H
+
+
+#include <math.h>
+#include <stdlib.h>
+
+
+typedef struct v2f_t {
+ float x, y;
+} v2f_t;
+
+
+static inline v2f_t * _v2f_allocated(v2f_t **ptr)
+{
+ if (!*ptr)
+ *ptr = malloc(sizeof(v2f_t));
+
+ return *ptr;
+}
+
+
+static inline v2f_t _v2f_add(const v2f_t *a, const v2f_t *b)
+{
+ return (v2f_t){a->x + b->x, a->y + b->y};
+}
+
+
+static inline v2f_t * v2f_add(v2f_t *res, const v2f_t *a, const v2f_t *b)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_add(a, b);
+
+ return res;
+}
+
+
+static inline v2f_t _v2f_sub(const v2f_t *a, const v2f_t *b)
+{
+ return (v2f_t){a->x - b->x, a->y - b->y};
+}
+
+
+static inline v2f_t * v2f_sub(v2f_t *res, const v2f_t *a, const v2f_t *b)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_sub(a, b);
+
+ return res;
+}
+
+
+static inline v2f_t _v2f_mult(const v2f_t *a, const v2f_t *b)
+{
+ return (v2f_t){a->x * b->x, a->y * b->y};
+}
+
+
+static inline v2f_t * v2f_mult(v2f_t *res, const v2f_t *a, const v2f_t *b)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_mult(a, b);
+
+ return res;
+}
+
+
+static inline v2f_t _v2f_mult_scalar(const v2f_t *v, float scalar)
+{
+ return (v2f_t){ v->x * scalar, v->y * scalar };
+}
+
+
+static inline v2f_t * v2f_mult_scalar(v2f_t *res, const v2f_t *v, float scalar)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_mult_scalar(v, scalar);
+
+ return res;
+}
+
+
+static inline v2f_t _v2f_div_scalar(const v2f_t *v, float scalar)
+{
+ return _v2f_mult_scalar(v, 1.f / scalar);
+}
+
+
+static inline v2f_t * v2f_div_scalar(v2f_t *res, const v2f_t *v, float scalar)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_div_scalar(v, scalar);
+
+ return res;
+}
+
+
+static inline float v2f_dot(const v2f_t *a, const v2f_t *b)
+{
+ return a->x * b->x + a->y * b->y;
+}
+
+
+static inline float v2f_length(const v2f_t *v)
+{
+ return sqrtf(v2f_dot(v, v));
+}
+
+
+static inline float v2f_distance(const v2f_t *a, const v2f_t *b)
+{
+ return v2f_length(v2f_sub(&(v2f_t){}, a, b));
+}
+
+
+static inline float v2f_distance_sq(const v2f_t *a, const v2f_t *b)
+{
+ v2f_t d = _v2f_sub(a, b);
+
+ return v2f_dot(&d, &d);
+}
+
+
+static inline v2f_t _v2f_normalize(const v2f_t *v)
+{
+ return _v2f_mult_scalar(v, 1.0f / v2f_length(v));
+}
+
+
+static inline v2f_t * v2f_normalize(v2f_t *res, const v2f_t *v)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_normalize(v);
+
+ return res;
+}
+
+
+static inline v2f_t _v2f_lerp(const v2f_t *a, const v2f_t *b, float t)
+{
+ v2f_t lerp_a, lerp_b;
+
+ lerp_a = _v2f_mult_scalar(a, 1.0f - t);
+ lerp_b = _v2f_mult_scalar(b, t);
+
+ return _v2f_add(&lerp_a, &lerp_b);
+}
+
+
+static inline v2f_t * v2f_lerp(v2f_t *res, const v2f_t *a, const v2f_t *b, float t)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_lerp(a, b, t);
+
+ return res;
+}
+
+
+static inline v2f_t _v2f_nlerp(const v2f_t *a, const v2f_t *b, float t)
+{
+ v2f_t lerp = _v2f_lerp(a, b, t);
+
+ return _v2f_normalize(&lerp);
+}
+
+
+static inline v2f_t * v2f_nlerp(v2f_t *res, const v2f_t *a, const v2f_t *b, float t)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_nlerp(a, b, t);
+
+ return res;
+}
+
+
+/*
+ * 1 ab-------bb
+ * | | |
+ * | | |
+ * | | |
+ * 0 aa-------ba
+ * t_x: 0---------1
+ * ^
+ * t_y
+ */
+static inline v2f_t _v2f_bilerp(const v2f_t *aa, const v2f_t *ab, const v2f_t *ba, const v2f_t *bb, float t_x, float t_y)
+{
+ v2f_t xa = _v2f_lerp(aa, ba, t_x);
+ v2f_t xb = _v2f_lerp(ab, bb, t_x);
+
+ return _v2f_lerp(&xa, &xb, t_y);
+}
+
+
+static inline v2f_t * v2f_bilerp(v2f_t *res, const v2f_t *aa, const v2f_t *ab, const v2f_t *ba, const v2f_t *bb, float t_x, float t_y)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_bilerp(aa, ab, ba, bb, t_x, t_y);
+
+ return res;
+}
+
+
+/*
+ * abb-------bbb
+ * /| /|
+ * aba-------bba|
+ * | | | |
+ * |aab------|bab
+ * |/ |/
+ * aaa-------baa
+ */
+static inline v2f_t _v2f_trilerp(const v2f_t *aaa, const v2f_t *aba, const v2f_t *aab, const v2f_t *abb, const v2f_t *baa, const v2f_t *bba, const v2f_t *bab, const v2f_t *bbb, float t_x, float t_y, float t_z)
+{
+ v2f_t xya = _v2f_bilerp(aaa, aba, baa, bba, t_x, t_y);
+ v2f_t xyb = _v2f_bilerp(aab, abb, bab, bbb, t_x, t_y);
+
+ return _v2f_lerp(&xya, &xyb, t_z);
+}
+
+
+static inline v2f_t * v2f_trilerp(v2f_t *res, const v2f_t *aaa, const v2f_t *aba, const v2f_t *aab, const v2f_t *abb, const v2f_t *baa, const v2f_t *bba, const v2f_t *bab, const v2f_t *bbb, float t_x, float t_y, float t_z)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_trilerp(aaa, aba, aab, abb, baa, bba, bab, bbb, t_x, t_y, t_z);
+
+ return res;
+}
+
+
+static inline v2f_t _v2f_rand(const v2f_t *min, const v2f_t *max)
+{
+ return (v2f_t){
+ .x = min->x + (float)rand() * (1.f/RAND_MAX) * (max->x - min->x),
+ .y = min->y + (float)rand() * (1.f/RAND_MAX) * (max->y - min->y),
+ };
+}
+
+
+static inline v2f_t * v2f_rand(v2f_t *res, const v2f_t *min, const v2f_t *max)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_rand(min, max);
+
+ return res;
+}
+
+
+static inline v2f_t _v2f_ceil(const v2f_t *v)
+{
+ return (v2f_t){
+ .x = ceilf(v->x),
+ .y = ceilf(v->y),
+ };
+}
+
+
+static inline v2f_t * v2f_ceil(v2f_t *res, const v2f_t *v)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_ceil(v);
+
+ return res;
+}
+
+
+static inline v2f_t _v2f_floor(const v2f_t *v)
+{
+ return (v2f_t){
+ .x = floorf(v->x),
+ .y = floorf(v->y),
+ };
+}
+
+
+static inline v2f_t * v2f_floor(v2f_t *res, const v2f_t *v)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_floor(v);
+
+ return res;
+}
+
+
+static inline v2f_t _v2f_clamp(const v2f_t *v, const v2f_t *min, const v2f_t *max)
+{
+ return (v2f_t){
+ .x = v->x < min->x ? min->x : v->x > max->x ? max->x : v->x,
+ .y = v->y < min->y ? min->y : v->y > max->y ? max->y : v->y,
+ };
+}
+
+
+static inline v2f_t * v2f_clamp(v2f_t *res, const v2f_t *v, const v2f_t *min, const v2f_t *max)
+{
+ if (_v2f_allocated(&res))
+ *res = _v2f_clamp(v, min, max);
+
+ return res;
+}
+
+#endif
diff --git a/src/modules/meta2d/v3f.h b/src/modules/meta2d/v3f.h
new file mode 100644
index 0000000..79aea72
--- /dev/null
+++ b/src/modules/meta2d/v3f.h
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2018-2019 - 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/>.
+ */
+
+/*
+ * 3D vector operations header
+ *
+ * Variants prefixed with _ return a result vector struct by value.
+ *
+ * Variants missing the _ prefix for vector result operations return a pointer
+ * and must be either supplied the result memory as the first "res" argument
+ * which is then returned after being populated with the result's value, or
+ * NULL to allocate space for the result and that pointer is returned after being
+ * populated with its value. When supplying NULL result pointers the functions
+ * must allocate memory and thus may return NULL on OOM, so callers should
+ * check for NULL returns when supplying NULL for "res".
+ *
+ * Example:
+ * v3f_t foo, *foop;
+ *
+ * foo = _v3f_mult(&(v3f_t){1.f,1.f,1.f}, &(v3f_t){2.f,2.f,2.f});
+ *
+ * is equivalent to:
+ *
+ * v3f_mult(&foo, &(v3f_t){1.f,1.f,1.f}, &(v3f_t){2.f,2.f,2.f});
+ *
+ * or dynamically allocated:
+ *
+ * foop = v3f_mult(NULL, &(v3f_t){1.f,1.f,1.f}, &(v3f_t){2.f,2.f,2.f});
+ * free(foop);
+ *
+ * is equivalent to:
+ *
+ * foop = malloc(sizeof(v3f_t));
+ * v3f_mult(foop, &(v3f_t){1.f,1.f,1.f}, &(v3f_t){2.f,2.f,2.f});
+ * free(foop);
+ */
+
+#ifndef _V3F_H
+#define _V3F_H
+
+
+#include <math.h>
+#include <stdlib.h>
+
+
+typedef struct v3f_t {
+ float x, y, z;
+} v3f_t;
+
+
+static inline v3f_t * _v3f_allocated(v3f_t **ptr)
+{
+ if (!*ptr)
+ *ptr = malloc(sizeof(v3f_t));
+
+ return *ptr;
+}
+
+
+static inline v3f_t _v3f_add(const v3f_t *a, const v3f_t *b)
+{
+ return (v3f_t){a->x + b->x, a->y + b->y, a->z + b->z};
+}
+
+
+static inline v3f_t * v3f_add(v3f_t *res, const v3f_t *a, const v3f_t *b)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_add(a, b);
+
+ return res;
+}
+
+
+static inline v3f_t _v3f_sub(const v3f_t *a, const v3f_t *b)
+{
+ return (v3f_t){a->x - b->x, a->y - b->y, a->z - b->z};
+}
+
+
+static inline v3f_t * v3f_sub(v3f_t *res, const v3f_t *a, const v3f_t *b)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_sub(a, b);
+
+ return res;
+}
+
+
+static inline v3f_t _v3f_mult(const v3f_t *a, const v3f_t *b)
+{
+ return (v3f_t){a->x * b->x, a->y * b->y, a->z * b->z};
+}
+
+
+static inline v3f_t * v3f_mult(v3f_t *res, const v3f_t *a, const v3f_t *b)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_mult(a, b);
+
+ return res;
+}
+
+
+static inline v3f_t _v3f_mult_scalar(const v3f_t *v, float scalar)
+{
+ return (v3f_t){v->x * scalar, v->y * scalar, v->z * scalar};
+}
+
+
+static inline v3f_t * v3f_mult_scalar(v3f_t *res, const v3f_t *v, float scalar)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_mult_scalar(v, scalar);
+
+ return res;
+}
+
+
+static inline v3f_t _v3f_div_scalar(const v3f_t *v, float scalar)
+{
+ return _v3f_mult_scalar(v, 1.f / scalar);
+}
+
+
+static inline v3f_t * v3f_div_scalar(v3f_t *res, const v3f_t *v, float scalar)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_div_scalar(v, scalar);
+
+ return res;
+}
+
+
+static inline float v3f_dot(const v3f_t *a, const v3f_t *b)
+{
+ return a->x * b->x + a->y * b->y + a->z * b->z;
+}
+
+
+static inline float v3f_length(const v3f_t *v)
+{
+ return sqrtf(v3f_dot(v, v));
+}
+
+
+static inline float v3f_distance(const v3f_t *a, const v3f_t *b)
+{
+ return v3f_length(v3f_sub(&(v3f_t){}, a, b));
+}
+
+
+static inline float v3f_distance_sq(const v3f_t *a, const v3f_t *b)
+{
+ v3f_t d = _v3f_sub(a, b);
+
+ return v3f_dot(&d, &d);
+}
+
+
+static inline v3f_t _v3f_normalize(const v3f_t *v)
+{
+ return _v3f_mult_scalar(v, 1.0f / v3f_length(v));
+}
+
+
+static inline v3f_t * v3f_normalize(v3f_t *res, const v3f_t *v)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_normalize(v);
+
+ return res;
+}
+
+
+static inline v3f_t _v3f_lerp(const v3f_t *a, const v3f_t *b, float t)
+{
+ v3f_t lerp_a, lerp_b;
+
+ lerp_a = _v3f_mult_scalar(a, 1.0f - t);
+ lerp_b = _v3f_mult_scalar(b, t);
+
+ return _v3f_add(&lerp_a, &lerp_b);
+}
+
+
+static inline v3f_t * v3f_lerp(v3f_t *res, const v3f_t *a, const v3f_t *b, float t)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_lerp(a, b, t);
+
+ return res;
+}
+
+
+static inline v3f_t _v3f_nlerp(const v3f_t *a, const v3f_t *b, float t)
+{
+ v3f_t lerp = _v3f_lerp(a, b, t);
+
+ return _v3f_normalize(&lerp);
+}
+
+
+static inline v3f_t * v3f_nlerp(v3f_t *res, const v3f_t *a, const v3f_t *b, float t)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_nlerp(a, b, t);
+
+ return res;
+}
+
+
+/*
+ * 1 ab-------bb
+ * | | |
+ * | | |
+ * | | |
+ * 0 aa-------ba
+ * t_x: 0---------1
+ * ^
+ * t_y
+ */
+static inline v3f_t _v3f_bilerp(const v3f_t *aa, const v3f_t *ab, const v3f_t *ba, const v3f_t *bb, float t_x, float t_y)
+{
+ v3f_t xa = _v3f_lerp(aa, ba, t_x);
+ v3f_t xb = _v3f_lerp(ab, bb, t_x);
+
+ return _v3f_lerp(&xa, &xb, t_y);
+}
+
+
+static inline v3f_t * v3f_bilerp(v3f_t *res, const v3f_t *aa, const v3f_t *ab, const v3f_t *ba, const v3f_t *bb, float t_x, float t_y)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_bilerp(aa, ab, ba, bb, t_x, t_y);
+
+ return res;
+}
+
+
+/*
+ * abb-------bbb
+ * /| /|
+ * aba-------bba|
+ * | | | |
+ * |aab------|bab
+ * |/ |/
+ * aaa-------baa
+ */
+static inline v3f_t _v3f_trilerp(const v3f_t *aaa, const v3f_t *aba, const v3f_t *aab, const v3f_t *abb, const v3f_t *baa, const v3f_t *bba, const v3f_t *bab, const v3f_t *bbb, float t_x, float t_y, float t_z)
+{
+ v3f_t xya = _v3f_bilerp(aaa, aba, baa, bba, t_x, t_y);
+ v3f_t xyb = _v3f_bilerp(aab, abb, bab, bbb, t_x, t_y);
+
+ return _v3f_lerp(&xya, &xyb, t_z);
+}
+
+
+static inline v3f_t * v3f_trilerp(v3f_t *res, const v3f_t *aaa, const v3f_t *aba, const v3f_t *aab, const v3f_t *abb, const v3f_t *baa, const v3f_t *bba, const v3f_t *bab, const v3f_t *bbb, float t_x, float t_y, float t_z)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_trilerp(aaa, aba, aab, abb, baa, bba, bab, bbb, t_x, t_y, t_z);
+
+ return res;
+}
+
+
+static inline v3f_t _v3f_cross(const v3f_t *a, const v3f_t *b)
+{
+ return (v3f_t){
+ .x = a->y * b->z - a->z * b->y,
+ .y = a->z * b->x - a->x * b->z,
+ .z = a->x * b->y - a->y * b->x,
+ };
+}
+
+
+static inline v3f_t * v3f_cross(v3f_t *res, const v3f_t *a, const v3f_t *b)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_cross(a, b);
+
+ return res;
+}
+
+
+static inline v3f_t _v3f_rand(const v3f_t *min, const v3f_t *max)
+{
+ return (v3f_t){
+ .x = min->x + (float)rand() * (1.f/RAND_MAX) * (max->x - min->x),
+ .y = min->y + (float)rand() * (1.f/RAND_MAX) * (max->y - min->y),
+ .z = min->z + (float)rand() * (1.f/RAND_MAX) * (max->z - min->z),
+ };
+}
+
+
+static inline v3f_t * v3f_rand(v3f_t *res, const v3f_t *min, const v3f_t *max)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_rand(min, max);
+
+ return res;
+}
+
+
+static inline v3f_t _v3f_ceil(const v3f_t *v)
+{
+ return (v3f_t){
+ .x = ceilf(v->x),
+ .y = ceilf(v->y),
+ .z = ceilf(v->z),
+ };
+}
+
+
+static inline v3f_t * v3f_ceil(v3f_t *res, const v3f_t *v)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_ceil(v);
+
+ return res;
+}
+
+
+static inline v3f_t _v3f_floor(const v3f_t *v)
+{
+ return (v3f_t){
+ .x = floorf(v->x),
+ .y = floorf(v->y),
+ .z = floorf(v->z),
+ };
+}
+
+
+static inline v3f_t * v3f_floor(v3f_t *res, const v3f_t *v)
+{
+ if (_v3f_allocated(&res))
+ *res = _v3f_floor(v);
+
+ return res;
+}
+
+#endif
diff --git a/src/rototiller.c b/src/rototiller.c
index 42b9007..d8fa665 100644
--- a/src/rototiller.c
+++ b/src/rototiller.c
@@ -34,6 +34,7 @@ fb_ops_t *fb_ops;
extern rototiller_module_t flui2d_module;
extern rototiller_module_t julia_module;
+extern rototiller_module_t meta2d_module;
extern rototiller_module_t montage_module;
extern rototiller_module_t pixbounce_module;
extern rototiller_module_t plasma_module;
@@ -49,6 +50,7 @@ extern rototiller_module_t swab_module;
static const rototiller_module_t *modules[] = {
&flui2d_module,
&julia_module,
+ &meta2d_module,
&montage_module,
&pixbounce_module,
&plasma_module,
© All Rights Reserved