diff options
Diffstat (limited to 'src/modules')
-rw-r--r-- | src/modules/Makefile.am | 2 | ||||
-rw-r--r-- | src/modules/shapes/Makefile.am | 3 | ||||
-rw-r--r-- | src/modules/shapes/shapes.c | 269 |
3 files changed, 273 insertions, 1 deletions
diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am index e5c9971..d00ac7b 100644 --- a/src/modules/Makefile.am +++ b/src/modules/Makefile.am @@ -1 +1 @@ -SUBDIRS = blinds checkers compose drizzle flui2d julia meta2d montage pixbounce plasma plato ray roto rtv snow sparkler spiro stars submit swab swarm voronoi +SUBDIRS = blinds checkers compose drizzle flui2d julia meta2d montage pixbounce plasma plato ray roto rtv shapes snow sparkler spiro stars submit swab swarm voronoi diff --git a/src/modules/shapes/Makefile.am b/src/modules/shapes/Makefile.am new file mode 100644 index 0000000..84f25fa --- /dev/null +++ b/src/modules/shapes/Makefile.am @@ -0,0 +1,3 @@ +noinst_LTLIBRARIES = libshapes.la +libshapes_la_SOURCES = shapes.c +libshapes_la_CPPFLAGS = -I@top_srcdir@/src diff --git a/src/modules/shapes/shapes.c b/src/modules/shapes/shapes.c new file mode 100644 index 0000000..6c912ca --- /dev/null +++ b/src/modules/shapes/shapes.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2022 - 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 2 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/>. + */ + +/* The impetus for adding this is a desire for adding a variety of shapes + * to modueles/checkers. I had started open-coding shapes like circle, + * rhombus, pinwheel, and star, directly into checkers with a new style= + * setting for choosing which to use instead of the plain filled square. + * + * But it seemed silly to bury this directly in checkers, when checkers + * could trivially call into another module for rendering the filled + * fragment. And as the shapes became more interesting like star/pinwheel, + * it also became clear that parameterizing them to really take advantage + * of their procedural implementation would be a lot of fun. Exposing + * those parameters only as checkers settings, and knobs once available, + * only within checkers, seemed like it'd be really selling things short. + * + * So here we are, shapes is its own module, it's kind of boring ATM. Its + * addition will likely be followed by checkers getting a filler module + * setting, which could then invoke shapes - or any other module. + * + * TODO: + * + * - Add more interesting shapes + * + * - Expose parameters as settings + * + * - Parameterize more things, stuff like twist for the radial shapes + * comes to mind. The simplistic CCW rotation of star/pinwheel should be + * more variable and exposed as settings, etc. + * + * - Go threaded, for ease of implementation this is currently simple + * non-threaded code. In the checkers use case, the individual checkers + * are already being rendered concurrently, so as-is this still becomes + * threaded there. It's just full-frame shapes situations where it + * hurts. + * + * - The presently static shapes like circle and rhombus could be simply + * rendered once @ context_create() into a dense buffer then copied at + * render_fragment() time. The current implementation is very naive and + * slow procedurally redrawing even these constant shapes. But the + * assumption is as more parameterizing is added, all the shapes will + * become dynamic. So there's no sense adding a cache. + */ + + + +#include <errno.h> +#include <math.h> +#include <stdlib.h> +#include <unistd.h> + +#include "til.h" +#include "til_fb.h" + +#define SHAPES_DEFAULT_TYPE SHAPES_TYPE_PINWHEEL + +typedef enum shapes_type_t { + SHAPES_TYPE_CIRCLE, + SHAPES_TYPE_PINWHEEL, + SHAPES_TYPE_RHOMBUS, + SHAPES_TYPE_STAR, +} shapes_type_t; + +typedef struct shapes_setup_t { + til_setup_t til_setup; + shapes_type_t type; +} shapes_setup_t; + +typedef struct shapes_context_t { + shapes_setup_t setup; +} shapes_context_t; + + +static shapes_setup_t shapes_default_setup = { + .type = SHAPES_DEFAULT_TYPE, +}; + + +static void * shapes_create_context(unsigned ticks, unsigned n_cpus, til_setup_t *setup) +{ + shapes_context_t *ctxt; + + if (!setup) + setup = &shapes_default_setup.til_setup; + + ctxt = calloc(1, sizeof(shapes_context_t)); + if (!ctxt) + return NULL; + + ctxt->setup = *(shapes_setup_t *)setup; + + return ctxt; +} + + +static void shapes_destroy_context(void *context) +{ + shapes_context_t *ctxt = context; + + free(ctxt); +} + + +static void shapes_render_fragment(void *context, unsigned ticks, unsigned cpu, til_fb_fragment_t *fragment) +{ + shapes_context_t *ctxt = context; + unsigned size = MIN(fragment->width, fragment->height); + unsigned xoff = (fragment->width - size) >> 1; + unsigned yoff = (fragment->height - size) >> 1; + + /* eventually these should probably get broken out into functions, + * but it's not too unwieldy for now. + */ + switch (ctxt->setup.type) { + case SHAPES_TYPE_CIRCLE: { + int r_sq = (size >> 1); + + r_sq *= r_sq; + + for (int y = yoff, Y = -(size >> 1); y < fragment->height; y++, Y++) { + for (int x = xoff, X = -(size >> 1); x < fragment->width; x++, X++) { + if (Y*Y+X*X <= r_sq) + til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, fragment->x + x, fragment->y + y, 0xffffffff); + else if (!fragment->cleared) + til_fb_fragment_put_pixel_unchecked(fragment, 0, fragment->x + x, fragment->y + y, 0x0); + + } + } + break; + } + + case SHAPES_TYPE_PINWHEEL: { + float s = 2.f / (float)size; + float X, Y; + + Y = -1.f; + for (unsigned y = yoff; y < fragment->height; y++, Y += s) { + X = -1.f; + for (unsigned x = xoff; x < fragment->width; x++, X += s) { + float rad = atan2f(Y, X) + (float)ticks * .001f; + float r = cosf(5.f * rad) * .5f + .5f; + + if (X * X + Y * Y < r * r) + til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, fragment->x + x, fragment->y + y, 0xffffffff); + else if (!fragment->cleared) + til_fb_fragment_put_pixel_unchecked(fragment, 0, fragment->x + x, fragment->y + y, 0x0); + + } + } + break; + } + + case SHAPES_TYPE_RHOMBUS: { + int r = (size >> 1); + + for (unsigned y = yoff, Y = -(size >> 1); y < fragment->height; y++, Y++) { + for (unsigned x = xoff, X = -(size >> 1); x < fragment->width; x++, X++) { + if (abs(Y)+abs(X) <= r) + til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, fragment->x + x, fragment->y + y, 0xffffffff); + else if (!fragment->cleared) + til_fb_fragment_put_pixel_unchecked(fragment, 0, fragment->x + x, fragment->y + y, 0x0); + + } + } + break; + } + + case SHAPES_TYPE_STAR: { + float s = 2.f / (float)size; + float X, Y; + + Y = -1.f; + for (unsigned y = yoff; y < fragment->height; y++, Y += s) { + X = -1.f; + for (unsigned x = xoff; x < fragment->width; x++, X += s) { + float rad = atan2f(Y, X) + (float)ticks * .001f; + float r = (M_2_PI * asinf(sinf(5 * rad) * .5f + .5f)) * .5f + .5f; + /* ^^^^^^^^^^^^^^^^^^^ approximates a triangle wave */ + + if (X * X + Y * Y < r * r) + til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, fragment->x + x, fragment->y + y, 0xffffffff); + else if (!fragment->cleared) + til_fb_fragment_put_pixel_unchecked(fragment, 0, fragment->x + x, fragment->y + y, 0x0); + } + } + break; + } + } +} + + +static int shapes_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup) +{ + const char *type; + const char *type_values[] = { + "circle", + "pinwheel", + "rhombus", + "star", + NULL + }; + int r; + + r = til_settings_get_and_describe_value(settings, + &(til_setting_desc_t){ + .name = "Shape type", + .key = "type", + .regex = "[a-zA-Z]+", + .preferred = type_values[SHAPES_DEFAULT_TYPE], + .values = type_values, + .annotations = NULL + }, + &type, + res_setting, + res_desc); + if (r) + return r; + + if (res_setup) { + int i; + + shapes_setup_t *setup; + + setup = til_setup_new(sizeof(*setup), (void(*)(til_setup_t *))free); + if (!setup) + return -ENOMEM; + + for (i = 0; type_values[i]; i++) { + if (!strcasecmp(type, type_values[i])) { + setup->type = i; + break; + } + } + + if (!type_values[i]) { + til_setup_free(&setup->til_setup); + return -EINVAL; + } + + *res_setup = &setup->til_setup; + } + + return 0; +} + + +til_module_t shapes_module = { + .create_context = shapes_create_context, + .destroy_context = shapes_destroy_context, + .render_fragment = shapes_render_fragment, + .setup = shapes_setup, + .name = "shapes", + .description = "Procedural 2D shapes", + .author = "Vito Caputo <vcaputo@pengaru.com>", + .flags = TIL_MODULE_OVERLAYABLE, +}; |