diff options
-rw-r--r-- | src/modules/swarm/swarm.c | 219 |
1 files changed, 197 insertions, 22 deletions
diff --git a/src/modules/swarm/swarm.c b/src/modules/swarm/swarm.c index 7c8caeb..8ff4812 100644 --- a/src/modules/swarm/swarm.c +++ b/src/modules/swarm/swarm.c @@ -27,8 +27,10 @@ #include "til.h" #include "til_fb.h" -#define SWARM_SIZE (32 * 1024) -#define SWARM_ZCONST 4.f +typedef enum swarm_draw_style_t { + SWARM_DRAW_STYLE_POINTS, /* simple opaque pixel per particle */ + SWARM_DRAW_STYLE_LINES, /* simple opaque lines per particle, oriented and sized by direction and velocity */ +} swarm_draw_style_t; typedef struct v3f_t { float x, y, z; @@ -50,6 +52,12 @@ typedef struct swarm_context_t { boid_t boids[]; } swarm_context_t; +#define SWARM_SIZE (32 * 1024) +#define SWARM_ZCONST 4.f +#define SWARM_DEFAULT_STYLE SWARM_DRAW_STYLE_LINES + +static swarm_draw_style_t swarm_draw_style = SWARM_DEFAULT_STYLE; + static inline float randf(float min, float max) { @@ -111,6 +119,16 @@ static inline void v3f_normalize(v3f_t *v) } +static inline v3f_t v3f_invert(v3f_t v) +{ + return (v3f_t){ + .x = -v.x, + .y = -v.y, + .z = -v.z, + }; +} + + static v3f_t v3f_lerp(v3f_t a, v3f_t b, float t) { return (v3f_t){ @@ -184,10 +202,34 @@ static void swarm_update(swarm_context_t *ctxt, unsigned ticks) { /* [0] = leader */ float r = M_PI * 2 * ((cosf((float)ticks * .001f) * .5f + .5f)); + v3f_t newpos = { + .x = cosf(r), + .y = sinf(r), + .z = cosf(r * 2.f), + }; + boid_t *b = &ctxt->boids[0]; + + if (newpos.x != b->position.x || + newpos.y != b->position.y || + newpos.z != b->position.z) { + + /* XXX: this must be conditional on position changing otherwise + * it could produce a zero direction vector, making normalize + * spit out NaN, and things fall apart. + */ + + b->direction = v3f_sub(b->position, newpos); + b->velocity = v3f_len(b->direction); + v3f_normalize(&b->direction); + b->position = newpos; - ctxt->boids[0].position.x = cosf(r); - ctxt->boids[0].position.y = sinf(r); - ctxt->boids[0].position.z = cosf(r * 2.f); + } +#if 0 + printf("pos={%f,%f,%f},dir={%f,%f,%f},v=%f\n", + b->position.x, b->position.y, b->position.z, + b->direction.x, b->direction.y, b->direction.z, + b->velocity); +#endif } /* characterize the current swarm */ @@ -236,37 +278,169 @@ static void swarm_update(swarm_context_t *ctxt, unsigned ticks) } -static void swarm_render_fragment(void *context, unsigned ticks, unsigned cpu, til_fb_fragment_t *fragment) +static inline v2f_t swarm_project_point(swarm_context_t *ctxt, v3f_t *point) { - swarm_context_t *ctxt = context; + return (v2f_t) { + .x = point->x / (point->z + SWARM_ZCONST + ctxt->ztweak), + .y = point->y / (point->z + SWARM_ZCONST + ctxt->ztweak), + }; +} - swarm_update(ctxt, ticks); - til_fb_fragment_zero(fragment); +static inline v2f_t swarm_scale(v2f_t normcoord, v2f_t scale) +{ + return (v2f_t) { + .x = normcoord.x * scale.x + scale.x, + .y = normcoord.y * scale.y + scale.y, + }; +} - { - float fw = fragment->frame_width, fh = fragment->frame_height; - uint32_t color = color_to_uint32(ctxt->color); - fw *= .5f; - fh *= .5f; +static inline v2f_t swarm_clip(v2f_t coord, til_fb_fragment_t *fragment) +{ +// printf("coord={%f,%f}\n", coord.x, coord.y); - for (unsigned i = 0; i < SWARM_SIZE; i++) { - boid_t *b = &ctxt->boids[i]; - v2f_t nc; + return (v2f_t) { + .x = coord.x < 0.f ? 0.f : (coord.x > (fragment->frame_width - 1) ? (fragment->frame_width - 1) : coord.x), + .y = coord.y < 0.f ? 0.f : (coord.y > (fragment->frame_height - 1) ? (fragment->frame_height - 1) : coord.y), + }; +} - 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; +static void swarm_draw_as_points(swarm_context_t *ctxt, til_fb_fragment_t *fragment) +{ + v2f_t scale = (v2f_t){ + .x = fragment->frame_width * .5f, + .y = fragment->frame_height * .5f, + }; + uint32_t color = color_to_uint32(ctxt->color); - til_fb_fragment_put_pixel_checked(fragment, nc.x, nc.y, color); + for (unsigned i = 0; i < SWARM_SIZE; i++) { + boid_t *b = &ctxt->boids[i]; + v2f_t nc = swarm_scale(swarm_project_point(ctxt, &b->position), scale); + + til_fb_fragment_put_pixel_checked(fragment, nc.x, nc.y, color); + } +} + + +static void draw_line_unchecked(til_fb_fragment_t *fragment, int x1, int y1, int x2, int y2, uint32_t color) +{ + int x_delta = x2 - x1; + int y_delta = y2 - y1; + int sdx = x_delta < 0 ? -1 : 1; + int sdy = y_delta < 0 ? -1 : 1; + + x_delta = abs(x_delta); + y_delta = abs(y_delta); + + if (x_delta >= y_delta) { + /* X-major */ + for (int minor = 0, x = 0; x <= x_delta; x++, x1 += sdx, minor += y_delta) { + if (minor >= x_delta) { + y1 += sdy; + minor -= x_delta; + } + + til_fb_fragment_put_pixel_unchecked(fragment, x1, y1, color); } + } else { + /* Y-major */ + for (int minor = 0, y = 0; y <= y_delta; y++, y1 += sdy, minor += x_delta) { + if (minor >= y_delta) { + x1 += sdx; + minor -= y_delta; + } + + til_fb_fragment_put_pixel_unchecked(fragment, x1, y1, color); + } + } +} + + +static void swarm_draw_as_lines(swarm_context_t *ctxt, til_fb_fragment_t *fragment) +{ + v2f_t scale = (v2f_t){ + .x = fragment->frame_width * .5f, + .y = fragment->frame_height * .5f, + }; + uint32_t color = color_to_uint32(ctxt->color); + + /* this is similar to draw_as_points(), but derives two 3D points per boid, + * connecting them with a line in 2D. + */ + for (unsigned i = 0; i < SWARM_SIZE; i++) { + boid_t *b = &ctxt->boids[i]; + v3f_t p1, p2; + v2f_t nc1, nc2; + + p1 = v3f_add(b->position, v3f_mult_scalar(b->direction, b->velocity)); + p2 = v3f_add(b->position, v3f_mult_scalar(v3f_invert(b->direction), b->velocity)); + + /* don't bother drawing anything too close/behind the viewer, it + * just produces diagonal lines across the entire frame. + */ + if (p1.z < -SWARM_ZCONST && p2.z < -SWARM_ZCONST) + continue; + + nc1 = swarm_clip(swarm_scale(swarm_project_point(ctxt, &p1), scale), fragment); + nc2 = swarm_clip(swarm_scale(swarm_project_point(ctxt, &p2), scale), fragment); + + draw_line_unchecked(fragment, nc1.x, nc1.y, nc2.x, nc2.y, color); + } +} + + +static void swarm_render_fragment(void *context, unsigned ticks, unsigned cpu, til_fb_fragment_t *fragment) +{ + swarm_context_t *ctxt = context; + + swarm_update(ctxt, ticks); + + til_fb_fragment_zero(fragment); + + switch (swarm_draw_style) { + case SWARM_DRAW_STYLE_POINTS: + return swarm_draw_as_points(ctxt, fragment); + case SWARM_DRAW_STYLE_LINES: + return swarm_draw_as_lines(ctxt, fragment); } } +static int swarm_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc) +{ + const char *styles[] = { + "points", + "lines", + NULL, + }; + const char *style; + int r; + + r = til_settings_get_and_describe_value(settings, + &(til_setting_desc_t){ + .name = "Particle drawing style", + .key = "style", + .values = styles, + .preferred = styles[SWARM_DEFAULT_STYLE], + .annotations = NULL + }, + &style, + res_setting, + res_desc); + if (r) + return r; + + for (int i = 0; styles[i]; i++) { + if (!strcmp(styles[i], style)) + swarm_draw_style = i; + } + + return 0; +} + + til_module_t swarm_module = { .create_context = swarm_create_context, .destroy_context = swarm_destroy_context, @@ -274,4 +448,5 @@ til_module_t swarm_module = { .name = "swarm", .description = "\"Boids\"-inspired particle swarm in 3D", .author = "Vito Caputo <vcaputo@pengaru.com>", + .setup = swarm_setup, }; |