summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPhilip J Freeman <elektron@halo.nu>2022-08-06 09:40:11 -0700
committerVito Caputo <vcaputo@pengaru.com>2023-12-01 14:00:32 -0800
commitdc59f812eedc39c0e764a19519aeb88856992d12 (patch)
tree1770e4990c126011b0175556026d15ff3959cb8e /src
parenta8d8fecb9d97cc6e4c3ff82771e98243a037691b (diff)
modules/spokes: add spokes module
This module started out as a way to test a line drawing algorithm, but ended up looking interesting enough to include as a module of it's own. #ZephyrCommit #BirdwalkCommit
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1
-rw-r--r--src/modules/Makefile.am1
-rw-r--r--src/modules/spokes/Makefile.am3
-rw-r--r--src/modules/spokes/draw.h16
-rw-r--r--src/modules/spokes/spokes.c347
-rw-r--r--src/til.c2
6 files changed, 370 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index a2a8770..d2fe42b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -62,6 +62,7 @@ libtil_la_LIBADD = \
modules/snow/libsnow.la \
modules/sparkler/libsparkler.la \
modules/spiro/libspiro.la \
+ modules/spokes/libspokes.la \
modules/stars/libstars.la \
modules/strobe/libstrobe.la \
modules/stub/libstub.la \
diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am
index 6ce4786..825295d 100644
--- a/src/modules/Makefile.am
+++ b/src/modules/Makefile.am
@@ -26,6 +26,7 @@ SUBDIRS = \
snow \
sparkler \
spiro \
+ spokes \
stars \
strobe \
stub \
diff --git a/src/modules/spokes/Makefile.am b/src/modules/spokes/Makefile.am
new file mode 100644
index 0000000..099ba66
--- /dev/null
+++ b/src/modules/spokes/Makefile.am
@@ -0,0 +1,3 @@
+noinst_LTLIBRARIES = libspokes.la
+libspokes_la_SOURCES = draw.h spokes.c
+libspokes_la_CPPFLAGS = -I@top_srcdir@/src
diff --git a/src/modules/spokes/draw.h b/src/modules/spokes/draw.h
new file mode 100644
index 0000000..0b68c00
--- /dev/null
+++ b/src/modules/spokes/draw.h
@@ -0,0 +1,16 @@
+#ifndef _DRAW_H
+#define _DRAW_H
+
+#include <stdint.h>
+
+/* helper for scaling rgb colors and packing them into an pixel */
+static inline uint32_t makergb(uint32_t r, uint32_t g, uint32_t b, float intensity)
+{
+ r = (((float)intensity) * r);
+ g = (((float)intensity) * g);
+ b = (((float)intensity) * b);
+
+ return (((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff));
+}
+
+#endif
diff --git a/src/modules/spokes/spokes.c b/src/modules/spokes/spokes.c
new file mode 100644
index 0000000..0aa8eed
--- /dev/null
+++ b/src/modules/spokes/spokes.c
@@ -0,0 +1,347 @@
+#include <math.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "til.h"
+#include "til_fb.h"
+#include "til_module_context.h"
+
+#include "draw.h"
+
+/* Copyright (C) 2022 Philip J. Freeman <elektron@halo.nu> */
+
+#define SPOKES_DEFAULT_ITERATIONS 3
+#define SPOKES_DEFAULT_TWIST 0.0625
+#define SPOKES_DEFAULT_THICKNESS 3
+
+typedef struct spokes_context_t {
+ til_module_context_t til_module_context;
+ int iterations;
+ float twist;
+ int thickness;
+} spokes_context_t;
+
+typedef struct spokes_setup_t {
+ til_setup_t til_setup;
+ int iterations;
+ float twist;
+ int thickness;
+} spokes_setup_t;
+
+static void spokes_draw_line(til_fb_fragment_t *fragment, int x1, int y1, int x2, int y2, uint32_t color, int thickness)
+{
+
+ int x, y, offset;
+
+ if(x1==x2 && y1==y2) {
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x1, y1, color);
+ return;
+ }
+
+ int dx=x2-x1;
+ int dy=y2-y1;
+
+ if(abs(dx)>=abs(dy)) { /* iterate along x */
+ float rate=(float)dy/(float)dx;
+ if(dx>0) {
+ for(x=x1; x<=x2; x++) {
+ y=y1+round((x-x1)*rate);
+ if(y>=0 && y<fragment->height)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, color);
+ for(offset=1; offset<thickness; offset++) {
+ if(y+offset<fragment->height)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y+offset, color);
+ if(y-offset>=0)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y-offset, color);
+ }
+ }
+ } else {
+ for(x=x1; x>=x2; x--) {
+ y=y1+round((x-x1)*rate);
+ if(y>=0 && y<fragment->height)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, color);
+ for(offset=1; offset<thickness; offset++) {
+ if(y+offset<fragment->height)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y+offset, color);
+ if(y-offset>=0)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y-offset, color);
+ }
+ }
+ }
+
+ } else { /* iterate along y */
+ float rate=(float)dx/(float)dy;
+ if(dy>0) {
+ for(y=y1; y<=y2; y++) {
+ x=x1+round((y-y1)*rate);
+ if(x>=0 && x<fragment->width)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, color);
+ for(offset=1; offset<thickness; offset++) {
+ if(x+offset<fragment->width)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x+offset, y, color);
+ if(x-offset>=0)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x-offset, y, color);
+ }
+ }
+ } else {
+ for(y=y1; y>=y2; y--) {
+ x=x1+round((y-y1)*rate);
+ if(x>=0 && x<fragment->width)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, color);
+ for(offset=1; offset<thickness; offset++) {
+ if(x+offset<fragment->width)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x+offset, y, color);
+ if(x-offset>=0)
+ til_fb_fragment_put_pixel_checked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x-offset, y, color);
+ }
+ }
+ }
+ }
+}
+
+static void spokes_draw_segmented_line(til_fb_fragment_t *fragment, int iterations, double theta, int x1, int y1, int x2, int y2, uint32_t color, int thickness)
+{
+ /* recurse until iterations == 0 */
+
+ if(iterations>0) {
+ int midpoint_x=((x1+x2)/2);
+ int midpoint_y=((y1+y2)/2);
+
+ midpoint_x=cos(theta)*(midpoint_x-x1)-sin(theta)*(midpoint_y-y1)+x1;
+ midpoint_y=sin(theta)*(midpoint_x-x1)+cos(theta)*(midpoint_y-y1)+y1;
+
+ /* Check if any of the midpoints are outside of the drawable area and fix them. */
+ if (midpoint_x<0) midpoint_x=0;
+ if (midpoint_x>=fragment->width) midpoint_x=fragment->width-1;
+ if (midpoint_y<0) midpoint_y=0;
+ if (midpoint_y>=fragment->height) midpoint_y=fragment->height-1;
+
+ spokes_draw_segmented_line(fragment, iterations-1, theta*0.5, x1, y1, midpoint_x, midpoint_y, color, thickness);
+ spokes_draw_segmented_line(fragment, iterations-1, theta*-0.5, x2, y2, midpoint_x, midpoint_y, color, thickness);
+ return;
+ }
+ spokes_draw_line(fragment, x1, y1, x2, y2, color, thickness);
+}
+
+static void spokes_render_fragment(til_module_context_t *context, til_stream_t *stream, unsigned ticks, unsigned cpu, til_fb_fragment_t **fragment_ptr)
+{
+ til_fb_fragment_t *fragment = *fragment_ptr;
+
+ int width = fragment->width, height = fragment->height;
+ int stride;
+ int offset;
+ uint32_t color;
+
+ int display_R, display_origin_x, display_origin_y;
+ int origin_x, origin_y;
+
+ spokes_context_t *ctxt = (spokes_context_t *)context;
+
+ /* calculate theta for twist */
+ double theta=M_PI*ctxt->twist;
+
+ /* Based on the fragment's dimensions, calculate the origin and radius of the largest
+ circle that can fully fit in the frame */
+
+ if(width>=height) {
+ display_R=(height-1)*0.5f;
+ display_origin_x=((width-height)*.5f)+display_R;
+ display_origin_y=display_R;
+ } else {
+ display_R=(width-1)*0.5f;
+ display_origin_x=display_R;
+ display_origin_y=((height-width)*.5f)+display_R;
+ }
+
+ /* calculate a moving origin for all the lines in this frame based on ticks */
+ origin_x=display_origin_x+cos((float)ticks*0.001)*display_R*0.7f;
+ origin_y=display_origin_y+sin((float)ticks*0.001)*display_R*0.7f;
+
+ /* calculate an offset for outer line endpoints based on ticks */
+ offset=(int)round((float)ticks*0.1); /* use the ticks. */
+
+ /* rotate through RGB color space slowly based on ticks */
+ color=makergb(
+ (int)round(255.0*sinf((float)ticks*0.00001)),
+ (int)round(255.0*sinf((float)ticks*0.00001+0.6667*M_PI)),
+ (int)round(255.0*sinf((float)ticks*0.00001+1.3333*M_PI)),
+ 1);
+
+ /* find the largest even stride for the fragments dimensions */
+ stride=1;
+ for(int i=2; i<(width+height)/2; i++) {
+ if((width+height)%i==0) {
+ stride=i;
+ }
+ }
+
+ /* we're setup now to draw some shit */
+ til_fb_fragment_clear(fragment);
+
+ for(int i=0; i<=width+height-stride; i+=stride){ /* iterate over half the perimiter at a time... */
+ int perimiter_x=-1, perimiter_y=-1;
+ if(i+offset%stride<width) {
+ perimiter_x=i+offset%stride;
+ perimiter_y=0;
+ } else {
+ perimiter_x=width-1;
+ perimiter_y=(i-width)+offset%stride;
+ }
+
+ spokes_draw_segmented_line(fragment, ctxt->iterations, theta, origin_x, origin_y, perimiter_x, perimiter_y, color, ctxt->thickness);
+
+ /* Calculate and draw the mirror line... */
+ perimiter_x=abs(perimiter_x-width);
+ perimiter_y=abs(perimiter_y-height);
+ spokes_draw_segmented_line(fragment, ctxt->iterations, theta, origin_x, origin_y, perimiter_x, perimiter_y, color, ctxt->thickness);
+ }
+}
+
+
+static til_module_context_t * spokes_create_context(const til_module_t *module, til_stream_t *stream, unsigned seed, unsigned ticks, unsigned n_cpus, til_setup_t *setup)
+{
+ spokes_context_t *ctxt;
+
+ ctxt = til_module_context_new(module, sizeof(spokes_context_t), stream, seed, ticks, n_cpus, setup);
+ if (!ctxt)
+ return NULL;
+ ctxt->iterations = ((spokes_setup_t *)setup)->iterations;
+ ctxt->twist = ((spokes_setup_t *)setup)->twist;
+ ctxt->thickness = ((spokes_setup_t *)setup)->thickness;
+ return &ctxt->til_module_context;
+
+}
+
+
+static void spokes_destroy_context(til_module_context_t *context)
+{
+ free(context);
+
+}
+
+
+int spokes_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup);
+
+til_module_t spokes_module = {
+ .create_context = spokes_create_context,
+ .destroy_context = spokes_destroy_context,
+ .render_fragment = spokes_render_fragment,
+ .setup = spokes_setup,
+ .name = "spokes",
+ .description = "Twisted spokes",
+ .author = "Philip J Freeman <elektron@halo.nu>",
+ .flags = TIL_MODULE_OVERLAYABLE,
+};
+
+
+int spokes_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup)
+{
+ til_setting_t *iterations;
+ const char *iterations_values[] = {
+ "1",
+ "2",
+ "3",
+ "4",
+ NULL
+ };
+ til_setting_t *twist;
+ const char *twist_values[] = {
+ "-4.0",
+ "-2.0",
+ "-1.0",
+ "-0.5",
+ "-0.25",
+ "-0.125",
+ "-0.0625",
+ "-0.03125",
+ "-0.015125",
+ "0.0",
+ "0.015125",
+ "0.03125",
+ "0.0625",
+ "0.125",
+ "0.25",
+ "0.5",
+ "1.0",
+ "2.0",
+ "4.0",
+ NULL
+ };
+ til_setting_t *thickness;
+ const char *thickness_values[] = {
+ "1",
+ "2",
+ "3",
+ "5",
+ NULL
+ };
+ int r;
+
+ r = til_settings_get_and_describe_setting(settings,
+ &(til_setting_spec_t){
+ .name = "Iterations",
+ .key = "iterations",
+ .regex = "[0-9]+",
+ .preferred = TIL_SETTINGS_STR(SPOKES_DEFAULT_ITERATIONS),
+ .values = iterations_values,
+ .annotations = NULL
+ },
+ &iterations,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+
+ r = til_settings_get_and_describe_setting(settings,
+ &(til_setting_spec_t){
+ .name = "Twist",
+ .key = "twist",
+ .regex = "[0-9]+\\.[0-9]+",
+ .preferred = TIL_SETTINGS_STR(SPOKES_DEFAULT_TWIST),
+ .values = twist_values,
+ .annotations = NULL
+ },
+ &twist,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+
+ r = til_settings_get_and_describe_setting(settings,
+ &(til_setting_spec_t){
+ .name = "Thickness",
+ .key = "thickness",
+ .regex = "[0-9]+",
+ .preferred = TIL_SETTINGS_STR(SPOKES_DEFAULT_THICKNESS),
+ .values = thickness_values,
+ .annotations = NULL
+ },
+ &thickness,
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+
+ if (res_setup) {
+ spokes_setup_t *setup;
+
+ setup = til_setup_new(settings,sizeof(*setup), NULL, &spokes_module);
+ if (!setup)
+ return -ENOMEM;
+
+ if (sscanf(iterations->value, "%i", &setup->iterations) != 1)
+ return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, iterations, res_setting, -EINVAL);
+
+ if (sscanf(twist->value, "%f", &setup->twist) != 1)
+ return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, twist, res_setting, -EINVAL);
+
+ if (sscanf(thickness->value, "%i", &setup->thickness) != 1)
+ return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, thickness, res_setting, -EINVAL);
+
+ *res_setup = &setup->til_setup;
+ }
+
+ return 0;
+}
diff --git a/src/til.c b/src/til.c
index 18733d9..709aabb 100644
--- a/src/til.c
+++ b/src/til.c
@@ -53,6 +53,7 @@ extern til_module_t shapes_module;
extern til_module_t signals_module;
extern til_module_t snow_module;
extern til_module_t sparkler_module;
+extern til_module_t spokes_module;
extern til_module_t spiro_module;
extern til_module_t stars_module;
extern til_module_t strobe_module;
@@ -97,6 +98,7 @@ static const til_module_t *modules[] = {
&snow_module,
&sparkler_module,
&spiro_module,
+ &spokes_module,
&stars_module,
&strobe_module,
&stub_module,
© All Rights Reserved