diff --git a/source/engine/debug/debugdraw.c b/source/engine/debug/debugdraw.c index 46dbfaf..73ee8da 100644 --- a/source/engine/debug/debugdraw.c +++ b/source/engine/debug/debugdraw.c @@ -11,6 +11,9 @@ #include "stb_ds.h" #include "sokol/sokol_gfx.h" +#define PAR_STREAMLINES_IMPLEMENTATION +#include "par/par_streamlines.h" + #include "font.h" static sg_pipeline grid_pipe; @@ -292,7 +295,7 @@ void draw_edge(cpVect *points, int n, struct color color, int thickness) void draw_circle(int x, int y, float radius, int pixels, float *color, int fill) { - float cv[circle_vert_c] = {0}; + float cv[circle_vert_c]; cv[0] = color[0]; cv[1] = color[1]; cv[2] = color[2]; diff --git a/source/engine/texture.c b/source/engine/texture.c index 1eb2904..df3bb93 100644 --- a/source/engine/texture.c +++ b/source/engine/texture.c @@ -7,6 +7,9 @@ #include #include #include + +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize.h" #include struct glrect ST_UNIT = {0.f, 1.f, 0.f, 1.f}; @@ -22,6 +25,19 @@ struct Texture *texture_notex() { return texture_pullfromfile("./icons/no_tex.png"); } +int mip_levels(int width, int height) +{ + int levels = 0; + while ( width > 1 && height > 1 ) + { + width >>= 1; + height >>= 1; + levels++; + } + return levels; + +} + /* If an empty string or null is put for path, loads default texture */ struct Texture *texture_pullfromfile(const char *path) { if (!path) return texture_notex(); @@ -51,18 +67,42 @@ struct Texture *texture_pullfromfile(const char *path) { } else { filter = SG_FILTER_LINEAR; } + + sg_image_data sg_img_data; + + int mips = mip_levels(tex->width, tex->height)+1; + + int mipw, miph; + mipw = tex->width; + miph = tex->height; + + sg_img_data.subimage[0][0] = (sg_range){ .ptr = data, .size = mipw*miph*4 }; + + unsigned char *mipdata[mips]; + mipdata[0] = data; + + for (int i = 1; i < mips; i++) { + int w, h; + w = mipw>>1; + h = miph>>1; + mipdata[i] = malloc(w * h * 4); + stbir_resize_uint8(mipdata[i-1], mipw, miph, 0, mipdata[i], w, h, 0, 4); + sg_img_data.subimage[0][i] = (sg_range){ .ptr = mipdata[i], .size = w*h*4 }; + + mipw = w; + miph = h; + } tex->id = sg_make_image(&(sg_image_desc){ .type = SG_IMAGETYPE_2D, .width = tex->width, .height = tex->height, .usage = SG_USAGE_IMMUTABLE, - .min_filter = filter, - .mag_filter = filter, - .max_anisotropy = 16, - .data.subimage[0][0] = { - .ptr = data, - .size = tex->width * tex->height * 4}}); + .min_filter = SG_FILTER_NEAREST_MIPMAP_NEAREST, + .mag_filter = SG_FILTER_NEAREST, + .num_mipmaps = mips, + .data = sg_img_data + }); if (shlen(texhash) == 0) sh_new_arena(texhash); diff --git a/source/engine/thirdparty/par/par_bluenoise.h b/source/engine/thirdparty/par/par_bluenoise.h new file mode 100644 index 0000000..7f81008 --- /dev/null +++ b/source/engine/thirdparty/par/par_bluenoise.h @@ -0,0 +1,563 @@ +// BLUENOISE :: https://github.com/prideout/par +// Generator for infinite 2D point sequences using Recursive Wang Tiles. +// +// In addition to this source code, you'll need to download one of the following +// tilesets, the first being 2 MB while the other is 257 KB. The latter cheats +// by referencing the point sequence from the first tile for all 8 tiles. This +// obviously produces poor results, but in many contexts, it isn't noticeable. +// +// https://prideout.net/assets/bluenoise.bin +// https://prideout.net/assets/bluenoise.trimmed.bin +// +// The code herein is an implementation of the algorithm described in: +// +// Recursive Wang Tiles for Real-Time Blue Noise +// Johannes Kopf, Daniel Cohen-Or, Oliver Deussen, Dani Lischinski +// ACM Transactions on Graphics 25, 3 (Proc. SIGGRAPH 2006) +// +// If you use this software for research purposes, please cite the above paper +// in any resulting publication. +// +// EXAMPLE +// +// Generate point samples whose density is guided by a 512x512 grayscale image: +// +// int npoints; +// float* points; +// int maxpoints = 1e6; +// float density = 30000; +// par_bluenoise_context* ctx; +// ctx = par_bluenoise_from_file("bluenoise.bin", maxpoints); +// par_bluenoise_density_from_gray(ctx, source_pixels, 512, 512, 1); +// points = par_bluenoise_generate(ctx, density, &npoints); +// ... Draw points here. Each point is a three-tuple of (X Y RANK). +// par_bluenoise_free(ctx); +// +// Distributed under the MIT License, see bottom of file. + +#ifndef PAR_BLUENOISE_H +#define PAR_BLUENOISE_H + +#ifdef __cplusplus +extern "C" { +#endif + +// ----------------------------------------------------------------------------- +// BEGIN PUBLIC API +// ----------------------------------------------------------------------------- + +typedef unsigned char par_byte; + +// Encapsulates a tile set and an optional density function. +typedef struct par_bluenoise_context_s par_bluenoise_context; + +// Creates a bluenoise context using the given tileset. The first argument is +// the file path the bin file. The second argument is the maximum number of +// points that you expect to ever be generated. +par_bluenoise_context* par_bluenoise_from_file(char const* path, int maxpts); + +// Creates a bluenoise context using the given tileset. The first and second +// arguments describe a memory buffer containing the contents of the bin file. +// The third argument is the maximum number of points that you expect to ever +// be generated. +par_bluenoise_context* par_bluenoise_from_buffer( + par_byte const* buffer, int nbytes, int maxpts); + +// Sets up a scissoring rectangle using the given lower-left and upper-right +// coordinates. By default the scissor encompasses [-0.5, -0.5] - [0.5, 0.5], +// which is the entire sampling domain for the two "generate" methods. +void par_bluenoise_set_viewport( + par_bluenoise_context*, float left, float bottom, float right, float top); + +// Sets up a reference window size. The only purpose of this is to ensure +// that apparent density remains constant when the window gets resized. +// Clients should call this *before* calling par_bluenoise_set_viewport. +void par_bluenoise_set_window(par_bluenoise_context*, int width, int height); + +// Frees all memory associated with the given bluenoise context. +void par_bluenoise_free(par_bluenoise_context* ctx); + +// Copies a grayscale image into the bluenoise context to guide point density. +// Darker regions generate a higher number of points. The given bytes-per-pixel +// value is the stride between pixels. +void par_bluenoise_density_from_gray(par_bluenoise_context* ctx, + const unsigned char* pixels, int width, int height, int bpp); + +// Creates a binary mask to guide point density. The given bytes-per-pixel +// value is the stride between pixels, which must be 4 or less. +void par_bluenoise_density_from_color(par_bluenoise_context* ctx, + const unsigned char* pixels, int width, int height, int bpp, + unsigned int background_color, int invert); + +// Generates samples using Recursive Wang Tiles. This is really fast! +// The returned pointer is a list of three-tuples, where XY are in [-0.5, +0.5] +// and Z is a rank value that can be used to create a progressive ordering. +// The caller should not free the returned pointer. +float* par_bluenoise_generate( + par_bluenoise_context* ctx, float density, int* npts); + +// Generates an ordered sequence of tuples with the specified sequence length. +// This is slower than the other "generate" method because it uses a dumb +// backtracking method to determine a reasonable density value, and it +// automatically sorts the output by rank. The dims argument must be 2 or more; +// it represents the desired stride (in floats) between consecutive verts in the +// returned data buffer. +float* par_bluenoise_generate_exact( + par_bluenoise_context* ctx, int npts, int dims); + +// Performs an in-place sort of 3-tuples, based on the 3rd component, then +// replaces the 3rd component with an index. +void par_bluenoise_sort_by_rank(float* pts, int npts); + +#ifndef PAR_PI +#define PAR_PI (3.14159265359) +#define PAR_MIN(a, b) (a > b ? b : a) +#define PAR_MAX(a, b) (a > b ? a : b) +#define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) +#define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#define PAR_SQR(a) ((a) * (a)) +#endif + +#ifndef PAR_MALLOC +#define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) +#define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) +#define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N))) +#define PAR_FREE(BUF) free(BUF) +#endif + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- + +#ifdef PAR_BLUENOISE_IMPLEMENTATION +#include +#include +#include +#include +#include + +#define PAR_MINI(a, b) ((a < b) ? a : b) +#define PAR_MAXI(a, b) ((a > b) ? a : b) + +typedef struct { + float x; + float y; +} par_vec2; + +typedef struct { + float x; + float y; + float rank; +} par_vec3; + +typedef struct { + int n, e, s, w; + int nsubtiles, nsubdivs, npoints, nsubpts; + int** subdivs; + par_vec2* points; + par_vec2* subpts; +} par_tile; + +struct par_bluenoise_context_s { + par_vec3* points; + par_tile* tiles; + float global_density; + float left, bottom, right, top; + int ntiles, nsubtiles, nsubdivs; + int npoints; + int maxpoints; + int density_width; + int density_height; + unsigned char* density; + float mag; + int window_width; + int window_height; + int abridged; +}; + +static float sample_density(par_bluenoise_context* ctx, float x, float y) +{ + unsigned char* density = ctx->density; + if (!density) { + return 1; + } + int width = ctx->density_width; + int height = ctx->density_height; + y = 1 - y; + x -= 0.5; + y -= 0.5; + float tx = x * PAR_MAXI(width, height); + float ty = y * PAR_MAXI(width, height); + x += 0.5; + y += 0.5; + tx += width / 2; + ty += height / 2; + int ix = PAR_CLAMP((int) tx, 0, width - 2); + int iy = PAR_CLAMP((int) ty, 0, height - 2); + return density[iy * width + ix] / 255.0f; +} + +static void recurse_tile( + par_bluenoise_context* ctx, par_tile* tile, float x, float y, int level) +{ + float left = ctx->left, right = ctx->right; + float top = ctx->top, bottom = ctx->bottom; + float mag = ctx->mag; + float tileSize = 1.f / powf(ctx->nsubtiles, level); + if (x + tileSize < left || x > right || y + tileSize < bottom || y > top) { + return; + } + float depth = powf(ctx->nsubtiles, 2 * level); + float threshold = mag / depth * ctx->global_density - tile->npoints; + int ntests = PAR_MINI(tile->nsubpts, threshold); + float factor = 1.f / mag * depth / ctx->global_density; + for (int i = 0; i < ntests; i++) { + float px = x + tile->subpts[i].x * tileSize; + float py = y + tile->subpts[i].y * tileSize; + if (px < left || px > right || py < bottom || py > top) { + continue; + } + if (sample_density(ctx, px, py) < (i + tile->npoints) * factor) { + continue; + } + ctx->points[ctx->npoints].x = px - 0.5; + ctx->points[ctx->npoints].y = py - 0.5; + ctx->points[ctx->npoints].rank = (level + 1) + i * factor; + ctx->npoints++; + if (ctx->npoints >= ctx->maxpoints) { + return; + } + } + const float scale = tileSize / ctx->nsubtiles; + if (threshold <= tile->nsubpts) { + return; + } + level++; + for (int ty = 0; ty < ctx->nsubtiles; ty++) { + for (int tx = 0; tx < ctx->nsubtiles; tx++) { + int tileIndex = tile->subdivs[0][ty * ctx->nsubtiles + tx]; + par_tile* subtile = &ctx->tiles[tileIndex]; + recurse_tile(ctx, subtile, x + tx * scale, y + ty * scale, level); + } + } +} + +void par_bluenoise_set_window(par_bluenoise_context* ctx, int width, int height) +{ + ctx->window_width = width; + ctx->window_height = height; +} + +void par_bluenoise_set_viewport(par_bluenoise_context* ctx, float left, + float bottom, float right, float top) +{ + // Transform [-.5, +.5] to [0, 1] + left = ctx->left = left + 0.5; + right = ctx->right = right + 0.5; + bottom = ctx->bottom = bottom + 0.5; + top = ctx->top = top + 0.5; + + // Determine magnification factor BEFORE clamping. + float scale = 1000 * (top - bottom) / ctx->window_height; + ctx->mag = powf(scale, -2); + + // The density function is only sampled in [0, +1]. + ctx->left = PAR_CLAMP(left, 0, 1); + ctx->right = PAR_CLAMP(right, 0, 1); + ctx->bottom = PAR_CLAMP(bottom, 0, 1); + ctx->top = PAR_CLAMP(top, 0, 1); +} + +float* par_bluenoise_generate( + par_bluenoise_context* ctx, float density, int* npts) +{ + ctx->global_density = density; + ctx->npoints = 0; + float left = ctx->left; + float right = ctx->right; + float bottom = ctx->bottom; + float top = ctx->top; + float mag = ctx->mag; + + int ntests = PAR_MINI(ctx->tiles[0].npoints, mag * ctx->global_density); + float factor = 1.f / mag / ctx->global_density; + for (int i = 0; i < ntests; i++) { + float px = ctx->tiles[0].points[i].x; + float py = ctx->tiles[0].points[i].y; + if (px < left || px > right || py < bottom || py > top) { + continue; + } + if (sample_density(ctx, px, py) < (i + 1) * factor) { + continue; + } + ctx->points[ctx->npoints].x = px - 0.5; + ctx->points[ctx->npoints].y = py - 0.5; + ctx->points[ctx->npoints].rank = i * factor; + ctx->npoints++; + if (ctx->npoints >= ctx->maxpoints) { + break; + } + } + recurse_tile(ctx, &ctx->tiles[0], 0, 0, 0); + *npts = ctx->npoints; + return &ctx->points->x; +} + +#define freadi() \ + *((int*) ptr); \ + ptr += sizeof(int) + +#define freadf() \ + *((float*) ptr); \ + ptr += sizeof(float) + +static par_bluenoise_context* par_bluenoise_create( + char const* filepath, int nbytes, int maxpts) +{ + par_bluenoise_context* ctx = PAR_MALLOC(par_bluenoise_context, 1); + ctx->maxpoints = maxpts; + ctx->points = PAR_MALLOC(par_vec3, maxpts); + ctx->density = 0; + ctx->abridged = 0; + par_bluenoise_set_window(ctx, 1024, 768); + par_bluenoise_set_viewport(ctx, -.5, -.5, .5, .5); + + char* buf = 0; + if (nbytes == 0) { + FILE* fin = fopen(filepath, "rb"); + assert(fin); + fseek(fin, 0, SEEK_END); + nbytes = (int) ftell(fin); + fseek(fin, 0, SEEK_SET); + buf = PAR_MALLOC(char, nbytes); + int consumed = (int) fread(buf, nbytes, 1, fin); + assert(consumed == 1); + fclose(fin); + } + + char const* ptr = buf ? buf : filepath; + int ntiles = ctx->ntiles = freadi(); + int nsubtiles = ctx->nsubtiles = freadi(); + int nsubdivs = ctx->nsubdivs = freadi(); + par_tile* tiles = ctx->tiles = PAR_MALLOC(par_tile, ntiles); + for (int i = 0; i < ntiles; i++) { + tiles[i].n = freadi(); + tiles[i].e = freadi(); + tiles[i].s = freadi(); + tiles[i].w = freadi(); + tiles[i].subdivs = PAR_MALLOC(int*, nsubdivs); + for (int j = 0; j < nsubdivs; j++) { + int* subdiv = PAR_MALLOC(int, PAR_SQR(nsubtiles)); + for (int k = 0; k < PAR_SQR(nsubtiles); k++) { + subdiv[k] = freadi(); + } + tiles[i].subdivs[j] = subdiv; + } + tiles[i].npoints = freadi(); + tiles[i].points = PAR_MALLOC(par_vec2, tiles[i].npoints); + for (int j = 0; j < tiles[i].npoints; j++) { + tiles[i].points[j].x = freadf(); + tiles[i].points[j].y = freadf(); + } + tiles[i].nsubpts = freadi(); + tiles[i].subpts = PAR_MALLOC(par_vec2, tiles[i].nsubpts); + for (int j = 0; j < tiles[i].nsubpts; j++) { + tiles[i].subpts[j].x = freadf(); + tiles[i].subpts[j].y = freadf(); + } + + // The following hack allows for an optimization whereby + // the first tile's point set is re-used for every other tile. + // This goes against the entire purpose of Recursive Wang Tiles, + // but in many applications the qualatitive loss is not + // observable, and the footprint savings are huge (10x). + + if (tiles[i].npoints == 0) { + ctx->abridged = 1; + tiles[i].npoints = tiles[0].npoints; + tiles[i].points = tiles[0].points; + tiles[i].nsubpts = tiles[0].nsubpts; + tiles[i].subpts = tiles[0].subpts; + } + } + free(buf); + return ctx; +} + +par_bluenoise_context* par_bluenoise_from_file(char const* path, int maxpts) +{ + return par_bluenoise_create(path, 0, maxpts); +} + +par_bluenoise_context* par_bluenoise_from_buffer( + par_byte const* buffer, int nbytes, int maxpts) +{ + return par_bluenoise_create((char const*) buffer, nbytes, maxpts); +} + +void par_bluenoise_density_from_gray(par_bluenoise_context* ctx, + const unsigned char* pixels, int width, int height, int bpp) +{ + ctx->density_width = width; + ctx->density_height = height; + ctx->density = PAR_MALLOC(unsigned char, width * height); + unsigned char* dst = ctx->density; + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + *dst++ = 255 - (*pixels); + pixels += bpp; + } + } +} + +void par_bluenoise_density_from_color(par_bluenoise_context* ctx, + const unsigned char* pixels, int width, int height, int bpp, + unsigned int background_color, int invert) +{ + unsigned int bkgd = background_color; + ctx->density_width = width; + ctx->density_height = height; + ctx->density = PAR_MALLOC(unsigned char, width * height); + unsigned char* dst = ctx->density; + unsigned int mask = 0x000000ffu; + if (bpp > 1) { + mask |= 0x0000ff00u; + } + if (bpp > 2) { + mask |= 0x00ff0000u; + } + if (bpp > 3) { + mask |= 0xff000000u; + } + assert(bpp <= 4); + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + unsigned int val = (*((unsigned int*) pixels)) & mask; + val = invert ? (val == bkgd) : (val != bkgd); + *dst++ = val ? 255 : 0; + pixels += bpp; + } + } +} + +void par_bluenoise_free(par_bluenoise_context* ctx) +{ + free(ctx->points); + for (int t = 0; t < ctx->ntiles; t++) { + for (int s = 0; s < ctx->nsubdivs; s++) { + free(ctx->tiles[t].subdivs[s]); + } + free(ctx->tiles[t].subdivs); + if (t == 0 || !ctx->abridged) { + free(ctx->tiles[t].points); + free(ctx->tiles[t].subpts); + } + } + free(ctx->tiles); + free(ctx->density); +} + +int cmp(const void* a, const void* b) +{ + const par_vec3* v1 = (const par_vec3*) a; + const par_vec3* v2 = (const par_vec3*) b; + if (v1->rank < v2->rank) { + return -1; + } + if (v1->rank > v2->rank) { + return 1; + } + return 0; +} + +void par_bluenoise_sort_by_rank(float* floats, int npts) +{ + par_vec3* vecs = (par_vec3*) floats; + qsort(vecs, npts, sizeof(vecs[0]), cmp); + for (int i = 0; i < npts; i++) { + vecs[i].rank = i; + } +} + +float* par_bluenoise_generate_exact( + par_bluenoise_context* ctx, int npts, int stride) +{ + assert(stride >= 2); + int maxpoints = npts * 2; + if (ctx->maxpoints < maxpoints) { + free(ctx->points); + ctx->maxpoints = maxpoints; + ctx->points = PAR_MALLOC(par_vec3, maxpoints); + } + int ngenerated = 0; + int nprevious = 0; + int ndesired = npts; + float density = 2048; + while (ngenerated < ndesired) { + par_bluenoise_generate(ctx, density, &ngenerated); + + // Might be paranoid, but break if something fishy is going on: + if (ngenerated == nprevious) { + return 0; + } + + // Perform crazy heuristic to approach a nice density: + if (ndesired / ngenerated >= 2) { + density *= 2; + } else { + density += density / 10; + } + + nprevious = ngenerated; + } + par_bluenoise_sort_by_rank(&ctx->points->x, ngenerated); + if (stride != 3) { + int nbytes = sizeof(float) * stride * ndesired; + float* pts = PAR_MALLOC(float, stride * ndesired); + float* dst = pts; + const float* src = &ctx->points->x; + for (int i = 0; i < ndesired; i++, src++) { + *dst++ = *src++; + *dst++ = *src++; + if (stride > 3) { + *dst++ = *src; + dst += stride - 3; + } + } + memcpy(ctx->points, pts, nbytes); + free(pts); + } + return &ctx->points->x; +} + +#undef PAR_MINI +#undef PAR_MAXI + +#endif // PAR_BLUENOISE_IMPLEMENTATION +#endif // PAR_BLUENOISE_H + +// par_bluenoise is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_bubbles.h b/source/engine/thirdparty/par/par_bubbles.h new file mode 100644 index 0000000..ddd929e --- /dev/null +++ b/source/engine/thirdparty/par/par_bubbles.h @@ -0,0 +1,1451 @@ +// BUBBLES :: https://github.com/prideout/par +// Simple C library for packing circles into hierarchical (or flat) diagrams. +// +// Implements "Visualization of Large Hierarchical Data by Circle Packing" from +// Wang et al (2006). +// +// Also contains an implementation of Emo Welzl's "Smallest enclosing disks" +// algorithm (1991) for enclosing points with circles, and a related method +// from Mike Bostock for enclosing circles with the smallest possible circle. +// +// The API is divided into four sections: +// +// - Enclosing. Compute the smallest bounding circle for points or circles. +// - Packing. Pack circles together, or into other circles. +// - Queries. Given a touch point, pick a circle from a hierarchy, etc. +// - Deep Zoom. Uses relative coordinate systems to support arbitrary depth. +// +// In addition to the comment block above each function declaration, the API +// has informal documentation here: +// +// https://prideout.net/bubbles +// +// Distributed under the MIT License, see bottom of file. + +#ifndef PAR_BUBBLES_H +#define PAR_BUBBLES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// This can be any signed integer type. +#ifndef PAR_BUBBLES_INT +#define PAR_BUBBLES_INT int32_t +#endif + +// This must be "float" or "double" or "long double". Note that you should not +// need high precision if you use the relative coordinate systems API. +#ifndef PAR_BUBBLES_FLT +#define PAR_BUBBLES_FLT double +#endif + +// Enclosing / Touching -------------------------------------------------------- + +// Read an array of (x,y) coordinates, write a single 3-tuple (x,y,radius). +void par_bubbles_enclose_points(PAR_BUBBLES_FLT const* xy, PAR_BUBBLES_INT npts, + PAR_BUBBLES_FLT* result); + +// Read an array of 3-tuples (x,y,radius), write a 3-tuple (x,y,radius). +void par_bubbles_enclose_disks(PAR_BUBBLES_FLT const* xyr, + PAR_BUBBLES_INT ndisks, PAR_BUBBLES_FLT* result); + +// Find the circle (x,y,radius) that is tangent to 3 points (x,y). +void par_bubbles_touch_three_points(PAR_BUBBLES_FLT const* xy, + PAR_BUBBLES_FLT* result); + +// Find a position for disk "c" that makes it tangent to "a" and "b". +// Note that the ordering of a and b can affect where c will land. +// All three arguments are pointers to three-tuples (x,y,radius). +void par_bubbles_touch_two_disks(PAR_BUBBLES_FLT* c, PAR_BUBBLES_FLT const* a, + PAR_BUBBLES_FLT const* b); + +// Returns the smallest circle that intersects the three specified circles. +// This is the problem of Problem of Apollonius! +void par_bubbles_touch_three_disks(PAR_BUBBLES_FLT const* xyr1, + PAR_BUBBLES_FLT const* xyr2, PAR_BUBBLES_FLT const* xyr3, + PAR_BUBBLES_FLT* result); + +// Packing --------------------------------------------------------------------- + +// Tiny POD structure returned by all packing functions. Private data is +// attached after the public fields, so clients should call the provided +// free function rather than freeing the memory manually. +typedef struct { + PAR_BUBBLES_FLT* xyr; // array of 3-tuples (x y radius) in input order + PAR_BUBBLES_INT count; // number of 3-tuples in "xyr" + PAR_BUBBLES_INT* ids; // populated by par_bubbles_cull +} par_bubbles_t; + +void par_bubbles_free_result(par_bubbles_t*); + +// Entry point for unbounded non-hierarchical packing. Takes a list of radii. +par_bubbles_t* par_bubbles_pack(PAR_BUBBLES_FLT const* radiuses, + PAR_BUBBLES_INT nradiuses); + +// Consume a hierarchy defined by a list of integers. Each integer is an index +// to its parent. The root node is its own parent, and it must be the first node +// in the list. Clients do not have control over individual radiuses, only the +// radius of the outermost enclosing disk. +par_bubbles_t* par_bubbles_hpack_circle(PAR_BUBBLES_INT* nodes, + PAR_BUBBLES_INT nnodes, PAR_BUBBLES_FLT radius); + +// Queries --------------------------------------------------------------------- + +// Find the node at the given position. Children are on top of their parents. +// If the result is -1, there is no node at the given pick coordinate. +PAR_BUBBLES_INT par_bubbles_pick(par_bubbles_t const*, PAR_BUBBLES_FLT x, + PAR_BUBBLES_FLT y); + +// Get bounding box; take a pointer to 4 floats and set them to min xy, max xy. +void par_bubbles_compute_aabb(par_bubbles_t const*, PAR_BUBBLES_FLT* aabb); + +// Check if the given circle (3-tuple) intersects the given aabb (4-tuple). +bool par_bubbles_check_aabb(PAR_BUBBLES_FLT const* disk, + PAR_BUBBLES_FLT const* aabb); + +// Clip the bubble diagram to the given AABB (4-tuple of left,bottom,right,top) +// and return the result. Circles smaller than the given world-space +// "minradius" are removed. Optionally, an existing diagram (dst) can be passed +// in to receive the culled dataset, which reduces the number of memory allocs +// when calling this function frequently. Pass null to "dst" to create a new +// culled diagram. +par_bubbles_t* par_bubbles_cull(par_bubbles_t const* src, + PAR_BUBBLES_FLT const* aabb, PAR_BUBBLES_FLT minradius, par_bubbles_t* dst); + +// Dump out a SVG file for diagnostic purposes. +void par_bubbles_export(par_bubbles_t const* bubbles, char const* filename); + +// Returns a pointer to a list of children nodes. +void par_bubbles_get_children(par_bubbles_t const* bubbles, PAR_BUBBLES_INT idx, + PAR_BUBBLES_INT** pchildren, PAR_BUBBLES_INT* nchildren); + +// Returns the given node's parent, or 0 if it's the root. +PAR_BUBBLES_INT par_bubbles_get_parent(par_bubbles_t const* bubbles, + PAR_BUBBLES_INT idx); + +// Finds the height of the tree and returns one of its deepest leaves. +void par_bubbles_get_maxdepth(par_bubbles_t const* bubbles, + PAR_BUBBLES_INT* maxdepth, PAR_BUBBLES_INT* leaf); + +// Finds the height of the tree at a certain node. +PAR_BUBBLES_INT par_bubbles_get_depth(par_bubbles_t const* bubbles, + PAR_BUBBLES_INT node); + +// Returns a 4-tuple (min xy, max xy) for the given node. +void par_bubbles_compute_aabb_for_node(par_bubbles_t const* bubbles, + PAR_BUBBLES_INT node, PAR_BUBBLES_FLT* aabb); + +// Find the deepest node that is an ancestor of both A and B. Classic! +PAR_BUBBLES_INT par_bubbles_lowest_common_ancestor(par_bubbles_t const* bubbles, + PAR_BUBBLES_INT node_a, PAR_BUBBLES_INT node_b); + +// Deep Zoom ------------------------------------------------------------------- + +// Similar to hpack, but maintains precision by storing disk positions within +// the local coordinate system of their parent. After calling this function, +// clients can use cull_local to flatten the coordinate systems. +par_bubbles_t* par_bubbles_hpack_local(PAR_BUBBLES_INT* nodes, + PAR_BUBBLES_INT nnodes); + +// Similar to par_bubbles_cull, but takes a root node rather than an AABB, +// and returns a result within the local coordinate system of the new root. +// In other words, the new root will have radius 1, centered at (0,0). The +// minradius is also expressed in this coordinate system. +par_bubbles_t* par_bubbles_cull_local(par_bubbles_t const* src, + PAR_BUBBLES_FLT const* aabb, PAR_BUBBLES_FLT minradius, + PAR_BUBBLES_INT root, par_bubbles_t* dst); + +// Finds the smallest node in the given bubble diagram that completely encloses +// the given axis-aligned bounding box (min xy, max xy). The AABB coordinates +// are expressed in the local coordinate system of the given root node. +PAR_BUBBLES_INT par_bubbles_find_local(par_bubbles_t const* src, + PAR_BUBBLES_FLT const* aabb, PAR_BUBBLES_INT root); + +// Similar to pick, but expects (x,y) to be in the coordinate system of the +// given root node. +PAR_BUBBLES_INT par_bubbles_pick_local(par_bubbles_t const*, PAR_BUBBLES_FLT x, + PAR_BUBBLES_FLT y, PAR_BUBBLES_INT root, PAR_BUBBLES_FLT minradius); + +// Obtains the scale and translation (which should be applied in that order) +// that can move a point from the node0 coord system to the node1 coord system. +// The "xform" argument should point to three floats, which will be populated +// with: x translation, y translation, and scale. +bool par_bubbles_transform_local(par_bubbles_t const* bubbles, + PAR_BUBBLES_FLT* xform, PAR_BUBBLES_INT node0, PAR_BUBBLES_INT node1); + +// Dump out a SVG file for diagnostic purposes. +void par_bubbles_export_local(par_bubbles_t const* bubbles, + PAR_BUBBLES_INT idx, char const* filename); + +typedef enum { + PAR_BUBBLES_FILTER_DEFAULT, + PAR_BUBBLES_FILTER_DISCARD_LAST_CHILD, + PAR_BUBBLES_FILTER_KEEP_ONLY_LAST_CHILD +} par_bubbles_filter; + +// Special-case function that affects the behavior of subsequent calls to +// cull_local. Allows clients to filter the children list of each non-leaf +// node, which is especially useful when using placeholder bubbles for labels. +void par_bubbles_set_filter(par_bubbles_t* src, par_bubbles_filter f); + +typedef enum { + PAR_BUBBLES_HORIZONTAL, + PAR_BUBBLES_VERTICAL +} par_bubbles_orientation; + +// Sets some global state that affect subsequent calls to hpack. The first two +// children can either be placed horizontally (default) or vertically. The +// effect of this is subtle, since overall layout is obviously circular. +void par_bubbles_set_orientation(par_bubbles_orientation ); + +#ifndef PAR_PI +#define PAR_PI (3.14159265359) +#define PAR_MIN(a, b) (a > b ? b : a) +#define PAR_MAX(a, b) (a > b ? a : b) +#define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) +#define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#define PAR_SQR(a) ((a) * (a)) +#endif + +#ifndef PAR_MALLOC +#define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) +#define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) +#define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N))) +#define PAR_FREE(BUF) free(BUF) +#endif + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- + +#ifdef PAR_BUBBLES_IMPLEMENTATION +#define PARINT PAR_BUBBLES_INT +#define PARFLT PAR_BUBBLES_FLT + +#include +#include +#include +#include +#include + +static par_bubbles_orientation par_bubbles__ostate = PAR_BUBBLES_HORIZONTAL; + +typedef struct { + PARINT prev; + PARINT next; +} par_bubbles__node; + +typedef struct { + PARFLT* xyr; // results array + PARINT count; // client-provided count + PARINT* ids; // populated by par_bubbles_cull + PARFLT const* radiuses; // client-provided radius list + par_bubbles__node* chain; // counterclockwise enveloping chain + PARINT const* graph_parents; // client-provided parent indices + PARINT* graph_children; // flat list of children indices + PARINT* graph_heads; // list of "pointers" to first child + PARINT* graph_tails; // list of "pointers" to one-past-last child + PARINT npacked; + PARINT maxwidth; + PARINT capacity; + par_bubbles_filter filter; +} par_bubbles__t; + +static PARFLT par_bubbles__len2(PARFLT const* a) +{ + return a[0] * a[0] + a[1] * a[1]; +} + +static void par_bubbles__initgraph(par_bubbles__t* bubbles) +{ + PARINT const* parents = bubbles->graph_parents; + PARINT* nchildren = PAR_CALLOC(PARINT, bubbles->count); + for (PARINT i = 0; i < bubbles->count; i++) { + nchildren[parents[i]]++; + } + PARINT c = 0; + bubbles->graph_heads = PAR_CALLOC(PARINT, bubbles->count * 2); + bubbles->graph_tails = bubbles->graph_heads + bubbles->count; + for (PARINT i = 0; i < bubbles->count; i++) { + bubbles->maxwidth = PAR_MAX(bubbles->maxwidth, nchildren[i]); + bubbles->graph_heads[i] = bubbles->graph_tails[i] = c; + c += nchildren[i]; + } + bubbles->graph_heads[0] = bubbles->graph_tails[0] = 1; + bubbles->graph_children = PAR_MALLOC(PARINT, c); + for (PARINT i = 1; i < bubbles->count; i++) { + PARINT parent = parents[i]; + bubbles->graph_children[bubbles->graph_tails[parent]++] = i; + } + PAR_FREE(nchildren); +} + +static void par_bubbles__initflat(par_bubbles__t* bubbles) +{ + PARFLT* xyr = bubbles->xyr; + PARFLT const* radii = bubbles->radiuses; + par_bubbles__node* chain = bubbles->chain; + PARFLT x0, y0, x1, y1; + if (par_bubbles__ostate == PAR_BUBBLES_HORIZONTAL) { + x0 = -radii[0]; + y0 = 0; + x1 = radii[1]; + y1 = 0; + } else { + x0 = 0; + y0 = -radii[0]; + x1 = 0; + y1 = radii[1]; + } + *xyr++ = x0; + *xyr++ = y0; + *xyr++ = *radii++; + if (bubbles->count == ++bubbles->npacked) { + return; + } + *xyr++ = x1; + *xyr++ = y1; + *xyr++ = *radii++; + if (bubbles->count == ++bubbles->npacked) { + return; + } + xyr[2] = *radii; + par_bubbles_touch_two_disks(xyr, xyr - 6, xyr - 3); + if (bubbles->count == ++bubbles->npacked) { + return; + } + chain[0].prev = 2; + chain[0].next = 1; + chain[1].prev = 0; + chain[1].next = 2; + chain[2].prev = 1; + chain[2].next = 0; +} + +// March forward or backward along the enveloping chain, starting with the +// node at "cn" and testing for collision against the node at "ci". +static PARINT par_bubbles__collide(par_bubbles__t* bubbles, PARINT ci, + PARINT cn, PARINT* cj, PARINT direction) +{ + PARFLT const* ci_xyr = bubbles->xyr + ci * 3; + par_bubbles__node* chain = bubbles->chain; + PARINT nsteps = 1; + if (direction > 0) { + for (PARINT i = chain[cn].next; i != cn; i = chain[i].next, ++nsteps) { + PARFLT const* i_xyr = bubbles->xyr + i * 3; + PARFLT dx = i_xyr[0] - ci_xyr[0]; + PARFLT dy = i_xyr[1] - ci_xyr[1]; + PARFLT dr = i_xyr[2] + ci_xyr[2]; + if (0.999 * dr * dr > dx * dx + dy * dy) { + *cj = i; + return nsteps; + } + } + return 0; + } + for (PARINT i = chain[cn].prev; i != cn; i = chain[i].prev, ++nsteps) { + PARFLT const* i_xyr = bubbles->xyr + i * 3; + PARFLT dx = i_xyr[0] - ci_xyr[0]; + PARFLT dy = i_xyr[1] - ci_xyr[1]; + PARFLT dr = i_xyr[2] + ci_xyr[2]; + if (0.999 * dr * dr > dx * dx + dy * dy) { + *cj = i; + return nsteps; + } + } + return 0; +} + +static void par_bubbles__packflat(par_bubbles__t* bubbles) +{ + PARFLT const* radii = bubbles->radiuses; + PARFLT* xyr = bubbles->xyr; + par_bubbles__node* chain = bubbles->chain; + + // Find the circle closest to the origin, known as "Cm" in the paper. + PARINT cm = 0; + PARFLT mindist = par_bubbles__len2(xyr + 0); + PARFLT dist = par_bubbles__len2(xyr + 3); + if (dist > mindist) { + cm = 1; + } + dist = par_bubbles__len2(xyr + 6); + if (dist > mindist) { + cm = 2; + } + + // In the paper, "Cn" is always the node that follows "Cm". + PARINT ci, cn = chain[cm].next; + + for (ci = bubbles->npacked; ci < bubbles->count; ) { + PARFLT* ci_xyr = xyr + ci * 3; + ci_xyr[2] = radii[ci]; + PARFLT* cm_xyr = xyr + cm * 3; + PARFLT* cn_xyr = xyr + cn * 3; + par_bubbles_touch_two_disks(ci_xyr, cn_xyr, cm_xyr); + + // Check for a collision. In the paper, "Cj" is the intersecting node. + PARINT cj_f; + PARINT nfsteps = par_bubbles__collide(bubbles, ci, cn, &cj_f, +1); + if (!nfsteps) { + chain[cm].next = ci; + chain[ci].prev = cm; + chain[ci].next = cn; + chain[cn].prev = ci; + cm = ci++; + continue; + } + + // Search backwards for a collision, in case it is closer. + PARINT cj_b; + PARINT nbsteps = par_bubbles__collide(bubbles, ci, cm, &cj_b, -1); + + // Intersection occurred after Cn. + if (nfsteps <= nbsteps) { + cn = cj_f; + chain[cm].next = cn; + chain[cn].prev = cm; + continue; + } + + // Intersection occurred before Cm. + cm = cj_b; + chain[cm].next = cn; + chain[cn].prev = cm; + } + + bubbles->npacked = bubbles->count; +} + +static void par__disk_from_two_points(PARFLT const* xy1, PARFLT const* xy2, + PARFLT* result) +{ + PARFLT dx = xy1[0] - xy2[0]; + PARFLT dy = xy1[1] - xy2[1]; + result[0] = 0.5 * (xy1[0] + xy2[0]); + result[1] = 0.5 * (xy1[1] + xy2[1]); + result[2] = sqrt(dx * dx + dy * dy) / 2.0; +} + +static PARINT par__disk_contains_point(PARFLT const* xyr, PARFLT const* xy) +{ + PARFLT dx = xyr[0] - xy[0]; + PARFLT dy = xyr[1] - xy[1]; + return dx * dx + dy * dy <= PAR_SQR(xyr[2]); +} + +static void par__easydisk_from_points(PARFLT* disk, PARFLT const* edgepts, + PARINT nedgepts) +{ + if (nedgepts == 0) { + disk[0] = 0; + disk[1] = 0; + disk[2] = 0; + return; + } + if (nedgepts == 1) { + disk[0] = edgepts[0]; + disk[1] = edgepts[1]; + disk[2] = 0; + return; + } + par__disk_from_two_points(edgepts, edgepts + 2, disk); + if (nedgepts == 2 || par__disk_contains_point(disk, edgepts + 4)) { + return; + } + par__disk_from_two_points(edgepts, edgepts + 4, disk); + if (par__disk_contains_point(disk, edgepts + 2)) { + return; + } + par__disk_from_two_points(edgepts + 2, edgepts + 4, disk); + if (par__disk_contains_point(disk, edgepts)) { + return; + } + par_bubbles_touch_three_points(edgepts, disk); +} + +static void par__minidisk_points(PARFLT* disk, PARFLT const* pts, PARINT npts, + PARFLT const* edgepts, PARINT nedgepts) +{ + if (npts == 0 || nedgepts == 3) { + par__easydisk_from_points(disk, edgepts, nedgepts); + return; + } + PARFLT const* pt = pts + (--npts) * 2; + par__minidisk_points(disk, pts, npts, edgepts, nedgepts); + if (!par__disk_contains_point(disk, pt)) { + PARFLT edgepts1[6]; + for (PARINT i = 0; i < nedgepts * 2; i += 2) { + edgepts1[i] = edgepts[i]; + edgepts1[i + 1] = edgepts[i + 1]; + } + edgepts1[2 * nedgepts] = pt[0]; + edgepts1[2 * nedgepts + 1] = pt[1]; + par__minidisk_points(disk, pts, npts, edgepts1, ++nedgepts); + } +} + +// Returns true if the specified circle1 contains the specified circle2. +static bool par__disk_contains_disk(PARFLT const* xyr1, PARFLT const* xyr2) +{ + PARFLT xc0 = xyr1[0] - xyr2[0]; + PARFLT yc0 = xyr1[1] - xyr2[1]; + return sqrt(xc0 * xc0 + yc0 * yc0) < xyr1[2] - xyr2[2] + 1e-6; +} + +// Returns the smallest circle that intersects the two specified circles. +static void par__disk_from_two_disks(PARFLT const* xyr1, PARFLT const* xyr2, + PARFLT* result) +{ + PARFLT x1 = xyr1[0], y1 = xyr1[1], r1 = xyr1[2]; + PARFLT x2 = xyr2[0], y2 = xyr2[1], r2 = xyr2[2]; + PARFLT x12 = x2 - x1, y12 = y2 - y1, r12 = r2 - r1; + PARFLT l = sqrt(x12 * x12 + y12 * y12); + result[0] = (x1 + x2 + x12 / l * r12) / 2; + result[1] = (y1 + y2 + y12 / l * r12) / 2; + result[2] = (l + r1 + r2) / 2; +} + +static void par__easydisk_from_disks(PARFLT* disk, PARFLT const* edgedisks, + PARINT nedgedisks) +{ + assert(nedgedisks <= 3); + if (nedgedisks == 0) { + disk[0] = 0; + disk[1] = 0; + disk[2] = 0; + return; + } + if (nedgedisks == 1) { + disk[0] = edgedisks[0]; + disk[1] = edgedisks[1]; + disk[2] = edgedisks[2]; + return; + } + if (nedgedisks == 2) { + par__disk_from_two_disks(edgedisks, edgedisks + 3, disk); + return; + } + par_bubbles_touch_three_disks(edgedisks, edgedisks + 3, edgedisks + 6, + disk); +} + +static void par__minidisk_disks(PARFLT* result, PARFLT const* disks, + PARINT ndisks, PARFLT const* edgedisks, PARINT nedgedisks) +{ + if (ndisks == 0 || nedgedisks == 3) { + par__easydisk_from_disks(result, edgedisks, nedgedisks); + return; + } + PARFLT const* disk = disks + (--ndisks) * 3; + par__minidisk_disks(result, disks, ndisks, edgedisks, nedgedisks); + if (!par__disk_contains_disk(result, disk)) { + PARFLT edgedisks1[9]; + for (PARINT i = 0; i < nedgedisks * 3; i += 3) { + edgedisks1[i] = edgedisks[i]; + edgedisks1[i + 1] = edgedisks[i + 1]; + edgedisks1[i + 2] = edgedisks[i + 2]; + } + edgedisks1[3 * nedgedisks] = disk[0]; + edgedisks1[3 * nedgedisks + 1] = disk[1]; + edgedisks1[3 * nedgedisks + 2] = disk[2]; + par__minidisk_disks(result, disks, ndisks, edgedisks1, ++nedgedisks); + } +} + +static void par_bubbles__copy_disk(par_bubbles__t const* src, + par_bubbles__t* dst, PARINT parent) +{ + PARINT i = dst->count++; + if (dst->capacity < dst->count) { + dst->capacity = PAR_MAX(16, dst->capacity) * 2; + dst->xyr = PAR_REALLOC(PARFLT, dst->xyr, 3 * dst->capacity); + dst->ids = PAR_REALLOC(PARINT, dst->ids, dst->capacity); + } + PARFLT const* xyr = src->xyr + parent * 3; + dst->xyr[i * 3] = xyr[0]; + dst->xyr[i * 3 + 1] = xyr[1]; + dst->xyr[i * 3 + 2] = xyr[2]; + dst->ids[i] = parent; +} + +void par_bubbles_enclose_points(PARFLT const* xy, PARINT npts, PARFLT* result) +{ + par__minidisk_points(result, xy, npts, 0, 0); +} + +void par_bubbles_enclose_disks(PARFLT const* xyr, PARINT ndisks, PARFLT* result) +{ + par__minidisk_disks(result, xyr, ndisks, 0, 0); +} + +void par_bubbles_touch_three_points(PARFLT const* xy, PARFLT* xyr) +{ + // Many thanks to Stephen Schmitts: + // http://www.abecedarical.com/zenosamples/zs_circle3pts.html + PARFLT p1x = xy[0], p1y = xy[1]; + PARFLT p2x = xy[2], p2y = xy[3]; + PARFLT p3x = xy[4], p3y = xy[5]; + PARFLT a = p2x - p1x, b = p2y - p1y; + PARFLT c = p3x - p1x, d = p3y - p1y; + PARFLT e = a * (p2x + p1x) * 0.5 + b * (p2y + p1y) * 0.5; + PARFLT f = c * (p3x + p1x) * 0.5 + d * (p3y + p1y) * 0.5; + PARFLT det = a*d - b*c; + PARFLT cx = xyr[0] = (d*e - b*f) / det; + PARFLT cy = xyr[1] = (-c*e + a*f) / det; + xyr[2] = sqrt((p1x - cx)*(p1x - cx) + (p1y - cy)*(p1y - cy)); +} + +void par_bubbles_touch_two_disks(PARFLT* c, PARFLT const* a, PARFLT const* b) +{ + PARFLT db = a[2] + c[2], dx = b[0] - a[0], dy = b[1] - a[1]; + if (db && (dx || dy)) { + PARFLT da = b[2] + c[2], dc = dx * dx + dy * dy; + da *= da; + db *= db; + PARFLT x = 0.5 + (db - da) / (2 * dc); + PARFLT db1 = db - dc; + PARFLT y0 = PAR_MAX(0, 2 * da * (db + dc) - db1 * db1 - da * da); + PARFLT y = sqrt(y0) / (2 * dc); + c[0] = a[0] + x * dx + y * dy; + c[1] = a[1] + x * dy - y * dx; + } else { + c[0] = a[0] + db; + c[1] = a[1]; + } +} + +void par_bubbles_touch_three_disks(PARFLT const* xyr1, PARFLT const* xyr2, + PARFLT const* xyr3, PARFLT* result) +{ + PARFLT x1 = xyr1[0], y1 = xyr1[1], r1 = xyr1[2], + x2 = xyr2[0], y2 = xyr2[1], r2 = xyr2[2], + x3 = xyr3[0], y3 = xyr3[1], r3 = xyr3[2], + a2 = 2 * (x1 - x2), + b2 = 2 * (y1 - y2), + c2 = 2 * (r2 - r1), + d2 = x1 * x1 + y1 * y1 - r1 * r1 - x2 * x2 - y2 * y2 + r2 * r2, + a3 = 2 * (x1 - x3), + b3 = 2 * (y1 - y3), + c3 = 2 * (r3 - r1), + d3 = x1 * x1 + y1 * y1 - r1 * r1 - x3 * x3 - y3 * y3 + r3 * r3, + ab = a3 * b2 - a2 * b3, + xa = (b2 * d3 - b3 * d2) / ab - x1, + xb = (b3 * c2 - b2 * c3) / ab, + ya = (a3 * d2 - a2 * d3) / ab - y1, + yb = (a2 * c3 - a3 * c2) / ab, + A = xb * xb + yb * yb - 1, + B = 2 * (xa * xb + ya * yb + r1), + C = xa * xa + ya * ya - r1 * r1, + r = (-B - sqrt(B * B - 4 * A * C)) / (2 * A); + result[0] = xa + xb * r + x1; + result[1] = ya + yb * r + y1; + result[2] = r; +} + +void par_bubbles_free_result(par_bubbles_t* pubbub) +{ + par_bubbles__t* bubbles = (par_bubbles__t*) pubbub; + PAR_FREE(bubbles->graph_children); + PAR_FREE(bubbles->graph_heads); + PAR_FREE(bubbles->chain); + PAR_FREE(bubbles->xyr); + PAR_FREE(bubbles->ids); + PAR_FREE(bubbles); +} + +par_bubbles_t* par_bubbles_pack(PARFLT const* radiuses, PARINT nradiuses) +{ + par_bubbles__t* bubbles = PAR_CALLOC(par_bubbles__t, 1); + if (nradiuses > 0) { + bubbles->radiuses = radiuses; + bubbles->count = nradiuses; + bubbles->chain = PAR_MALLOC(par_bubbles__node, nradiuses); + bubbles->xyr = PAR_MALLOC(PARFLT, 3 * nradiuses); + par_bubbles__initflat(bubbles); + par_bubbles__packflat(bubbles); + } + return (par_bubbles_t*) bubbles; +} + +// Assigns a radius to every node according to its number of descendants. +void par_bubbles__generate_radii(par_bubbles__t* bubbles, + par_bubbles__t* worker, PARINT parent) +{ + PARINT head = bubbles->graph_heads[parent]; + PARINT tail = bubbles->graph_tails[parent]; + PARINT nchildren = tail - head; + PARINT pr = parent * 3 + 2; + bubbles->xyr[pr] = 1; + if (nchildren == 0) { + return; + } + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = bubbles->graph_children[cindex]; + par_bubbles__generate_radii(bubbles, worker, child); + bubbles->xyr[pr] += bubbles->xyr[child * 3 + 2]; + } + // The following square root seems to produce a nicer, more space-filling, + // distribution of radiuses in randomly-generated trees. + bubbles->xyr[pr] = sqrtf(bubbles->xyr[pr]); +} + +void par_bubbles__hpack(par_bubbles__t* bubbles, par_bubbles__t* worker, + PARINT parent, bool local) +{ + PARINT head = bubbles->graph_heads[parent]; + PARINT tail = bubbles->graph_tails[parent]; + PARINT nchildren = tail - head; + if (nchildren == 0) { + return; + } + + // Cast away const because we're using the worker as a cache to avoid + // a kazillion malloc / free calls. + PARFLT* radiuses = (PARFLT*) worker->radiuses; + + // We perform flat layout twice: once without padding (to determine scale) + // and then again with scaled padding. + PARFLT enclosure[3]; + PARFLT px = bubbles->xyr[parent * 3 + 0]; + PARFLT py = bubbles->xyr[parent * 3 + 1]; + PARFLT pr = bubbles->xyr[parent * 3 + 2]; + const PARFLT PAR_HPACK_PADDING1 = 0.15; + const PARFLT PAR_HPACK_PADDING2 = 0.025; + PARFLT scaled_padding = 0.0; + while (1) { + worker->npacked = 0; + worker->count = nchildren; + PARINT c = 0; + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = bubbles->graph_children[cindex]; + radiuses[c++] = bubbles->xyr[child * 3 + 2] + scaled_padding; + } + par_bubbles__initflat(worker); + par_bubbles__packflat(worker); + + // Using Welzl's algorithm instead of a simple AABB enclosure is + // slightly slower and doesn't yield much aesthetic improvement. + + #if PAR_BUBBLES_HPACK_WELZL + par_bubbles_enclose_disks(worker->xyr, nchildren, enclosure); + #else + PARFLT aabb[6]; + par_bubbles_compute_aabb((par_bubbles_t const*) worker, aabb); + enclosure[0] = 0.5 * (aabb[0] + aabb[2]); + enclosure[1] = 0.5 * (aabb[1] + aabb[3]); + enclosure[2] = 0; + for (PARINT c = 0; c < nchildren; c++) { + PARFLT x = worker->xyr[c * 3 + 0] - enclosure[0]; + PARFLT y = worker->xyr[c * 3 + 1] - enclosure[1]; + PARFLT r = worker->xyr[c * 3 + 2]; + enclosure[2] = PAR_MAX(enclosure[2], r + sqrtf(x * x + y * y)); + } + #endif + + if (scaled_padding || !PAR_HPACK_PADDING1) { + break; + } else { + scaled_padding = PAR_HPACK_PADDING1 / enclosure[2]; + } + } + PARFLT cx = enclosure[0], cy = enclosure[1], cr = enclosure[2]; + scaled_padding *= cr; + cr += PAR_HPACK_PADDING2 * cr; + + // Transform the children to fit nicely into either (a) the unit circle, + // or (b) their parent. The former is used if "local" is true. + PARFLT scale, tx, ty; + if (local) { + scale = 1.0 / cr; + tx = 0; + ty = 0; + } else { + scale = pr / cr; + tx = px; + ty = py; + } + PARFLT const* src = worker->xyr; + for (PARINT cindex = head; cindex != tail; cindex++, src += 3) { + PARFLT* dst = bubbles->xyr + 3 * bubbles->graph_children[cindex]; + dst[0] = tx + scale * (src[0] - cx); + dst[1] = ty + scale * (src[1] - cy); + dst[2] = scale * (src[2] - scaled_padding); + } + + // Recursion. TODO: It might be better to use our own stack here. + for (PARINT cindex = head; cindex != tail; cindex++) { + par_bubbles__hpack(bubbles, worker, bubbles->graph_children[cindex], + local); + } +} + +par_bubbles_t* par_bubbles_hpack_circle(PARINT* nodes, PARINT nnodes, + PARFLT radius) +{ + par_bubbles__t* bubbles = PAR_CALLOC(par_bubbles__t, 1); + if (nnodes > 0) { + bubbles->graph_parents = nodes; + bubbles->count = nnodes; + bubbles->chain = PAR_MALLOC(par_bubbles__node, nnodes); + bubbles->xyr = PAR_MALLOC(PARFLT, 3 * nnodes); + par_bubbles__initgraph(bubbles); + par_bubbles__t* worker = PAR_CALLOC(par_bubbles__t, 1); + worker->radiuses = PAR_MALLOC(PARFLT, bubbles->maxwidth); + worker->chain = PAR_MALLOC(par_bubbles__node, bubbles->maxwidth); + worker->xyr = PAR_MALLOC(PARFLT, 3 * bubbles->maxwidth); + par_bubbles__generate_radii(bubbles, worker, 0); + bubbles->xyr[0] = 0; + bubbles->xyr[1] = 0; + bubbles->xyr[2] = radius; + par_bubbles__hpack(bubbles, worker, 0, false); + par_bubbles_free_result((par_bubbles_t*) worker); + } + return (par_bubbles_t*) bubbles; +} + +// TODO: use a stack instead of recursion +static PARINT par_bubbles__pick(par_bubbles__t const* bubbles, PARINT parent, + PARFLT x, PARFLT y) +{ + PARFLT const* xyr = bubbles->xyr + parent * 3; + PARFLT d2 = PAR_SQR(x - xyr[0]) + PAR_SQR(y - xyr[1]); + if (d2 > PAR_SQR(xyr[2])) { + return -1; + } + PARINT head = bubbles->graph_heads[parent]; + PARINT tail = bubbles->graph_tails[parent]; + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = bubbles->graph_children[cindex]; + PARINT result = par_bubbles__pick(bubbles, child, x, y); + if (result > -1) { + return result; + } + } + return parent; +} + +PARINT par_bubbles_pick(par_bubbles_t const* cbubbles, PARFLT x, PARFLT y) +{ + par_bubbles__t const* bubbles = (par_bubbles__t const*) cbubbles; + if (bubbles->count == 0) { + return -1; + } + return par_bubbles__pick(bubbles, 0, x, y); +} + +void par_bubbles_compute_aabb(par_bubbles_t const* bubbles, PARFLT* aabb) +{ + if (bubbles->count == 0) { + return; + } + PARFLT const* xyr = bubbles->xyr; + aabb[0] = aabb[2] = xyr[0]; + aabb[1] = aabb[3] = xyr[1]; + for (PARINT i = 0; i < bubbles->count; i++, xyr += 3) { + aabb[0] = PAR_MIN(xyr[0] - xyr[2], aabb[0]); + aabb[1] = PAR_MIN(xyr[1] - xyr[2], aabb[1]); + aabb[2] = PAR_MAX(xyr[0] + xyr[2], aabb[2]); + aabb[3] = PAR_MAX(xyr[1] + xyr[2], aabb[3]); + } +} + +bool par_bubbles_check_aabb(PARFLT const* disk, PARFLT const* aabb) +{ + PARFLT cx = PAR_CLAMP(disk[0], aabb[0], aabb[2]); + PARFLT cy = PAR_CLAMP(disk[1], aabb[1], aabb[3]); + PARFLT dx = disk[0] - cx; + PARFLT dy = disk[1] - cy; + PARFLT d2 = dx * dx + dy * dy; + return d2 < (disk[2] * disk[2]); +} + +static void par_bubbles__cull(par_bubbles__t const* src, PARFLT const* aabb, + PARFLT minradius, par_bubbles__t* dst, PARINT parent) +{ + PARFLT const* xyr = src->xyr + parent * 3; + if (xyr[2] < minradius || !par_bubbles_check_aabb(xyr, aabb)) { + return; + } + par_bubbles__copy_disk(src, dst, parent); + PARINT head = src->graph_heads[parent]; + PARINT tail = src->graph_tails[parent]; + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = src->graph_children[cindex]; + par_bubbles__cull(src, aabb, minradius, dst, child); + } +} + +par_bubbles_t* par_bubbles_cull(par_bubbles_t const* psrc, + PARFLT const* aabb, PARFLT minradius, par_bubbles_t* pdst) +{ + par_bubbles__t const* src = (par_bubbles__t const*) psrc; + par_bubbles__t* dst = (par_bubbles__t*) pdst; + if (!dst) { + dst = PAR_CALLOC(par_bubbles__t, 1); + pdst = (par_bubbles_t*) dst; + } else { + dst->count = 0; + } + if (src->count == 0) { + return pdst; + } + par_bubbles__cull(src, aabb, minradius, dst, 0); + return pdst; +} + +void par_bubbles_export(par_bubbles_t const* bubbles, char const* filename) +{ + PARFLT aabb[4]; + par_bubbles_compute_aabb(bubbles, aabb); + PARFLT maxextent = PAR_MAX(aabb[2] - aabb[0], aabb[3] - aabb[1]); + PARFLT padding = 0.05 * maxextent; + FILE* svgfile = fopen(filename, "wt"); + if (!svgfile) { + fprintf(stderr, "Unable to open %s for writing.\n", filename); + return; + } + fprintf(svgfile, + "\n" + "\n" + "\n", + aabb[0] - padding, aabb[1] - padding, + aabb[2] - aabb[0] + 2 * padding, aabb[3] - aabb[1] + 2 * padding, + aabb[0] - padding, aabb[1] - padding); + PARFLT const* xyr = bubbles->xyr; + for (PARINT i = 0; i < bubbles->count; i++, xyr += 3) { + fprintf(svgfile, "\n", + xyr[2] * 0.01, xyr[0], xyr[1], xyr[2]); + fprintf(svgfile, "%d\n", + xyr[0], xyr[1] + xyr[2] * 0.125, xyr[2] * 0.5, (int) i); + } + fputs("\n", svgfile); + fclose(svgfile); +} + +void par_bubbles_get_children(par_bubbles_t const* pbubbles, PARINT node, + PARINT** pchildren, PARINT* nchildren) +{ + par_bubbles__t const* bubbles = (par_bubbles__t const*) pbubbles; + *pchildren = bubbles->graph_children + bubbles->graph_heads[node]; + *nchildren = bubbles->graph_tails[node] - bubbles->graph_heads[node]; +} + +PARINT par_bubbles_get_parent(par_bubbles_t const* pbubbles, PARINT node) +{ + par_bubbles__t const* bubbles = (par_bubbles__t const*) pbubbles; + return bubbles->graph_parents[node]; +} + +void par_bubbles__get_maxdepth(par_bubbles__t const* bubbles, PARINT* maxdepth, + PARINT* leaf, PARINT parent, PARINT depth) +{ + if (depth > *maxdepth) { + *leaf = parent; + *maxdepth = depth; + } + PARINT* children; + PARINT nchildren; + par_bubbles_t const* pbubbles = (par_bubbles_t const*) bubbles; + par_bubbles_get_children(pbubbles, parent, &children, &nchildren); + for (PARINT c = 0; c < nchildren; c++) { + par_bubbles__get_maxdepth(bubbles, maxdepth, leaf, children[c], + depth + 1); + } +} + +void par_bubbles_get_maxdepth(par_bubbles_t const* pbubbles, PARINT* maxdepth, + PARINT* leaf) +{ + par_bubbles__t const* bubbles = (par_bubbles__t const*) pbubbles; + *maxdepth = -1; + *leaf = -1; + return par_bubbles__get_maxdepth(bubbles, maxdepth, leaf, 0, 0); +} + +PARINT par_bubbles_get_depth(par_bubbles_t const* pbubbles, PARINT node) +{ + par_bubbles__t const* bubbles = (par_bubbles__t const*) pbubbles; + PARINT const* parents = bubbles->graph_parents; + PARINT depth = 0; + while (node) { + node = parents[node]; + depth++; + } + return depth; +} + +void par_bubbles_compute_aabb_for_node(par_bubbles_t const* bubbles, + PAR_BUBBLES_INT node, PAR_BUBBLES_FLT* aabb) +{ + PARFLT const* xyr = bubbles->xyr + 3 * node; + aabb[0] = aabb[2] = xyr[0]; + aabb[1] = aabb[3] = xyr[1]; + aabb[0] = PAR_MIN(xyr[0] - xyr[2], aabb[0]); + aabb[1] = PAR_MIN(xyr[1] - xyr[2], aabb[1]); + aabb[2] = PAR_MAX(xyr[0] + xyr[2], aabb[2]); + aabb[3] = PAR_MAX(xyr[1] + xyr[2], aabb[3]); +} + +PARINT par_bubbles_lowest_common_ancestor(par_bubbles_t const* bubbles, + PARINT node_a, PARINT node_b) +{ + if (node_a == node_b) { + return node_a; + } + par_bubbles__t const* src = (par_bubbles__t const*) bubbles; + PARINT depth_a = par_bubbles_get_depth(bubbles, node_a); + PARINT* chain_a = PAR_MALLOC(PARINT, depth_a); + for (PARINT i = depth_a - 1; i >= 0; i--) { + chain_a[i] = node_a; + node_a = src->graph_parents[node_a]; + } + PARINT depth_b = par_bubbles_get_depth(bubbles, node_b); + PARINT* chain_b = PAR_MALLOC(PARINT, depth_b); + for (PARINT i = depth_b - 1; i >= 0; i--) { + chain_b[i] = node_b; + node_b = src->graph_parents[node_b]; + } + PARINT lca = 0; + for (PARINT i = 1; i < PAR_MIN(depth_a, depth_b); i++) { + if (chain_a[i] != chain_b[i]) { + break; + } + lca = chain_a[i]; + } + PAR_FREE(chain_a); + PAR_FREE(chain_b); + return lca; +} + +void par_bubbles_export_local(par_bubbles_t const* bubbles, + PAR_BUBBLES_INT root, char const* filename) +{ + par_bubbles_t* clone = par_bubbles_cull_local(bubbles, 0, 0, root, 0); + FILE* svgfile = fopen(filename, "wt"); + fprintf(svgfile, + "\n" + "\n" + "\n", + -1.0, -1.0, 2.0, 2.0, -1.0, -1.0); + PARFLT const* xyr = clone->xyr; + for (PARINT i = 0; i < clone->count; i++, xyr += 3) { + fprintf(svgfile, "\n", + xyr[2] * 0.01, xyr[0], xyr[1], xyr[2]); + } + fputs("\n", svgfile); + fclose(svgfile); + par_bubbles_free_result(clone); +} + +void par_bubbles_set_filter(par_bubbles_t* bubbles, par_bubbles_filter f) +{ + par_bubbles__t* src = (par_bubbles__t*) bubbles; + src->filter = f; +} + +static void par_bubbles__copy_disk_local(par_bubbles__t const* src, + par_bubbles__t* dst, PARINT parent, PARFLT const* xform) +{ + PARINT i = dst->count++; + if (dst->capacity < dst->count) { + dst->capacity = PAR_MAX(16, dst->capacity) * 2; + dst->xyr = PAR_REALLOC(PARFLT, dst->xyr, 3 * dst->capacity); + dst->ids = PAR_REALLOC(PARINT, dst->ids, dst->capacity); + } + PARFLT const* xyr = src->xyr + parent * 3; + dst->xyr[i * 3] = xyr[0] * xform[2] + xform[0]; + dst->xyr[i * 3 + 1] = xyr[1] * xform[2] + xform[1]; + dst->xyr[i * 3 + 2] = xyr[2] * xform[2]; + dst->ids[i] = parent; +} + +static void par_bubbles__cull_local(par_bubbles__t const* src, + PARFLT const* aabb, PARFLT const* xform, PARFLT minradius, + par_bubbles__t* dst, PARINT parent) +{ + PARFLT const* xyr = src->xyr + parent * 3; + PARFLT child_xform[3] = { + xform[0] + xform[2] * xyr[0], + xform[1] + xform[2] * xyr[1], + xform[2] * xyr[2] + }; + if (aabb && !par_bubbles_check_aabb(child_xform, aabb)) { + return; + } + if (child_xform[2] < minradius) { + return; + } + par_bubbles__copy_disk_local(src, dst, parent, xform); + xform = child_xform; + PARINT head = src->graph_heads[parent]; + PARINT tail = src->graph_tails[parent]; + if (src->filter == PAR_BUBBLES_FILTER_DISCARD_LAST_CHILD) { + tail--; + } else if (src->filter == PAR_BUBBLES_FILTER_KEEP_ONLY_LAST_CHILD) { + head = PAR_MAX(head, tail - 1); + } + for (PARINT cindex = head; cindex < tail; cindex++) { + PARINT child = src->graph_children[cindex]; + par_bubbles__cull_local(src, aabb, xform, minradius, dst, child); + } +} + +par_bubbles_t* par_bubbles_cull_local(par_bubbles_t const* psrc, + PAR_BUBBLES_FLT const* aabb, PAR_BUBBLES_FLT minradius, + PAR_BUBBLES_INT root, par_bubbles_t* pdst) +{ + par_bubbles__t const* src = (par_bubbles__t const*) psrc; + par_bubbles__t* dst = (par_bubbles__t*) pdst; + if (!dst) { + dst = PAR_CALLOC(par_bubbles__t, 1); + pdst = (par_bubbles_t*) dst; + } else { + dst->count = 0; + } + if (src->count == 0) { + return pdst; + } + PARFLT xform[3] = {0, 0, 1}; + par_bubbles__copy_disk_local(src, dst, root, xform); + dst->xyr[0] = dst->xyr[1] = 0; + dst->xyr[2] = 1; + PARINT head = src->graph_heads[root]; + PARINT tail = src->graph_tails[root]; + if (src->filter == PAR_BUBBLES_FILTER_DISCARD_LAST_CHILD) { + tail--; + } else if (src->filter == PAR_BUBBLES_FILTER_KEEP_ONLY_LAST_CHILD) { + head = PAR_MAX(head, tail - 1); + } + for (PARINT cindex = head; cindex < tail; cindex++) { + PARINT child = src->graph_children[cindex]; + par_bubbles__cull_local(src, aabb, xform, minradius, dst, child); + } + return pdst; +} + +par_bubbles_t* par_bubbles_hpack_local(PARINT* nodes, PARINT nnodes) +{ + par_bubbles__t* bubbles = PAR_CALLOC(par_bubbles__t, 1); + if (nnodes > 0) { + bubbles->graph_parents = nodes; + bubbles->count = nnodes; + bubbles->chain = PAR_MALLOC(par_bubbles__node, nnodes); + bubbles->xyr = PAR_MALLOC(PARFLT, 3 * nnodes); + par_bubbles__initgraph(bubbles); + par_bubbles__t* worker = PAR_CALLOC(par_bubbles__t, 1); + worker->radiuses = PAR_MALLOC(PARFLT, bubbles->maxwidth); + worker->chain = PAR_MALLOC(par_bubbles__node, bubbles->maxwidth); + worker->xyr = PAR_MALLOC(PARFLT, 3 * bubbles->maxwidth); + par_bubbles__generate_radii(bubbles, worker, 0); + bubbles->xyr[0] = 0; + bubbles->xyr[1] = 0; + bubbles->xyr[2] = 1; + par_bubbles__hpack(bubbles, worker, 0, true); + par_bubbles_free_result((par_bubbles_t*) worker); + } + return (par_bubbles_t*) bubbles; +} + +static bool par_bubbles__disk_encloses_aabb(PAR_BUBBLES_FLT cx, + PAR_BUBBLES_FLT cy, PAR_BUBBLES_FLT r, PAR_BUBBLES_FLT const* aabb) +{ + PAR_BUBBLES_FLT x, y; + PAR_BUBBLES_FLT r2 = r * r; + x = aabb[0]; y = aabb[1]; + if (PAR_SQR(x - cx) + PAR_SQR(y - cy) > r2) { + return false; + } + x = aabb[2]; y = aabb[1]; + if (PAR_SQR(x - cx) + PAR_SQR(y - cy) > r2) { + return false; + } + x = aabb[0]; y = aabb[3]; + if (PAR_SQR(x - cx) + PAR_SQR(y - cy) > r2) { + return false; + } + x = aabb[2]; y = aabb[3]; + return PAR_SQR(x - cx) + PAR_SQR(y - cy) <= r2; +} + +static bool par_bubbles__get_local(par_bubbles__t const* src, PARFLT* xform, + PARINT parent, PARINT node); + +static bool par_bubbles_transform_parent(par_bubbles__t const* src, + PARFLT* xform, PARINT node0) +{ + PARINT node1 = src->graph_parents[node0]; + xform[0] = 0; + xform[1] = 0; + xform[2] = 1; + + PARINT head = src->graph_heads[node1]; + PARINT tail = src->graph_tails[node1]; + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = src->graph_children[cindex]; + if (par_bubbles__get_local(src, xform, child, node0)) { + return true; + } + } + return false; +} + +PARINT par_bubbles__find_local(par_bubbles__t const* src, + PARFLT const* xform, PARFLT const* aabb, PARINT parent) +{ + PARFLT const* xyr = src->xyr + parent * 3; + PARFLT child_xform[3] = { + xform[2] * xyr[0] + xform[0], + xform[2] * xyr[1] + xform[1], + xform[2] * xyr[2] + }; + xform = child_xform; + if (!par_bubbles__disk_encloses_aabb(xform[0], xform[1], xform[2], aabb)) { + return -1; + } + PARFLT maxrad = 0; + PARINT head = src->graph_heads[parent]; + PARINT tail = src->graph_tails[parent]; + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = src->graph_children[cindex]; + PARFLT const* xyr = src->xyr + child * 3; + maxrad = PAR_MAX(maxrad, xyr[2]); + } + PARFLT maxext = PAR_MAX(aabb[2] - aabb[0], aabb[3] - aabb[1]); + if (2 * maxrad < maxext) { + return parent; + } + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = src->graph_children[cindex]; + PARINT cresult = par_bubbles__find_local(src, xform, aabb, child); + if (cresult > -1) { + return cresult; + } + } + return parent; +} + +// This finds the deepest node that completely encloses the box. +PARINT par_bubbles_find_local(par_bubbles_t const* bubbles, PARFLT const* aabb, + PARINT root) +{ + par_bubbles__t const* src = (par_bubbles__t const*) bubbles; + + // Since the aabb is expressed in the coordinate system of the given root, + // we can do a trivial rejection right away, using the unit circle. + if (!par_bubbles__disk_encloses_aabb(0, 0, 1, aabb)) { + if (root == 0) { + return -1; + } + PARFLT xform[3]; + par_bubbles_transform_parent(src, xform, root); + PARFLT width = aabb[2] - aabb[0]; + PARFLT height = aabb[3] - aabb[1]; + PARFLT cx = 0.5 * (aabb[0] + aabb[2]); + PARFLT cy = 0.5 * (aabb[1] + aabb[3]); + width *= xform[2]; + height *= xform[2]; + cx = cx * xform[2] + xform[0]; + cy = cy * xform[2] + xform[1]; + PARFLT new_aabb[4] = { + cx - width * 0.5, + cy - height * 0.5, + cx + width * 0.5, + cy + height * 0.5 + }; + PARINT parent = src->graph_parents[root]; + return par_bubbles_find_local(bubbles, new_aabb, parent); + } + + PARFLT maxrad = 0; + PARINT head = src->graph_heads[root]; + PARINT tail = src->graph_tails[root]; + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = src->graph_children[cindex]; + PARFLT const* xyr = src->xyr + child * 3; + maxrad = PAR_MAX(maxrad, xyr[2]); + } + PARFLT maxext = PAR_MAX(aabb[2] - aabb[0], aabb[3] - aabb[1]); + if (2 * maxrad < maxext) { + return root; + } + + PARFLT xform[3] = {0, 0, 1}; + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = src->graph_children[cindex]; + PARINT cresult = par_bubbles__find_local(src, xform, aabb, child); + if (cresult > -1) { + return cresult; + } + } + return root; +} + +// This could be implemented much more efficiently, but for now it simply +// calls find_local with a zero-size AABB, then ensures that the result +// has a radius that is greater than or equal to minradius. +PARINT par_bubbles_pick_local(par_bubbles_t const* bubbles, PARFLT x, PARFLT y, + PARINT root, PARFLT minradius) +{ + par_bubbles__t const* src = (par_bubbles__t const*) bubbles; + PARFLT aabb[] = { x, y, x, y }; + PARINT result = par_bubbles_find_local(bubbles, aabb, root); + if (result == -1) { + return result; + } + PARINT depth = par_bubbles_get_depth(bubbles, result); + PARINT* chain = PAR_MALLOC(PARINT, depth); + PARINT node = result; + for (PARINT i = depth - 1; i >= 0; i--) { + chain[i] = node; + node = src->graph_parents[node]; + } + PARFLT radius = 1; + for (PARINT i = 1; i < depth; i++) { + PARINT node = chain[i]; + radius *= src->xyr[node * 3 + 2]; + if (radius < minradius) { + result = chain[i - 1]; + break; + } + } + PAR_FREE(chain); + return result; +} + +static bool par_bubbles__get_local(par_bubbles__t const* src, PARFLT* xform, + PARINT parent, PARINT node) +{ + PARFLT const* xyr = src->xyr + parent * 3; + PARFLT child_xform[3] = { + xform[2] * xyr[0] + xform[0], + xform[2] * xyr[1] + xform[1], + xform[2] * xyr[2] + }; + if (parent == node) { + xform[0] = child_xform[0]; + xform[1] = child_xform[1]; + xform[2] = child_xform[2]; + return true; + } + PARINT head = src->graph_heads[parent]; + PARINT tail = src->graph_tails[parent]; + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = src->graph_children[cindex]; + if (par_bubbles__get_local(src, child_xform, child, node)) { + xform[0] = child_xform[0]; + xform[1] = child_xform[1]; + xform[2] = child_xform[2]; + return true; + } + } + return false; +} + +// Obtains the scale and translation (which should be applied in that order) +// that can move a point from the node0 coord system to the node1 coord system. +// The "xform" argument should point to three floats, which will be populated +// with: x translation, y translation, and scale. +bool par_bubbles_transform_local(par_bubbles_t const* bubbles, PARFLT* xform, + PARINT node0, PARINT node1) +{ + par_bubbles__t const* src = (par_bubbles__t const*) bubbles; + xform[0] = 0; + xform[1] = 0; + xform[2] = 1; + if (node0 == node1) { + return true; + } + if (node1 == src->graph_parents[node0]) { + return par_bubbles_transform_parent(src, xform, node0); + } + + // First try the case where node1 is a descendant of node0 + PARINT head = src->graph_heads[node0]; + PARINT tail = src->graph_tails[node0]; + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = src->graph_children[cindex]; + if (par_bubbles__get_local(src, xform, child, node1)) { + float tx = xform[0]; + float ty = xform[1]; + float s = xform[2]; + xform[0] = -tx / s; + xform[1] = -ty / s; + xform[2] = 1.0 / s; + return true; + } + } + + // Next, try the case where node0 is a descendant of node1 + head = src->graph_heads[node1]; + tail = src->graph_tails[node1]; + for (PARINT cindex = head; cindex != tail; cindex++) { + PARINT child = src->graph_children[cindex]; + if (par_bubbles__get_local(src, xform, child, node0)) { + return true; + } + } + + // If we reach here, then node0 is neither an ancestor nor a descendant, so + // do something hacky and return false. It would be best to find the lowest + // common ancestor, but let's just assume the lowest common ancestor is 0. + PARFLT xform2[3] = {0, 0, 1}; + par_bubbles_transform_local(bubbles, xform, node0, 0); + par_bubbles_transform_local(bubbles, xform2, 0, node1); + xform[0] *= xform2[2]; + xform[1] *= xform2[2]; + xform[2] *= xform2[2]; + xform[0] += xform2[0]; + xform[1] += xform2[1]; + + return false; +} + +void par_bubbles_set_orientation(par_bubbles_orientation ostate) +{ + par_bubbles__ostate = ostate; +} + +#undef PARINT +#undef PARFLT +#endif // PAR_BUBBLES_IMPLEMENTATION +#endif // PAR_BUBBLES_H + +// par_bubbles is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_camera_control.h b/source/engine/thirdparty/par/par_camera_control.h new file mode 100644 index 0000000..0ffc8eb --- /dev/null +++ b/source/engine/thirdparty/par/par_camera_control.h @@ -0,0 +1,1095 @@ +// CAMERA CONTROL :: https://github.com/prideout/par +// Enables orbit controls (a.k.a. tumble, arcball, trackball) or pan-and-zoom like Google Maps. +// +// This simple library controls a camera that orbits or pans over a 3D object or terrain. No +// assumptions are made about the renderer or platform. In a sense, this is just a math library. +// Clients notify the controller of generic input events (e.g. grab_begin, grab_move, grab_end) +// and retrieve the look-at vectors (position, target, up) or 4x4 matrices for the camera. +// +// In map mode, users can control their viewing position by grabbing and dragging locations in the +// scene. Sometimes this is known as "through-the-lens" camera control. In this mode the controller +// takes an optional raycast callback to support precise grabbing behavior. If this is not required +// for your use case (e.g. a top-down terrain with an orthgraphic projection), provide NULL for the +// callback and the library will simply raycast against the ground plane. +// +// When the controller is in orbit mode, the orientation of the camera is defined by a Y-axis +// rotation followed by an X-axis rotation. Additionally, the camera can fly forward or backward +// along the viewing direction. +// +// For a complex usage example, go to: +// https://github.com/prideout/camera_demo +// +// Distributed under the MIT License, see bottom of file. + +#ifndef PAR_CAMERA_CONTROL_H +#define PAR_CAMERA_CONTROL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef PARCC_USE_DOUBLE +typedef double parcc_float; +#else +typedef float parcc_float; +#endif + +// Opaque handle to a camera controller. +typedef struct parcc_context_s parcc_context; + +// The camera controller can be configured using either a VERTICAL or HORIZONTAL field of view. +// This specifies which of the two FOV angles should be held constant. For example, if you use a +// horizontal FOV, shrinking the viewport width will change the height of the frustum, but will +// leave the frustum width intact. +typedef enum { + PARCC_VERTICAL, + PARCC_HORIZONTAL, +} parcc_fov; + +// The controller can be configured in orbit mode or pan-and-zoom mode. +typedef enum { + PARCC_ORBIT, // aka tumble, trackball, or arcball + PARCC_MAP, // pan and zoom like Google Maps +} parcc_mode; + +// Pan and zoom constraints for MAP mode. +typedef enum { + // No constraints except that map_min_distance is enforced. + PARCC_CONSTRAIN_NONE, + + // Constrains pan and zoom to limit the viewport's extent along the FOV axis so that it always + // lies within the map_extent. With this constraint, it is possible to see the entire map at + // once, but some portion of the map must always be visible. + PARCC_CONSTRAIN_AXIS, + + // Constrains pan and zoom to limit the viewport's extent into the map_extent. With this + // constraint, it may be impossible to see the entire map at once, but users can never see any + // of the empty void that lies outside the map extent. + PARCC_CONSTRAIN_FULL, +} parcc_constraint; + +// Optional user-provided ray casting function to enable precise panning behavior. +typedef bool (*parcc_raycast_fn)(const parcc_float origin[3], const parcc_float dir[3], + parcc_float* t, void* userdata); + +// The parcc_properties structure holds all user-controlled state in the library. +// Many fields are swapped with fallback values values if they are zero-filled. + +typedef struct { + // REQUIRED PROPERTIES + parcc_mode mode; // must be PARCC_ORBIT or PARCC_MAP + int viewport_width; // horizontal extent in pixels + int viewport_height; // vertical extent in pixels + parcc_float near_plane; // distance between camera and near clipping plane + parcc_float far_plane; // distance between camera and far clipping plane + + // PROPERTIES WITH DEFAULT VALUES + parcc_fov fov_orientation; // defaults to PARCC_VERTICAL + parcc_float fov_degrees; // full field-of-view angle (not half-angle), defaults to 33. + parcc_float zoom_speed; // defaults to 0.01 + parcc_float home_target[3]; // world-space coordinate, defaults to (0,0,0) + parcc_float home_upward[3]; // unit-length vector, defaults to (0,1,0) + + // MAP-MODE PROPERTIES + parcc_float map_extent[2]; // (required) size of quad centered at home_target + parcc_float map_plane[4]; // plane equation with normalized XYZ, defaults to (0,0,1,0) + parcc_constraint map_constraint; // defaults to PARCC_CONSTRAIN_NONE + parcc_float map_min_distance; // constrains zoom using distance between camera and plane + parcc_raycast_fn raycast_function; // defaults to a simple plane intersector + void* raycast_userdata; // arbitrary data for the raycast callback + + // ORBIT-MODE PROPERTIES + parcc_float home_vector[3]; // non-unitized vector from home_target to initial eye position + parcc_float orbit_speed[2]; // rotational speed (defaults to 0.01) + parcc_float orbit_zoom_speed; // zoom speed (defaults to 0.01) + parcc_float orbit_strafe_speed[2]; // strafe speed (defaults to 0.001) + +} parcc_properties; + +// The parcc_frame structure holds captured camera state for Van Wijk animation and bookmarks. +// From the user's perspective, this should be treated as an opaque structure. +// clang-format off + +typedef struct { + parcc_mode mode; + union { + struct { parcc_float extent, center[2]; }; + struct { parcc_float phi, theta, pivot_distance, pivot[3]; }; + }; +} parcc_frame; + +// clang-format on +// CONTROLLER CONSTRUCTOR AND DESTRUCTOR + +// The constructor is the only function in the library that performs heap allocation. It +// does not retain the given properties pointer, it simply copies values out of it. + +parcc_context* parcc_create_context(const parcc_properties* props); +void parcc_destroy_context(parcc_context* ctx); + +// PROPERTY SETTERS AND GETTERS + +// The client owns its own instance of the property struct and these functions simply copy values in +// or out of the given struct. Changing some properties might cause a small amount of work to be +// performed. + +void parcc_set_properties(parcc_context* context, const parcc_properties* props); +void parcc_get_properties(const parcc_context* context, parcc_properties* out); + +// CAMERA RETRIEVAL FUNCTIONS + +void parcc_get_look_at(const parcc_context* ctx, parcc_float eyepos[3], parcc_float target[3], + parcc_float upward[3]); +void parcc_get_matrices(const parcc_context* ctx, parcc_float projection[16], parcc_float view[16]); + +// SCREEN-SPACE FUNCTIONS FOR USER INTERACTION + +// Each of these functions take winx / winy coords. +// - The winx coord should be in [0, viewport_width) where 0 is the left-most column. +// - The winy coord should be in [0, viewport_height) where 0 is the top-most row. +// +// The scrolldelta argument is used for zooming. Positive values indicate "zoom in" in MAP mode or +// "move forward" in ORBIT mode. This gets scaled by zoom_speed. In MAP mode, the zoom speed is also +// scaled by distance-to-ground. To prevent zooming in too far, use a non-zero value for +// map_min_distance. +// +// The strafe argument exists only for ORBIT mode and is typically associated with the right mouse +// button or two-finger dragging. This is used to pan the view. Note that orbit mode maintains a +// "pivot point" which is initially set to home_target. When flying forward or backward, the pivot +// does not move. However strafing will cause it to move around. This matches sketchfab behavior. +// When flying past the orbit point, the controller enters a "flipped" state to prevent the flight +// direction from suddenly changing. + +void parcc_grab_begin(parcc_context* context, int winx, int winy, bool strafe); +void parcc_grab_update(parcc_context* context, int winx, int winy); +void parcc_grab_end(parcc_context* context); +void parcc_zoom(parcc_context* context, int winx, int winy, parcc_float scrolldelta); +bool parcc_raycast(parcc_context* context, int winx, int winy, parcc_float result[3]); + +// BOOKMARKING AND VAN WIJK INTERPOLATION FUNCTIONS + +parcc_frame parcc_get_current_frame(const parcc_context* context); +parcc_frame parcc_get_home_frame(const parcc_context* context); +void parcc_goto_frame(parcc_context* context, parcc_frame state); +parcc_frame parcc_interpolate_frames(parcc_frame a, parcc_frame b, double t); +double parcc_get_interpolation_duration(parcc_frame a, parcc_frame b); + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- +#ifdef PAR_CAMERA_CONTROL_IMPLEMENTATION + +#include +#include +#include +#include + +#define PARCC_PI (3.14159265359) +#define PARCC_MIN(a, b) (a > b ? b : a) +#define PARCC_MAX(a, b) (a > b ? a : b) +#define PARCC_CLAMP(v, lo, hi) PARCC_MAX(lo, PARCC_MIN(hi, v)) +#define PARCC_CALLOC(T, N) ((T*)calloc(N * sizeof(T), 1)) +#define PARCC_FREE(BUF) free(BUF) +#define PARCC_SWAP(T, A, B) \ + { \ + T tmp = B; \ + B = A; \ + A = tmp; \ + } + +static void parcc_float4_set(parcc_float dst[4], parcc_float x, parcc_float y, parcc_float z, + parcc_float w) { + dst[0] = x; + dst[1] = y; + dst[2] = z; + dst[3] = w; +} + +static parcc_float parcc_float4_dot(const parcc_float a[4], const parcc_float b[4]) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; +} + +static void parcc_float3_set(parcc_float dst[3], parcc_float x, parcc_float y, parcc_float z) { + dst[0] = x; + dst[1] = y; + dst[2] = z; +} + +static void parcc_float3_add(parcc_float dst[3], const parcc_float a[3], const parcc_float b[3]) { + dst[0] = a[0] + b[0]; + dst[1] = a[1] + b[1]; + dst[2] = a[2] + b[2]; +} + +static void parcc_float3_subtract(parcc_float dst[3], const parcc_float a[3], + const parcc_float b[3]) { + dst[0] = a[0] - b[0]; + dst[1] = a[1] - b[1]; + dst[2] = a[2] - b[2]; +} + +static parcc_float parcc_float3_dot(const parcc_float a[3], const parcc_float b[3]) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +static void parcc_float3_cross(parcc_float dst[3], const parcc_float a[3], const parcc_float b[3]) { + dst[0] = a[1] * b[2] - a[2] * b[1]; + dst[1] = a[2] * b[0] - a[0] * b[2]; + dst[2] = a[0] * b[1] - a[1] * b[0]; +} + +static void parcc_float3_scale(parcc_float dst[3], parcc_float v) { + dst[0] *= v; + dst[1] *= v; + dst[2] *= v; +} + +static void parcc_float3_lerp(parcc_float dst[3], const parcc_float a[3], const parcc_float b[3], + parcc_float t) { + dst[0] = a[0] * (1 - t) + b[0] * t; + dst[1] = a[1] * (1 - t) + b[1] * t; + dst[2] = a[2] * (1 - t) + b[2] * t; +} + +static parcc_float parcc_float_lerp(const parcc_float a, const parcc_float b, parcc_float t) { + return a * (1 - t) + b * t; +} + +static parcc_float parcc_float3_length(const parcc_float dst[3]) { + return sqrtf(parcc_float3_dot(dst, dst)); +} + +static void parcc_float3_normalize(parcc_float dst[3]) { + parcc_float3_scale(dst, 1.0f / parcc_float3_length(dst)); +} + +static void parcc_float3_copy(parcc_float dst[3], const parcc_float src[3]) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; +} + +static void parcc_float16_look_at(float dst[16], const float eye[3], const float target[3], + const float up[3]) { + parcc_float v3X[3]; + parcc_float v3Y[3]; + parcc_float v3Z[3]; + + parcc_float3_copy(v3Y, up); + parcc_float3_normalize(v3Y); + + parcc_float3_subtract(v3Z, eye, target); + parcc_float3_normalize(v3Z); + + parcc_float3_cross(v3X, v3Y, v3Z); + parcc_float3_normalize(v3X); + + parcc_float3_cross(v3Y, v3Z, v3X); + + parcc_float4_set(dst + 0, v3X[0], v3Y[0], v3Z[0], 0); + parcc_float4_set(dst + 4, v3X[1], v3Y[1], v3Z[1], 0); + parcc_float4_set(dst + 8, v3X[2], v3Y[2], v3Z[2], 0); + parcc_float4_set(dst + 12, // + -parcc_float3_dot(v3X, eye), // + -parcc_float3_dot(v3Y, eye), // + -parcc_float3_dot(v3Z, eye), 1.0); +} + +static void parcc_float16_perspective_y(float dst[16], float fovy_degrees, float aspect_ratio, + float near, float far) { + const parcc_float fovy_radians = fovy_degrees * PARCC_PI / 180; + const parcc_float f = tan(PARCC_PI / 2.0 - 0.5 * fovy_radians); + const parcc_float rangeinv = 1.0f / (near - far); + dst[0] = f / aspect_ratio; + dst[1] = 0; + dst[2] = 0; + dst[3] = 0; + dst[4] = 0; + dst[5] = f; + dst[6] = 0; + dst[7] = 0; + dst[8] = 0; + dst[9] = 0; + dst[10] = (near + far) * rangeinv; + dst[11] = -1; + dst[12] = 0; + dst[13] = 0; + dst[14] = ((near * far) * rangeinv) * 2.0f; + dst[15] = 0; +} + +static void parcc_float16_perspective_x(float dst[16], float fovy_degrees, float aspect_ratio, + float near, float far) { + const parcc_float fovy_radians = fovy_degrees * PARCC_PI / 180; + const parcc_float f = tan(PARCC_PI / 2.0 - 0.5 * fovy_radians); + const parcc_float rangeinv = 1.0 / (near - far); + dst[0] = f; + dst[1] = 0; + dst[2] = 0; + dst[3] = 0; + dst[4] = 0; + dst[5] = f * aspect_ratio; + dst[6] = 0; + dst[7] = 0; + dst[8] = 0; + dst[9] = 0; + dst[10] = (near + far) * rangeinv; + dst[11] = -1; + dst[12] = 0; + dst[13] = 0; + dst[14] = ((near * far) * rangeinv) * 2.0; + dst[15] = 0; +} + +// Implementation note about the "parcc_frame" POD. This is an abbreviated camera state +// used for animation and bookmarking. +// +// MAP mode: +// - zoom level is represented with the extent of the rectangle formed by the intersection of +// the frustum with the viewing plane at home_target. It is either a width or a height, depending +// on fov_orientation. +// - the pan offset is stored as a 2D vector from home_target that gets projected to map_plane. +// +// ORBIT mode: +// - phi = X-axis rotation in [-pi/2, +pi/2] (applies first) +// - theta = Y-axis rotation in [-pi, +pi] (applies second) +// - pivot is initialized to home_center but might be changed via strafe +// - pivot_distance is the distance between eye and pivot (negative distance = orbit_flipped) + +typedef enum { PARCC_GRAB_NONE, PARCC_GRAB, PARCC_GRAB_STRAFE } parcc_grab_state; + +static const parcc_float PARCC_MAX_PHI = PARCC_PI / 2.0 - 0.001; + +struct parcc_context_s { + parcc_properties props; + parcc_float eyepos[3]; + parcc_float target[3]; + parcc_grab_state grabbing; + parcc_float grab_point_pivot[3]; + parcc_float grab_point_far[3]; + parcc_float grab_point_world[3]; + parcc_float grab_point_eyepos[3]; + parcc_float grab_point_target[3]; + parcc_frame grab_frame; + int grab_winx; + int grab_winy; + parcc_float orbit_pivot[3]; + bool orbit_flipped; +}; + +static bool parcc_raycast_plane(const parcc_float origin[3], const parcc_float dir[3], + parcc_float* t, void* userdata); + +static void parcc_get_ray_far(parcc_context* context, int winx, int winy, parcc_float result[3]); + +static void parcc_move_with_constraints(parcc_context* context, const parcc_float eyepos[3], + const parcc_float target[3]); + +parcc_context* parcc_create_context(const parcc_properties* props) { + parcc_context* context = PARCC_CALLOC(parcc_context, 1); + parcc_set_properties(context, props); + parcc_goto_frame(context, parcc_get_home_frame(context)); + return context; +} + +void parcc_get_properties(const parcc_context* context, parcc_properties* props) { + *props = context->props; +} + +void parcc_set_properties(parcc_context* context, const parcc_properties* pprops) { + parcc_properties props = *pprops; + if (props.fov_degrees == 0) { + props.fov_degrees = 33; + } + if (props.zoom_speed == 0) { + props.zoom_speed = 0.01; + } + if (parcc_float3_dot(props.home_upward, props.home_upward) == 0) { + props.home_upward[1] = 1; + } + if (parcc_float4_dot(props.map_plane, props.map_plane) == 0) { + props.map_plane[2] = 1; + } + if (props.orbit_speed[0] == 0) { + props.orbit_speed[0] = 0.01; + } + if (props.orbit_speed[1] == 0) { + props.orbit_speed[1] = 0.01; + } + if (props.orbit_zoom_speed == 0) { + props.orbit_zoom_speed = 0.01; + } + if (props.orbit_strafe_speed[0] == 0) { + props.orbit_strafe_speed[0] = 0.001; + } + if (props.orbit_strafe_speed[1] == 0) { + props.orbit_strafe_speed[1] = 0.001; + } + + if (parcc_float3_dot(props.home_vector, props.home_vector) == 0) { + const parcc_float extent = props.fov_orientation == PARCC_VERTICAL ? props.map_extent[1] : + props.map_extent[0]; + const parcc_float fov = props.fov_degrees * PARCC_PI / 180.0; + props.home_vector[0] = 0; + props.home_vector[1] = 0; + props.home_vector[2] = 0.5 * extent / tan(fov / 2.0); + } + + const bool more_constrained = (int)props.map_constraint > (int)context->props.map_constraint; + const bool orientation_changed = props.fov_orientation != context->props.fov_orientation; + const bool viewport_resized = props.viewport_height != context->props.viewport_height || + props.viewport_width != context->props.viewport_width; + + context->props = props; + + if (props.mode == PARCC_MAP && (more_constrained || orientation_changed || + (viewport_resized && context->props.map_constraint == PARCC_CONSTRAIN_FULL))) { + parcc_move_with_constraints(context, context->eyepos, context->target); + } +} + +void parcc_destroy_context(parcc_context* context) { PARCC_FREE(context); } + +void parcc_get_matrices(const parcc_context* context, parcc_float projection[16], + parcc_float view[16]) { + parcc_float gaze[3]; + parcc_float3_subtract(gaze, context->target, context->eyepos); + parcc_float3_normalize(gaze); + + parcc_float right[3]; + parcc_float3_cross(right, gaze, context->props.home_upward); + parcc_float3_normalize(right); + + parcc_float upward[3]; + parcc_float3_cross(upward, right, gaze); + parcc_float3_normalize(upward); + + parcc_float16_look_at(view, context->eyepos, context->target, upward); + const parcc_properties props = context->props; + const parcc_float aspect = (parcc_float)props.viewport_width / props.viewport_height; + const parcc_float fov = props.fov_degrees; + if (context->props.fov_orientation == PARCC_HORIZONTAL) { + parcc_float16_perspective_x(projection, fov, aspect, props.near_plane, props.far_plane); + } else { + parcc_float16_perspective_y(projection, fov, aspect, props.near_plane, props.far_plane); + } +} + +void parcc_get_look_at(const parcc_context* ctx, parcc_float eyepos[3], parcc_float target[3], + parcc_float upward[3]) { + parcc_float3_copy(eyepos, ctx->eyepos); + parcc_float3_copy(target, ctx->target); + if (upward) { + parcc_float gaze[3]; + parcc_float3_subtract(gaze, ctx->target, ctx->eyepos); + parcc_float3_normalize(gaze); + + parcc_float right[3]; + parcc_float3_cross(right, gaze, ctx->props.home_upward); + parcc_float3_normalize(right); + + parcc_float3_cross(upward, right, gaze); + parcc_float3_normalize(upward); + } +} + +void parcc_grab_begin(parcc_context* context, int winx, int winy, bool strafe) { + context->grabbing = strafe ? PARCC_GRAB_STRAFE : PARCC_GRAB; + + if (context->props.mode == PARCC_MAP) { + if (!parcc_raycast(context, winx, winy, context->grab_point_world)) { + return; + } + parcc_get_ray_far(context, winx, winy, context->grab_point_far); + } + + if (context->props.mode == PARCC_ORBIT) { + context->grab_frame = parcc_get_current_frame(context); + context->grab_winx = winx; + context->grab_winy = winy; + parcc_float3_copy(context->grab_point_pivot, context->orbit_pivot); + } + + parcc_float3_copy(context->grab_point_eyepos, context->eyepos); + parcc_float3_copy(context->grab_point_target, context->target); +} + +void parcc_grab_update(parcc_context* context, int winx, int winy) { + if (context->props.mode == PARCC_MAP && context->grabbing == PARCC_GRAB) { + parcc_float u_vec[3]; + parcc_float3_subtract(u_vec, context->grab_point_world, context->grab_point_eyepos); + const parcc_float u_len = parcc_float3_length(u_vec); + + parcc_float v_vec[3]; + parcc_float3_subtract(v_vec, context->grab_point_far, context->grab_point_world); + const parcc_float v_len = parcc_float3_length(v_vec); + + parcc_float far_point[3]; + parcc_get_ray_far(context, winx, winy, far_point); + + parcc_float translation[3]; + parcc_float3_subtract(translation, far_point, context->grab_point_far); + parcc_float3_scale(translation, -u_len / v_len); + + parcc_float eyepos[3]; + parcc_float3_add(eyepos, context->grab_point_eyepos, translation); + + parcc_float target[3]; + parcc_float3_add(target, context->grab_point_target, translation); + + parcc_move_with_constraints(context, eyepos, target); + } + + if (context->props.mode == PARCC_ORBIT && context->grabbing == PARCC_GRAB) { + parcc_frame frame = parcc_get_current_frame(context); + + const int delx = context->grab_winx - winx; + const int dely = context->grab_winy - winy; + + const parcc_float phi = dely * context->props.orbit_speed[1]; + const parcc_float theta = delx * context->props.orbit_speed[0]; + + frame.phi = context->grab_frame.phi + phi; + frame.theta = context->grab_frame.theta + theta; + frame.phi = PARCC_CLAMP(frame.phi, -PARCC_MAX_PHI, PARCC_MAX_PHI); + + parcc_goto_frame(context, frame); + } + + if (context->props.mode == PARCC_ORBIT && context->grabbing == PARCC_GRAB_STRAFE) { + parcc_float upward[3]; + + parcc_float gaze[3]; + parcc_float3_subtract(gaze, context->target, context->eyepos); + parcc_float3_normalize(gaze); + + parcc_float right[3]; + parcc_float3_cross(right, gaze, context->props.home_upward); + parcc_float3_normalize(right); + + parcc_float3_cross(upward, right, gaze); + parcc_float3_normalize(upward); + + const int delx = context->grab_winx - winx; + const int dely = context->grab_winy - winy; + + const parcc_float dx = delx * context->props.orbit_strafe_speed[0]; + const parcc_float dy = dely * context->props.orbit_strafe_speed[1]; + + parcc_float3_scale(right, dx); + parcc_float3_scale(upward, dy); + + parcc_float movement[3]; + parcc_float3_add(movement, upward, right); + + parcc_float3_add(context->orbit_pivot, context->grab_point_pivot, movement); + parcc_float3_add(context->eyepos, context->grab_point_eyepos, movement); + parcc_float3_add(context->target, context->grab_point_target, movement); + } +} + +void parcc_zoom(parcc_context* context, int winx, int winy, parcc_float scrolldelta) { + if (context->props.mode == PARCC_MAP) { + parcc_float grab_point_world[3]; + if (!parcc_raycast(context, winx, winy, grab_point_world)) { + return; + } + + // We intentionally avoid normalizing this vector since you usually + // want to slow down when approaching the surface. + parcc_float u_vec[3]; + parcc_float3_subtract(u_vec, grab_point_world, context->eyepos); + + // Prevent getting stuck; this needs to be done regardless + // of the user's min_distance setting, which is enforced in + // parcc_move_with_constraints. + const parcc_float zoom_speed = context->props.zoom_speed; + if (scrolldelta > 0.0) { + const parcc_float distance_to_surface = parcc_float3_length(u_vec); + if (distance_to_surface < zoom_speed) { + return; + } + } + + parcc_float3_scale(u_vec, scrolldelta * zoom_speed); + + parcc_float eyepos[3]; + parcc_float3_add(eyepos, context->eyepos, u_vec); + + parcc_float target[3]; + parcc_float3_add(target, context->target, u_vec); + + parcc_move_with_constraints(context, eyepos, target); + } + + if (context->props.mode == PARCC_ORBIT) { + parcc_float gaze[3]; + parcc_float3_subtract(gaze, context->target, context->eyepos); + parcc_float3_normalize(gaze); + parcc_float3_scale(gaze, context->props.orbit_zoom_speed * scrolldelta); + + parcc_float v0[3]; + parcc_float3_subtract(v0, context->orbit_pivot, context->eyepos); + + parcc_float3_add(context->eyepos, context->eyepos, gaze); + parcc_float3_add(context->target, context->target, gaze); + + parcc_float v1[3]; + parcc_float3_subtract(v1, context->orbit_pivot, context->eyepos); + + if (parcc_float3_dot(v0, v1) < 0) { + context->orbit_flipped = !context->orbit_flipped; + } + } +} + +void parcc_grab_end(parcc_context* context) { context->grabbing = PARCC_GRAB_NONE; } + +bool parcc_raycast(parcc_context* context, int winx, int winy, parcc_float result[3]) { + const parcc_float width = context->props.viewport_width; + const parcc_float height = context->props.viewport_height; + const parcc_float fov = context->props.fov_degrees * PARCC_PI / 180.0; + const bool vertical_fov = context->props.fov_orientation == PARCC_VERTICAL; + const parcc_float* origin = context->eyepos; + + parcc_float gaze[3]; + parcc_float3_subtract(gaze, context->target, origin); + parcc_float3_normalize(gaze); + + parcc_float right[3]; + parcc_float3_cross(right, gaze, context->props.home_upward); + parcc_float3_normalize(right); + + parcc_float upward[3]; + parcc_float3_cross(upward, right, gaze); + parcc_float3_normalize(upward); + + // Remap the grid coordinate into [-1, +1] and shift it to the pixel center. + const parcc_float u = 2.0 * (winx + 0.5) / width - 1.0; + const parcc_float v = 2.0 * (winy + 0.5) / height - 1.0; + + // Compute the tangent of the field-of-view angle as well as the aspect ratio. + const parcc_float tangent = tan(fov / 2.0); + const parcc_float aspect = width / height; + + // Adjust the gaze so it goes through the pixel of interest rather than the grid center. + if (vertical_fov) { + parcc_float3_scale(right, tangent * u * aspect); + parcc_float3_scale(upward, tangent * v); + } else { + parcc_float3_scale(right, tangent * u); + parcc_float3_scale(upward, tangent * v / aspect); + } + parcc_float3_add(gaze, gaze, right); + parcc_float3_add(gaze, gaze, upward); + parcc_float3_normalize(gaze); + + // Invoke the user's callback or fallback function. + parcc_raycast_fn callback = context->props.raycast_function; + parcc_raycast_fn fallback = parcc_raycast_plane; + void* userdata = context->props.raycast_userdata; + if (!callback) { + callback = fallback; + userdata = context; + } + + // If the ray misses, then try the fallback function. + parcc_float t; + if (!callback(origin, gaze, &t, userdata)) { + if (callback == fallback) { + return false; + } + if (!fallback(origin, gaze, &t, context)) { + return false; + } + } + + parcc_float3_scale(gaze, t); + parcc_float3_add(result, origin, gaze); + return true; +} + +parcc_frame parcc_get_current_frame(const parcc_context* context) { + parcc_frame frame; + frame.mode = context->props.mode; + + if (context->props.mode == PARCC_MAP) { + const parcc_float* origin = context->eyepos; + const parcc_float* upward = context->props.home_upward; + + parcc_float direction[3]; + parcc_float3_subtract(direction, context->target, origin); + parcc_float3_normalize(direction); + + parcc_float distance; + parcc_raycast_plane(origin, direction, &distance, (void*)context); + + const parcc_float fov = context->props.fov_degrees * PARCC_PI / 180.0; + const parcc_float half_extent = distance * tan(fov / 2); + + parcc_float target[3]; + parcc_float3_scale(direction, distance); + parcc_float3_add(target, origin, direction); + + // Compute the tangent frame defined by the map_plane normal and the home_upward vector. + parcc_float uvec[3]; + parcc_float vvec[3]; + parcc_float target_to_eye[3]; + + parcc_float3_copy(target_to_eye, context->props.map_plane); + parcc_float3_cross(uvec, upward, target_to_eye); + parcc_float3_cross(vvec, target_to_eye, uvec); + + parcc_float3_subtract(target, target, context->props.home_target); + + frame.extent = half_extent * 2; + frame.center[0] = parcc_float3_dot(uvec, target); + frame.center[1] = parcc_float3_dot(vvec, target); + } + + if (context->props.mode == PARCC_ORBIT) { + parcc_float pivot_to_eye[3]; + parcc_float3_subtract(pivot_to_eye, context->eyepos, context->orbit_pivot); + const parcc_float d = parcc_float3_length(pivot_to_eye); + const parcc_float x = pivot_to_eye[0] / d; + const parcc_float y = pivot_to_eye[1] / d; + const parcc_float z = pivot_to_eye[2] / d; + frame.phi = asin(y); + frame.theta = atan2(x, z); + frame.pivot_distance = context->orbit_flipped ? -d : d; + parcc_float3_copy(frame.pivot, context->orbit_pivot); + } + + return frame; +} + +parcc_frame parcc_get_home_frame(const parcc_context* context) { + const parcc_float width = context->props.viewport_width; + const parcc_float height = context->props.viewport_height; + const parcc_float aspect = width / height; + + parcc_frame frame; + frame.mode = context->props.mode; + + if (frame.mode == PARCC_MAP) { + const parcc_float map_width = context->props.map_extent[0] / 2; + const parcc_float map_height = context->props.map_extent[1] / 2; + const bool horiz = context->props.fov_orientation == PARCC_HORIZONTAL; + frame.extent = horiz ? context->props.map_extent[0] : context->props.map_extent[1]; + frame.center[0] = 0; + frame.center[1] = 0; + if (context->props.map_constraint != PARCC_CONSTRAIN_FULL) { + return frame; + } + if (horiz) { + parcc_float vp_width = frame.extent / 2; + parcc_float vp_height = vp_width / aspect; + if (map_height < vp_height) { + frame.extent = 2 * map_height * aspect; + } + } else { + parcc_float vp_height = frame.extent / 2; + parcc_float vp_width = vp_height * aspect; + if (map_width < vp_width) { + frame.extent = 2 * map_width / aspect; + } + } + } + + if (frame.mode == PARCC_ORBIT) { + frame.theta = frame.phi = 0; + parcc_float3_copy(frame.pivot, context->props.home_target); + frame.pivot_distance = parcc_float3_length(context->props.home_vector); + } + + return frame; +} + +void parcc_goto_frame(parcc_context* context, parcc_frame frame) { + if (context->props.mode == PARCC_MAP) { + const parcc_float* upward = context->props.home_upward; + const parcc_float half_extent = frame.extent / 2.0; + const parcc_float fov = context->props.fov_degrees * PARCC_PI / 180.0; + const parcc_float distance = half_extent / tan(fov / 2); + + // Compute the tangent frame defined by the map_plane normal and the home_upward vector. + parcc_float uvec[3]; + parcc_float vvec[3]; + parcc_float target_to_eye[3]; + + parcc_float3_copy(target_to_eye, context->props.map_plane); + parcc_float3_cross(uvec, upward, target_to_eye); + parcc_float3_cross(vvec, target_to_eye, uvec); + + // Scale the U and V components by the frame coordinate. + parcc_float3_scale(uvec, frame.center[0]); + parcc_float3_scale(vvec, frame.center[1]); + + // Obtain the new target position by adding U and V to home_target. + parcc_float3_copy(context->target, context->props.home_target); + parcc_float3_add(context->target, context->target, uvec); + parcc_float3_add(context->target, context->target, vvec); + + // Obtain the new eye position by adding the scaled plane normal to the new target + // position. + parcc_float3_scale(target_to_eye, distance); + parcc_float3_add(context->eyepos, context->target, target_to_eye); + } + + if (context->props.mode == PARCC_ORBIT) { + parcc_float3_copy(context->orbit_pivot, frame.pivot); + const parcc_float x = sin(frame.theta) * cos(frame.phi); + const parcc_float y = sin(frame.phi); + const parcc_float z = cos(frame.theta) * cos(frame.phi); + parcc_float3_set(context->eyepos, x, y, z); + parcc_float3_scale(context->eyepos, fabs(frame.pivot_distance)); + parcc_float3_add(context->eyepos, context->eyepos, context->orbit_pivot); + + context->orbit_flipped = frame.pivot_distance < 0; + + parcc_float3_set(context->target, x, y, z); + parcc_float3_scale(context->target, context->orbit_flipped ? 1.0 : -1.0); + parcc_float3_add(context->target, context->target, context->eyepos); + } +} + +parcc_frame parcc_interpolate_frames(parcc_frame a, parcc_frame b, double t) { + parcc_frame frame; + if (a.mode == PARCC_MAP && b.mode == PARCC_MAP) { + const double rho = sqrt(2.0); + const double rho2 = 2, rho4 = 4; + const double ux0 = a.center[0], uy0 = a.center[1], w0 = a.extent; + const double ux1 = b.center[0], uy1 = b.center[1], w1 = b.extent; + const double dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, d1 = sqrt(d2); + const double b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2.0 * w0 * rho2 * d1); + const double b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2.0 * w1 * rho2 * d1); + const double r0 = log(sqrt(b0 * b0 + 1.0) - b0); + const double r1 = log(sqrt(b1 * b1 + 1) - b1); + const double dr = r1 - r0; + const int valid_dr = (dr == dr) && dr != 0; + const double S = (valid_dr ? dr : log(w1 / w0)) / rho; + const double s = t * S; + if (valid_dr) { + const double coshr0 = cosh(r0); + const double u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0)); + frame.center[0] = ux0 + u * dx; + frame.center[1] = uy0 + u * dy; + frame.extent = w0 * coshr0 / cosh(rho * s + r0); + return frame; + } + frame.center[0] = ux0 + t * dx; + frame.center[1] = uy0 + t * dy; + frame.extent = w0 * exp(rho * s); + } else if (a.mode == PARCC_ORBIT && b.mode == PARCC_ORBIT) { + frame.phi = parcc_float_lerp(a.phi, b.phi, t); + frame.theta = parcc_float_lerp(a.theta, b.theta, t); + frame.pivot_distance = parcc_float_lerp(a.pivot_distance, b.pivot_distance, t); + parcc_float3_lerp(frame.pivot, a.pivot, b.pivot, t); + } else { + // Cross-mode interpolation is not implemented. + frame = b; + } + return frame; +} + +double parcc_get_interpolation_duration(parcc_frame a, parcc_frame b) { + if (a.mode == PARCC_MAP && b.mode == PARCC_MAP) { + const double rho = sqrt(2.0); + const double rho2 = 2, rho4 = 4; + const double ux0 = a.center[0], uy0 = a.center[1], w0 = a.extent; + const double ux1 = b.center[0], uy1 = b.center[1], w1 = b.extent; + const double dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, d1 = sqrt(d2); + const double b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2.0 * w0 * rho2 * d1); + const double b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2.0 * w1 * rho2 * d1); + const double r0 = log(sqrt(b0 * b0 + 1.0) - b0); + const double r1 = log(sqrt(b1 * b1 + 1) - b1); + const double dr = r1 - r0; + const int valid_dr = (dr == dr) && dr != 0; + const double S = (valid_dr ? dr : log(w1 / w0)) / rho; + return fabs(S); + } else if (a.mode == PARCC_ORBIT && b.mode == PARCC_ORBIT) { + return 1; + } else { + // Cross-mode interpolation is not implemented. + } + return 0; +} + +static bool parcc_raycast_plane(const parcc_float origin[3], const parcc_float dir[3], + parcc_float* t, void* userdata) { + parcc_context* context = (parcc_context*)userdata; + const parcc_float* plane = context->props.map_plane; + parcc_float n[3] = {plane[0], plane[1], plane[2]}; + parcc_float p0[3] = {plane[0], plane[1], plane[2]}; + parcc_float3_scale(p0, plane[3]); + const parcc_float denom = -parcc_float3_dot(n, dir); + if (denom > 1e-6 || denom < -1e-6) { + parcc_float p0l0[3]; + parcc_float3_subtract(p0l0, p0, origin); + *t = parcc_float3_dot(p0l0, n) / -denom; + return *t >= 0; + } + return false; +} + +// Finds the point on the frustum's far plane that a pick ray intersects. +static void parcc_get_ray_far(parcc_context* context, int winx, int winy, parcc_float result[3]) { + const parcc_float width = context->props.viewport_width; + const parcc_float height = context->props.viewport_height; + const parcc_float fov = context->props.fov_degrees * PARCC_PI / 180.0; + const bool vertical_fov = context->props.fov_orientation == PARCC_VERTICAL; + const parcc_float* origin = context->eyepos; + + parcc_float gaze[3]; + parcc_float3_subtract(gaze, context->target, origin); + parcc_float3_normalize(gaze); + + parcc_float right[3]; + parcc_float3_cross(right, gaze, context->props.home_upward); + parcc_float3_normalize(right); + + parcc_float upward[3]; + parcc_float3_cross(upward, right, gaze); + parcc_float3_normalize(upward); + + // Remap the grid coordinate into [-1, +1] and shift it to the pixel center. + const parcc_float u = 2.0 * (winx + 0.5) / width - 1.0; + const parcc_float v = 2.0 * (winy + 0.5) / height - 1.0; + + // Compute the tangent of the field-of-view angle as well as the aspect ratio. + const parcc_float tangent = tan(fov / 2.0); + const parcc_float aspect = width / height; + + // Adjust the gaze so it goes through the pixel of interest rather than the grid center. + if (vertical_fov) { + parcc_float3_scale(right, tangent * u * aspect); + parcc_float3_scale(upward, tangent * v); + } else { + parcc_float3_scale(right, tangent * u); + parcc_float3_scale(upward, tangent * v / aspect); + } + parcc_float3_add(gaze, gaze, right); + parcc_float3_add(gaze, gaze, upward); + parcc_float3_scale(gaze, context->props.far_plane); + parcc_float3_add(result, origin, gaze); +} + +static void parcc_move_with_constraints(parcc_context* context, const parcc_float eyepos[3], + const parcc_float target[3]) { + const parcc_constraint constraint = context->props.map_constraint; + const parcc_float width = context->props.viewport_width; + const parcc_float height = context->props.viewport_height; + const parcc_float aspect = width / height; + const parcc_float map_width = context->props.map_extent[0] / 2; + const parcc_float map_height = context->props.map_extent[1] / 2; + const parcc_frame home = parcc_get_home_frame(context); + const parcc_frame previous_frame = parcc_get_current_frame(context); + const parcc_float fov = context->props.fov_degrees * PARCC_PI / 180.0; + const parcc_float min_extent = 2.0 * context->props.map_min_distance * tan(fov / 2); + + parcc_float3_copy(context->eyepos, eyepos); + parcc_float3_copy(context->target, target); + + parcc_frame frame = parcc_get_current_frame(context); + + if (frame.extent < min_extent) { + frame.extent = min_extent; + frame.center[0] = previous_frame.center[0]; + frame.center[1] = previous_frame.center[1]; + } + + if (constraint == PARCC_CONSTRAIN_NONE) { + parcc_goto_frame(context, frame); + return; + } + + parcc_float x = frame.center[0]; + parcc_float y = frame.center[1]; + + if (context->props.fov_orientation == PARCC_HORIZONTAL) { + parcc_float vp_width = frame.extent / 2; + parcc_float vp_height = vp_width / aspect; + if (map_width < vp_width) { + frame.extent = home.extent; + vp_width = frame.extent / 2; + vp_height = vp_width / aspect; + x = 0; + y = previous_frame.center[1]; + } + x = PARCC_CLAMP(x, -map_width + vp_width, map_width - vp_width); + if (map_height < vp_height) { + if (context->props.map_constraint == PARCC_CONSTRAIN_FULL) { + frame.extent = 2 * map_height * aspect; + vp_width = frame.extent / 2; + vp_height = vp_width / aspect; + x = previous_frame.center[0]; + x = PARCC_CLAMP(x, -map_width + vp_width, map_width - vp_width); + y = PARCC_CLAMP(y, -map_height + vp_height, map_height - vp_height); + } else { + y = PARCC_CLAMP(y, -vp_height + map_height, vp_height - map_height); + } + } else { + y = PARCC_CLAMP(y, -map_height + vp_height, map_height - vp_height); + } + } else { + parcc_float vp_height = frame.extent / 2; + parcc_float vp_width = vp_height * aspect; + if (map_height < vp_height) { + frame.extent = home.extent; + vp_height = frame.extent / 2; + vp_width = vp_height * aspect; + y = 0; + x = previous_frame.center[0]; + } + y = PARCC_CLAMP(y, -map_height + vp_height, map_height - vp_height); + if (map_width < vp_width) { + if (context->props.map_constraint == PARCC_CONSTRAIN_FULL) { + frame.extent = 2 * map_width / aspect; + vp_height = frame.extent / 2; + vp_width = vp_height * aspect; + y = previous_frame.center[1]; + y = PARCC_CLAMP(y, -map_height + vp_height, map_height - vp_height); + x = PARCC_CLAMP(x, -map_width + vp_width, map_width - vp_width); + } else { + x = PARCC_CLAMP(x, -vp_width + map_width, vp_width - map_width); + } + } else { + x = PARCC_CLAMP(x, -map_width + vp_width, map_width - vp_width); + } + } + + frame.center[0] = x; + frame.center[1] = y; + parcc_goto_frame(context, frame); +} + +#endif // PAR_CAMERA_CONTROL_IMPLEMENTATION +#endif // PAR_CAMERA_CONTROL_H + +// par_camera_control is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_easings.h b/source/engine/thirdparty/par/par_easings.h new file mode 100644 index 0000000..2f8c4f9 --- /dev/null +++ b/source/engine/thirdparty/par/par_easings.h @@ -0,0 +1,429 @@ +// EASINGS :: https://github.com/prideout/par +// Robert Penner's easing functions. +// +// Distributed under the MIT License, see bottom of file. + +// ----------------------------------------------------------------------------- +// BEGIN PUBLIC API +// ----------------------------------------------------------------------------- + +#ifndef PAR_EASINGS_H +#define PAR_EASINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PAR_EASINGS_FLOAT +#define PAR_EASINGS_FLOAT float +#endif + +PAR_EASINGS_FLOAT par_easings_linear(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_in_cubic(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_out_cubic(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_in_out_cubic(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_in_quad(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_out_quad(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_in_out_quad(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_in_elastic(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_out_elastic(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_in_out_elastic(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_in_bounce(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_out_bounce(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_in_out_bounce(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_in_back(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_out_back(PAR_EASINGS_FLOAT t); +PAR_EASINGS_FLOAT par_easings_in_out_back(PAR_EASINGS_FLOAT t); + +#ifndef PAR_PI +#define PAR_PI (3.14159265359) +#define PAR_MIN(a, b) (a > b ? b : a) +#define PAR_MAX(a, b) (a > b ? a : b) +#define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) +#define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#define PAR_SQR(a) ((a) * (a)) +#endif + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- + +#ifdef PAR_EASINGS_IMPLEMENTATION + +#define PARFLT PAR_EASINGS_FLOAT + +PARFLT par_easings__linear(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + return c * t / d + b; +} + +PARFLT par_easings__in_cubic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d; + return c * t * t * t + b; +} + +PARFLT par_easings__out_cubic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t = t / d - 1; + return c * (t * t * t + 1) + b; +} + +PARFLT par_easings__in_out_cubic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t + b; + } + t -= 2; + return c / 2 * (t * t * t + 2) + b; +} + +PARFLT par_easings__in_quad(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d; + return c * t * t + b; +} + +PARFLT par_easings__out_quad(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d; + return -c * t * (t - 2) + b; +} + +PARFLT par_easings__in_out_quad(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d / 2; + if (t < 1) { + return c / 2 * t * t + b; + } + --t; + return -c / 2 * (t * (t - 2) - 1) + b; +} + +PARFLT par_easings__in_quart(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d; + return c * t * t * t * t + b; +} + +PARFLT par_easings__out_quart(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t = t / d - 1; + return -c * (t * t * t * t - 1) + b; +} + +PARFLT par_easings__in_out_quart(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t + b; + } + t -= 2; + return -c / 2 * (t * t * t * t - 2) + b; +} + +PARFLT par_easings__in_quint(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d; + return c * t * t * t * t * t + b; +} + +PARFLT par_easings__out_quint(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t = t / d - 1; + return c * (t * t * t * t * t + 1) + b; +} + +PARFLT par_easings__in_out_quint(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t * t * t + b; + } + t -= 2; + return c / 2 * (t * t * t * t * t + 2) + b; +} + +PARFLT par_easings__in_sine(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + return -c * cos(t / d * (PAR_PI / 2)) + c + b; +} + +PARFLT par_easings__out_sine(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + return c * sin(t / d * (PAR_PI / 2)) + b; +} + +PARFLT par_easings__in_out_sine(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + return -c / 2 * (cos(PAR_PI * t / d) - 1) + b; +} + +PARFLT par_easings__in_out_expo(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d / 2; + if (t < 1) { + return c / 2 * pow(2, 10 * (t - 1)) + b; + } + return c / 2 * (-pow(2, -10 * --t) + 2) + b; +} + +PARFLT par_easings__in_circ(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d; + return -c * (sqrt(1 - t * t) - 1) + b; +} + +PARFLT par_easings__out_circ(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t = t / d - 1; + return c * sqrt(1 - t * t) + b; +} + +PARFLT par_easings__in_out_circ(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d / 2; + if (t < 1) { + return -c / 2 * (sqrt(1 - t * t) - 1) + b; + } + t -= 2; + return c / 2 * (sqrt(1 - t * t) + 1) + b; +} + +PARFLT par_easings__in_elastic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + PARFLT a, p, s; + s = 1.70158; + p = 0; + a = c; + if (!p) { + p = d * 0.3; + } + if (a < fabsf(c)) { + a = c; + s = p / 4; + } else { + s = p / (2 * PAR_PI) * asin(c / a); + } + t -= 1; + return -(a * pow(2, 10 * t) * + sin((t * d - s) * (2 * PAR_PI) / p)) + b; +} + +PARFLT par_easings__out_elastic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + PARFLT a, p, s; + s = 1.70158; + p = 0; + a = c; + if (!p) { + p = d * 0.3; + } + if (a < fabsf(c)) { + a = c; + s = p / 4; + } else { + s = p / (2 * PAR_PI) * asin(c / a); + } + return a * pow(2, -10 * t) * + sin((t * d - s) * (2 * PAR_PI) / p) + c + b; +} + +PARFLT par_easings__in_out_elastic(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + PARFLT a, p, s; + s = 1.70158; + p = 0; + a = c; + if (!p) { + p = d * (0.3 * 1.5); + } + if (a < fabsf(c)) { + a = c; + s = p / 4; + } else { + s = p / (2 * PAR_PI) * asin(c / a); + } + if (t < 1) { + t -= 1; + return -0.5 * (a * pow(2, 10 * t) * + sin((t * d - s) * (2 * PAR_PI) / p)) + b; + } + t -= 1; + return a * pow(2, -10 * t) * + sin((t * d - s) * (2 * PAR_PI) / p) * 0.5 + c + b; +} + +PARFLT par_easings__in_back(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + PARFLT s = 1.70158; + t/=d; + return c*t*t*((s+1)*t - s) + b; +} + +PARFLT par_easings__out_back(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + PARFLT s = 1.70158; + t=t/d-1; + return c*(t*t*((s+1)*t + s) + 1) + b; +} + +PARFLT par_easings__in_out_back(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + PARFLT s = 1.70158; + t/=d/2; + s*=1.525; + if (t < 1) { return c/2*(t*t*((s+1)*t - s)) + b; } + s*=1.525; + t-=2; + return (c/2*(t*t*(s+1)*t + s) + 2) + b; +} + +PARFLT par_easings__out_bounce(PARFLT t, PARFLT b, PARFLT c, PARFLT d); + +PARFLT par_easings__in_bounce(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + PARFLT v = par_easings__out_bounce(d - t, 0, c, d); + return c - v + b; +} + +PARFLT par_easings__out_bounce(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + t /= d; + if (t < 1.0 / 2.75) { + return c * (7.5625 * t * t) + b; + } + if (t < 2.0 / 2.75) { + t -= 1.5 / 2.75; + return c * (7.5625 * t * t + 0.75) + b; + } + if (t < 2.5 / 2.75) { + t -= 2.25 / 2.75; + return c * (7.5625 * t * t + 0.9375) + b; + } + t -= 2.625 / 2.75; + return c * (7.5625 * t * t + 0.984375) + b; +} + +PARFLT par_easings__in_out_bounce(PARFLT t, PARFLT b, PARFLT c, PARFLT d) +{ + PARFLT v; + if (t < d / 2) { + v = par_easings__in_bounce(t * 2, 0, c, d); + return v * 0.5 + b; + } + v = par_easings__out_bounce(t * 2 - d, 0, c, d); + return v * 0.5 + c * 0.5 + b; +} + +PARFLT par_easings_linear(PARFLT t) +{ + return par_easings__linear(t, 0, 1, 1); +} + +PARFLT par_easings_in_cubic(PARFLT t) +{ + return par_easings__in_cubic(t, 0, 1, 1); +} + +PARFLT par_easings_out_cubic(PARFLT t) +{ + return par_easings__out_cubic(t, 0, 1, 1); +} + +PARFLT par_easings_in_out_cubic(PARFLT t) +{ + return par_easings__in_out_cubic(t, 0, 1, 1); +} + +PARFLT par_easings_in_quad(PARFLT t) +{ + return par_easings__in_quad(t, 0, 1, 1); +} + +PARFLT par_easings_out_quad(PARFLT t) +{ + return par_easings__out_quad(t, 0, 1, 1); +} + +PARFLT par_easings_in_out_quad(PARFLT t) +{ + return par_easings__in_out_quad(t, 0, 1, 1); +} + +PARFLT par_easings_in_elastic(PARFLT t) +{ + return par_easings__in_elastic(t, 0, 1, 1); +} + +PARFLT par_easings_out_elastic(PARFLT t) +{ + return par_easings__out_elastic(t, 0, 1, 1); +} + +PARFLT par_easings_in_out_elastic(PARFLT t) +{ + return par_easings__in_out_elastic(t, 0, 1, 1); +} + +PARFLT par_easings_in_bounce(PARFLT t) +{ + return par_easings__in_bounce(t, 0, 1, 1); +} + +PARFLT par_easings_out_bounce(PARFLT t) +{ + return par_easings__out_bounce(t, 0, 1, 1); +} + +PARFLT par_easings_in_out_bounce(PARFLT t) +{ + return par_easings__in_out_bounce(t, 0, 1, 1); +} + +PARFLT par_easings_in_back(PARFLT t) +{ + return par_easings__in_back(t, 0, 1, 1); +} + +PARFLT par_easings_out_back(PARFLT t) +{ + return par_easings__out_back(t, 0, 1, 1); +} + +PARFLT par_easings_in_out_back(PARFLT t) +{ + return par_easings__in_out_back(t, 0, 1, 1); +} + +#undef PARFLT + +#endif // PAR_EASINGS_IMPLEMENTATION +#endif // PAR_EASINGS_H + +// par_easings is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_easycurl.h b/source/engine/thirdparty/par/par_easycurl.h new file mode 100644 index 0000000..d9106e1 --- /dev/null +++ b/source/engine/thirdparty/par/par_easycurl.h @@ -0,0 +1,228 @@ +// EASYCURL :: https://github.com/prideout/par +// Wrapper around libcurl for performing simple synchronous HTTP requests. +// +// Distributed under the MIT License, see bottom of file. + +// ----------------------------------------------------------------------------- +// BEGIN PUBLIC API +// ----------------------------------------------------------------------------- + +#ifndef PAR_EASYCURL_H +#define PAR_EASYCURL_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned char par_byte; + +// Call this before calling any other easycurl function. The flags are +// currently unused, so you can just pass 0. +void par_easycurl_init(unsigned int flags); + +// Allocates a memory buffer and downloads a data blob into it. +// Returns 1 for success and 0 otherwise. The byte count should be +// pre-allocated. The caller is responsible for freeing the returned data. +// This does not do any caching! +int par_easycurl_to_memory(char const* url, par_byte** data, int* nbytes); + +// Downloads a file from the given URL and saves it to disk. Returns 1 for +// success and 0 otherwise. +int par_easycurl_to_file(char const* srcurl, char const* dstpath); + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- + +#ifdef PAR_EASYCURL_IMPLEMENTATION + +#include +#include +#include +#include + +#ifdef _MSC_VER +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#else +#include +#endif + +static int _ready = 0; + +void par_easycurl_init(unsigned int flags) +{ + if (!_ready) { + curl_global_init(CURL_GLOBAL_DEFAULT); + _ready = 1; + } +} + +void par_easycurl_shutdown() +{ + if (_ready) { + curl_global_cleanup(); + } +} + +static size_t onheader(void* v, size_t size, size_t nmemb) +{ + size_t n = size * nmemb; + char* h = (char*) v; + if (n > 14 && !strncasecmp("Last-Modified:", h, 14)) { + char const* s = h + 14; + time_t r = curl_getdate(s, 0); + if (r != -1) { + // TODO handle last-modified + } + } else if (n > 5 && !strncasecmp("ETag:", h, 5)) { + // TODO handle etag + } + return n; +} + +typedef struct { + par_byte* data; + int nbytes; +} par_easycurl_buffer; + +static size_t onwrite(char* contents, size_t size, size_t nmemb, void* udata) +{ + size_t realsize = size * nmemb; + par_easycurl_buffer* mem = (par_easycurl_buffer*) udata; + mem->data = (par_byte*) realloc(mem->data, mem->nbytes + realsize + 1); + if (!mem->data) { + return 0; + } + memcpy(mem->data + mem->nbytes, contents, realsize); + mem->nbytes += realsize; + mem->data[mem->nbytes] = 0; + return realsize; +} + +#if IOS_EXAMPLE +bool curlToMemory(char const* url, uint8_t** data, int* nbytes) +{ + NSString* nsurl = + [NSString stringWithCString:url encoding:NSASCIIStringEncoding]; + NSMutableURLRequest* request = + [NSMutableURLRequest requestWithURL:[NSURL URLWithString:nsurl]]; + [request setTimeoutInterval: TIMEOUT_SECONDS]; + NSURLResponse* response = nil; + NSError* error = nil; + // Use the simple non-async API because we're in a secondary thread anyway. + NSData* nsdata = [NSURLConnection sendSynchronousRequest:request + returningResponse:&response + error:&error]; + if (error == nil) { + *nbytes = (int) [nsdata length]; + *data = (uint8_t*) malloc([nsdata length]); + memcpy(*data, [nsdata bytes], [nsdata length]); + return true; + } + BLAZE_ERROR("%s\n", [[error localizedDescription] UTF8String]); + return false; +} + +#endif + +int par_easycurl_to_memory(char const* url, par_byte** data, int* nbytes) +{ + char errbuf[CURL_ERROR_SIZE] = {0}; + par_easycurl_buffer buffer = {(par_byte*) malloc(1), 0}; + long code = 0; + long status = 0; + CURL* handle = curl_easy_init(); + curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate"); + curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 8); + curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, onwrite); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, &buffer); + curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, onheader); + curl_easy_setopt(handle, CURLOPT_URL, url); + curl_easy_setopt(handle, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); + curl_easy_setopt(handle, CURLOPT_TIMEVALUE, 0); + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, 0); + curl_easy_setopt(handle, CURLOPT_TIMEOUT, 60); + curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errbuf); + curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0); + CURLcode res = curl_easy_perform(handle); + if (res != CURLE_OK) { + printf("CURL Error: %s\n", errbuf); + return 0; + } + curl_easy_getinfo(handle, CURLINFO_CONDITION_UNMET, &code); + curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status); + if (status == 304 || status >= 400) { + return 0; + } + *data = buffer.data; + *nbytes = buffer.nbytes; + curl_easy_cleanup(handle); + return 1; +} + +int par_easycurl_to_file(char const* srcurl, char const* dstpath) +{ + long code = 0; + long status = 0; + FILE* filehandle = fopen(dstpath, "wb"); + if (!filehandle) { + printf("Unable to open %s for writing.\n", dstpath); + return 0; + } + CURL* handle = curl_easy_init(); + curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate"); + curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 8); + curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, filehandle); + curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, onheader); + curl_easy_setopt(handle, CURLOPT_URL, srcurl); + curl_easy_setopt(handle, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); + curl_easy_setopt(handle, CURLOPT_TIMEVALUE, 0); + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, 0); + curl_easy_setopt(handle, CURLOPT_TIMEOUT, 60); + curl_easy_perform(handle); + curl_easy_getinfo(handle, CURLINFO_CONDITION_UNMET, &code); + curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &status); + fclose(filehandle); + if (status == 304 || status >= 400) { + remove(dstpath); + return 0; + } + curl_easy_cleanup(handle); + return 1; +} + +#endif // PAR_EASYCURL_IMPLEMENTATION +#endif // PAR_EASYCURL_H + +// par_easycurl is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_filecache.h b/source/engine/thirdparty/par/par_filecache.h new file mode 100644 index 0000000..e022bb5 --- /dev/null +++ b/source/engine/thirdparty/par/par_filecache.h @@ -0,0 +1,458 @@ +// FILECACHE :: https://github.com/prideout/par +// Simple file-based LRU cache for blobs with content-addressable names. +// +// Each cached item is stored on disk as "{PREFIX}{NAME}", where {PREFIX} +// is passed in when initializing the cache. You'll probably want to specify +// a folder path for your prefix, including the trailing slash. +// +// Each item is divided into a payload (arbitrary size) and an optional header +// (fixed size). The structure of the payload and header are completely up to +// you. The list of items is stored in a text file at "{PREFIX}table", which +// contains a list of names, timestamps, and byte counts. This table is loaded +// only once, but is saved every time the client fetches a blob from the cache, +// so that the most-recently-accessed timestamps are always up to date, even +// when your application doesn't close gracefully. +// +// Distributed under the MIT License, see bottom of file. + +// ----------------------------------------------------------------------------- +// BEGIN PUBLIC API +// ----------------------------------------------------------------------------- + +#ifndef PAR_FILECACHE_H +#define PAR_FILECACHE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// Initialize the filecache using the given prefix (usually a folder path with +// a trailing slash) and the given maximum byte count. If items already exist +// in the cache when this is called, they are not evicted. Cached items are +// meant to persist from run to run. +void par_filecache_init(char const* prefix, int maxsize); + +// Save a blob to the cache using the given unique name. If adding the blob +// would cause the cache to exceed maxsize, the least-recently-used item is +// evicted at this time. +void par_filecache_save(char const* name, uint8_t const* payload, + int payloadsize, uint8_t const* header, int headersize); + +// Check if the given blob is in the cache; if not, return 0. If so, return 1 +// and allocate new memory for payload. The caller should free the payload. +// The header is preallocated so the caller needs to know its size beforehand. +bool par_filecache_load(char const* name, uint8_t** payload, int* payloadsize, + uint8_t* header, int headersize); + +// Remove all items from the cache. +void par_filecache_evict_all(); + +// Set this to zero if you wish to avoid LZ4 compression. I recommend using +// it though, because it's very fast and it's a two-file library. +#ifndef ENABLE_LZ4 +#define ENABLE_LZ4 0 +#endif + +#ifndef PAR_FILECACHE_VERBOSE +#define PAR_FILECACHE_VERBOSE 0 +#endif + +#ifndef PAR_PI +#define PAR_PI (3.14159265359) +#define PAR_MIN(a, b) (a > b ? b : a) +#define PAR_MAX(a, b) (a > b ? a : b) +#define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) +#define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#define PAR_SQR(a) ((a) * (a)) +#endif + +#ifndef PAR_MALLOC +#define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) +#define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) +#define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N))) +#define PAR_FREE(BUF) free(BUF) +#endif + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- + +#ifdef PAR_FILECACHE_IMPLEMENTATION +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#if ENABLE_LZ4 +#include "lz4.h" +#endif + +static char * _par_strdup(char const* s) +{ + if (s) { + size_t l = strlen(s); + char *s2 = (char*) malloc(l + 1); + if (s2) { + strcpy(s2, s); + } + return s2; + } + return 0; +} + +#define PAR_MAX_ENTRIES 64 + +typedef struct { + time_t last_used_timestamp; + uint64_t hashed_name; + char const* name; + int nbytes; +} filecache_entry_t; + +typedef struct { + filecache_entry_t entries[PAR_MAX_ENTRIES]; + int nentries; + int totalbytes; +} filecache_table_t; + +static void _update_table(char const* item_name, int item_size); +static void _append_table(char const* item_name, int item_size); +static void _read_or_create_tablefile(); +static void _save_tablefile(); +static void _evict_lru(); +static uint64_t _hash(char const* name); + +static char _fileprefix[PATH_MAX] = "./_cache."; +static char _tablepath[PATH_MAX] = "./_cache.table"; +static int _maxtotalbytes = 1024 * 1024 * 16; +static filecache_table_t* _table = 0; + +void par_filecache_init(char const* prefix, int maxsize) +{ + size_t len = strlen(prefix); + assert(len + 1 < PATH_MAX && "Cache prefix is too long"); + strncpy(_fileprefix, prefix, len + 1); + strcpy(_tablepath, _fileprefix); + strcat(_tablepath, "table"); + _maxtotalbytes = maxsize; +} + +#if IOS_EXAMPLE +NSString* getPrefix() +{ + NSString* cachesFolder = [NSSearchPathForDirectoriesInDomains( + NSCachesDirectory, NSUserDomainMask, YES) firstObject]; + NSError* error = nil; + if (![[NSFileManager defaultManager] createDirectoryAtPath : cachesFolder + withIntermediateDirectories : YES + attributes : nil + error : &error]) { + NSLog(@"MGMPlatformGetCachesFolder error: %@", error); + return nil; + } + return [cachesFolder stringByAppendingString : @"/_cache."]; +} + +#endif + +static bool par_filecache__read(void* dest, int nbytes, FILE* file) +{ + int consumed = (int) fread(dest, nbytes, 1, file); + return consumed == 1; +} + +bool par_filecache_load(char const* name, uint8_t** payload, int* payloadsize, + uint8_t* header, int headersize) +{ + char qualified[PATH_MAX]; + size_t len = strlen(name); + if (len == 0) { + return false; + } + assert(len + strlen(_fileprefix) < PATH_MAX); + strcpy(qualified, _fileprefix); + strcat(qualified, name); + if (access(qualified, F_OK) == -1) { + return false; + } + FILE* cachefile = fopen(qualified, "rb"); + assert(cachefile && "Unable to open cache file for reading"); + fseek(cachefile, 0, SEEK_END); + long fsize = ftell(cachefile); + fseek(cachefile, 0, SEEK_SET); + if (headersize > 0 && !par_filecache__read(header, headersize, cachefile)) { + return false; + } + int32_t dnbytes; +#if ENABLE_LZ4 + long cnbytes = fsize - headersize - sizeof(dnbytes); + if (!par_filecache__read(&dnbytes, sizeof(dnbytes), cachefile)) { + return false; + } +#else + long cnbytes = fsize - headersize; + dnbytes = (int32_t) cnbytes; +#endif + char* cbuff = (char*) malloc(cnbytes); + if (!par_filecache__read(cbuff, (int) cnbytes, cachefile)) { + return false; + } +#if ENABLE_LZ4 + char* dbuff = (char*) malloc(dnbytes); + LZ4_decompress_safe(cbuff, dbuff, (int) cnbytes, dnbytes); + free(cbuff); +#else + char* dbuff = cbuff; +#endif + fclose(cachefile); + *payload = (uint8_t*) dbuff; + *payloadsize = dnbytes; + _update_table(name, (int) cnbytes); + return true; +} + +void par_filecache_save(char const* name, uint8_t const* payload, + int payloadsize, uint8_t const* header, int headersize) +{ + char qualified[PATH_MAX]; + size_t len = strlen(name); + if (len == 0) { + return; + } + assert(len + strlen(_fileprefix) < PATH_MAX); + strcpy(qualified, _fileprefix); + strcat(qualified, name); + FILE* cachefile = fopen(qualified, "wb"); + assert(cachefile && "Unable to open cache file for writing"); + if (headersize > 0) { + fwrite(header, 1, headersize, cachefile); + } + int csize = 0; + if (payloadsize > 0) { +#if ENABLE_LZ4 + int32_t nbytes = payloadsize; + fwrite(&nbytes, 1, sizeof(nbytes), cachefile); + int maxsize = LZ4_compressBound(nbytes); + char* dst = (char*) malloc(maxsize); + char const* src = (char const*) payload; + assert(nbytes < LZ4_MAX_INPUT_SIZE); + csize = LZ4_compress_default(src, dst, nbytes, maxsize); + fwrite(dst, 1, csize, cachefile); + free(dst); +#else + csize = payloadsize; + int actual = (int) fwrite(payload, 1, csize, cachefile); + if (actual < csize) { + fclose(cachefile); + remove(qualified); + printf("Unable to save %s to cache (%d bytes)\n", name, csize); + return; + } +#endif + } + fclose(cachefile); + _update_table(name, csize + headersize); +} + +void par_filecache_evict_all() +{ + #if PAR_FILECACHE_VERBOSE + printf("Evicting all.\n"); + #endif + char qualified[PATH_MAX]; + if (!_table) { + _read_or_create_tablefile(); + } + filecache_entry_t* entry = _table->entries; + for (int i = 0; i < _table->nentries; i++, entry++) { + strcpy(qualified, _fileprefix); + strcat(qualified, entry->name); + #if PAR_FILECACHE_VERBOSE + printf("Evicting %s\n", qualified); + #endif + remove(qualified); + } + _table->nentries = 0; + _table->totalbytes = 0; + remove(_tablepath); +} + +// Adds the given item to the table and evicts the LRU items if the total cache +// size exceeds the specified maxsize. +static void _append_table(char const* item_name, int item_size) +{ + time_t now = time(0); + if (!_table) { + _read_or_create_tablefile(); + } + uint64_t hashed_name = _hash(item_name); + int total = _table->totalbytes + item_size; + while (_table->nentries >= PAR_MAX_ENTRIES || total > _maxtotalbytes) { + assert(_table->nentries > 0 && "Cache size is too small."); + _evict_lru(); + total = _table->totalbytes + item_size; + } + _table->totalbytes = total; + filecache_entry_t* entry = &_table->entries[_table->nentries++]; + entry->last_used_timestamp = now; + entry->hashed_name = hashed_name; + entry->name = _par_strdup(item_name); + entry->nbytes = item_size; + _save_tablefile(); +} + +// Updates the timestamp associated with the given item. +static void _update_table(char const* item_name, int item_size) +{ + time_t now = time(0); + if (!_table) { + _read_or_create_tablefile(); + } + uint64_t hashed_name = _hash(item_name); + filecache_entry_t* entry = _table->entries; + int i; + for (i = 0; i < _table->nentries; i++, entry++) { + if (entry->hashed_name == hashed_name) { + break; + } + } + if (i >= _table->nentries) { + _append_table(item_name, item_size); + return; + } + entry->last_used_timestamp = now; + _save_tablefile(); +} + +static void _read_or_create_tablefile() +{ + _table = (filecache_table_t*) calloc(sizeof(filecache_table_t), 1); + FILE* fhandle = fopen(_tablepath, "r"); + if (!fhandle) { + fhandle = fopen(_tablepath, "w"); + if (!fhandle) { + mkdir(dirname(_tablepath), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + fhandle = fopen(_tablepath, "w"); + } + assert(fhandle && "Unable to create filecache info file."); + } else { + filecache_entry_t entry; + char name[PATH_MAX]; + while (1) { + int nargs = fscanf(fhandle, "%ld %d %s", &entry.last_used_timestamp, + &entry.nbytes, name); + if (nargs != 3) { + break; + } + entry.name = _par_strdup(name); + entry.hashed_name = _hash(entry.name); + _table->entries[_table->nentries++] = entry; + _table->totalbytes += entry.nbytes; + } + } + fclose(fhandle); +} + +static void _save_tablefile() +{ + FILE* fhandle = fopen(_tablepath, "w"); + assert(fhandle && "Unable to create filecache info file."); + filecache_entry_t* entry = _table->entries; + for (int i = 0; i < _table->nentries; i++, entry++) { + fprintf(fhandle, "%ld %d %s\n", entry->last_used_timestamp, + entry->nbytes, entry->name); + } + fclose(fhandle); +} + +static void _evict_lru() +{ + const uint64_t never_evict = _hash("version"); + int oldest_index = -1; + time_t oldest_time = LONG_MAX; + filecache_entry_t* entry = _table->entries; + for (int i = 0; i < _table->nentries; i++, entry++) { + if (entry->hashed_name == never_evict) { + continue; + } + if (entry->last_used_timestamp < oldest_time) { + oldest_time = entry->last_used_timestamp; + oldest_index = i; + } + } + if (oldest_index > -1) { + entry = _table->entries + oldest_index; + char qualified[PATH_MAX]; + size_t len = strlen(entry->name); + assert(len + strlen(_fileprefix) < PATH_MAX); + strcpy(qualified, _fileprefix); + strcat(qualified, entry->name); + #if PAR_FILECACHE_VERBOSE + printf("Evicting %s\n", entry->name); + #endif + remove(qualified); + _table->totalbytes -= entry->nbytes; + if (_table->nentries-- > 1) { + *entry = _table->entries[_table->nentries]; + } + } +} + +// https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function + +static uint64_t _hash(char const* name) +{ + const uint64_t OFFSET = 14695981039346656037ull; + const uint64_t PRIME = 1099511628211ull; + const unsigned char* str = (const unsigned char*) name; + uint64_t hval = OFFSET; + while (*str) { + hval *= PRIME; + hval ^= (uint64_t) *(str++); + } + return hval; +} + +#undef PAR_MAX_ENTRIES +#endif // PAR_FILECACHE_IMPLEMENTATION +#endif // PAR_FILECACHE_H + +// par_filecache is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_msquares.h b/source/engine/thirdparty/par/par_msquares.h new file mode 100644 index 0000000..63de86f --- /dev/null +++ b/source/engine/thirdparty/par/par_msquares.h @@ -0,0 +1,2156 @@ +// MSQUARES :: https://github.com/prideout/par +// Converts fp32 grayscale images, or 8-bit color images, into triangles. +// +// THIS IS EXPERIMENTAL CODE, DO NOT USE IN PRODUCTION +// +// Note that a potentially more interesting project for converting bitmaps +// into vectors can be found at https://github.com/BlockoS/blob, which is an +// implementation of "A linear-time component-labeling algorithm using contour +// tracing technique" by Fu Chang, Chun-Jen Chen, and Chi-Jen Lu. I recommend +// using that in combination with a simple ear-clipping algorithm for triangle +// tessellation. (see https://prideout.net/polygon.js) +// +// For grayscale images, a threshold is specified to determine insideness. +// For color images, an exact color is specified to determine insideness. +// Color images can be r8, rg16, rgb24, or rgba32. For a visual overview of +// the API and all the flags, see: +// +// https://prideout.net/marching-squares +// +// Distributed under the MIT License, see bottom of file. + +#ifndef PAR_MSQUARES_H +#define PAR_MSQUARES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// ----------------------------------------------------------------------------- +// BEGIN PUBLIC API +// ----------------------------------------------------------------------------- +#ifndef PAR_MSQUARES_T +#define PAR_MSQUARES_T uint16_t +#endif + +typedef uint8_t par_byte; + +typedef struct par_msquares_meshlist_s par_msquares_meshlist; + +// Results of a marching squares operation. Triangles are counter-clockwise. +typedef struct { + float* points; // pointer to XY (or XYZ) vertex coordinates + int npoints; // number of vertex coordinates + PAR_MSQUARES_T* triangles; // pointer to 3-tuples of vertex indices + int ntriangles; // number of 3-tuples + int dim; // number of floats per point (either 2 or 3) + uint32_t color; // used only with par_msquares_color_multi +} par_msquares_mesh; + +// Polyline boundary extracted from a mesh, composed of one or more chains. +// Counterclockwise chains are solid, clockwise chains are holes. So, when +// serializing to SVG, all chains can be aggregated in a single , +// provided they each terminate with a "Z" and use the default fill rule. +typedef struct { + float* points; // list of XY vertex coordinates + int npoints; // number of vertex coordinates + float** chains; // list of pointers to the start of each chain + PAR_MSQUARES_T* lengths; // list of chain lengths + int nchains; // number of chains +} par_msquares_boundary; + +// Reverses the "insideness" test. +#define PAR_MSQUARES_INVERT (1 << 0) + +// Returns a meshlist with two meshes: one for the inside, one for the outside. +#define PAR_MSQUARES_DUAL (1 << 1) + +// Requests that returned meshes have 3-tuple coordinates instead of 2-tuples. +// When using a color-based function, the Z coordinate represents the alpha +// value of the nearest pixel. +#define PAR_MSQUARES_HEIGHTS (1 << 2) + +// Applies a step function to the Z coordinates. Requires HEIGHTS and DUAL. +#define PAR_MSQUARES_SNAP (1 << 3) + +// Adds extrusion triangles to each mesh other than the lowest mesh. Requires +// the PAR_MSQUARES_HEIGHTS flag to be present. +#define PAR_MSQUARES_CONNECT (1 << 4) + +// Enables quick & dirty (not best) simpification of the returned mesh. +#define PAR_MSQUARES_SIMPLIFY (1 << 5) + +// Indicates that the "color" argument is ABGR instead of ARGB. +#define PAR_MSQUARES_SWIZZLE (1 << 6) + +// Ensures there are no T-junction vertices. (par_msquares_color_multi only) +// Requires the PAR_MSQUARES_SIMPLIFY flag to be disabled. +#define PAR_MSQUARES_CLEAN (1 << 7) + +par_msquares_meshlist* par_msquares_grayscale(float const* data, int width, + int height, int cellsize, float threshold, int flags); + +par_msquares_meshlist* par_msquares_color(par_byte const* data, int width, + int height, int cellsize, uint32_t color, int bpp, int flags); + +par_msquares_mesh const* par_msquares_get_mesh(par_msquares_meshlist*, int n); + +int par_msquares_get_count(par_msquares_meshlist*); + +void par_msquares_free(par_msquares_meshlist*); + +void par_msquares_free_boundary(par_msquares_boundary*); + +typedef int (*par_msquares_inside_fn)(int, void*); +typedef float (*par_msquares_height_fn)(float, float, void*); + +par_msquares_meshlist* par_msquares_function(int width, int height, + int cellsize, int flags, void* context, par_msquares_inside_fn insidefn, + par_msquares_height_fn heightfn); + +par_msquares_meshlist* par_msquares_grayscale_multi(float const* data, + int width, int height, int cellsize, float const* thresholds, + int nthresholds, int flags); + +par_msquares_meshlist* par_msquares_color_multi(par_byte const* data, int width, + int height, int cellsize, int bpp, int flags); + +par_msquares_boundary* par_msquares_extract_boundary(par_msquares_mesh const* ); + +#ifndef PAR_PI +#define PAR_PI (3.14159265359) +#define PAR_MIN(a, b) (a > b ? b : a) +#define PAR_MAX(a, b) (a > b ? a : b) +#define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) +#define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#define PAR_SQR(a) ((a) * (a)) +#endif + +#ifndef PAR_MALLOC +#define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) +#define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) +#define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N))) +#define PAR_FREE(BUF) free(BUF) +#endif + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- + +#ifdef PAR_MSQUARES_IMPLEMENTATION +#include +#include +#include +#include + +typedef struct { + PAR_MSQUARES_T* values; + size_t count; + size_t capacity; +} par__uint16list; + +typedef struct { + float* points; + int npoints; + PAR_MSQUARES_T* triangles; + int ntriangles; + int dim; + uint32_t color; + int nconntriangles; + PAR_MSQUARES_T* conntri; + par__uint16list* tjunctions; +} par_msquares__mesh; + +struct par_msquares_meshlist_s { + int nmeshes; + par_msquares__mesh** meshes; +}; + +static int** par_msquares_binary_point_table = 0; +static int** par_msquares_binary_triangle_table = 0; +static int* par_msquares_quaternary_triangle_table[64][4]; +static int* par_msquares_quaternary_boundary_table[64][4]; + +static par_msquares_meshlist* par_msquares__merge(par_msquares_meshlist** lists, + int count, int snap); + +static void par_init_tables() +{ + char const* BINARY_TABLE = + "0" + "1017" + "1123" + "2023370" + "1756" + "2015560" + "2123756" + "3023035056" + "1345" + "4013034045057" + "2124451" + "3024045057" + "2734467" + "3013034046" + "3124146167" + "2024460"; + char const* binary_token = BINARY_TABLE; + + par_msquares_binary_point_table = PAR_CALLOC(int*, 16); + par_msquares_binary_triangle_table = PAR_CALLOC(int*, 16); + for (int i = 0; i < 16; i++) { + int ntris = *binary_token - '0'; + binary_token++; + par_msquares_binary_triangle_table[i] = + PAR_CALLOC(int, (ntris + 1) * 3); + int* sqrtris = par_msquares_binary_triangle_table[i]; + sqrtris[0] = ntris; + int mask = 0; + int* sqrpts = par_msquares_binary_point_table[i] = PAR_CALLOC(int, 7); + sqrpts[0] = 0; + for (int j = 0; j < ntris * 3; j++, binary_token++) { + int midp = *binary_token - '0'; + int bit = 1 << midp; + if (!(mask & bit)) { + mask |= bit; + sqrpts[++sqrpts[0]] = midp; + } + sqrtris[j + 1] = midp; + } + } + + char const* QUATERNARY_TABLE = + "2024046000" + "3346360301112300" + "3346360301112300" + "3346360301112300" + "3560502523013450" + "2015056212414500" + "4018087785756212313828348450" + "4018087785756212313828348450" + "3560502523013450" + "4018087785756212313828348450" + "2015056212414500" + "4018087785756212313828348450" + "3560502523013450" + "4018087785756212313828348450" + "4018087785756212313828348450" + "2015056212414500" + "3702724745001756" + "2018087212313828348452785756" + "4013034045057112301756" + "4013034045057112301756" + "2023037027347460" + "1701312414616700" + "2018087212313847857568348450" + "2018087212313847857568348450" + "4018087123138028348452785756" + "1701467161262363513450" + "2018087412313883484502785756" + "2018087212313828348452785756" + "4018087123138028348452785756" + "1701467161262363513450" + "2018087212313828348452785756" + "2018087412313883484502785756" + "3702724745001756" + "4013034045057112301756" + "2018087212313828348452785756" + "4013034045057112301756" + "4018087123138028348452785756" + "2018087412313883484502785756" + "1701467161262363513450" + "2018087212313828348452785756" + "2023037027347460" + "2018087212313847857568348450" + "1701312414616700" + "2018087212313847857568348450" + "4018087123138028348452785756" + "2018087212313828348452785756" + "1701467161262363513450" + "2018087412313883484502785756" + "3702724745001756" + "4013034045057112301756" + "4013034045057112301756" + "2018087212313828348452785756" + "4018087123138028348452785756" + "2018087412313883484502785756" + "2018087212313828348452785756" + "1701467161262363513450" + "4018087123138028348452785756" + "2018087212313828348452785756" + "2018087412313883484502785756" + "1701467161262363513450" + "2023037027347460" + "2018087212313847857568348450" + "2018087212313847857568348450" + "1701312414616700"; + char const* quaternary_token = QUATERNARY_TABLE; + + int* quaternary_values = PAR_CALLOC(int, strlen(QUATERNARY_TABLE)); + int* vals = quaternary_values; + for (int i = 0; i < 64; i++) { + int ntris = *quaternary_token++ - '0'; + *vals = ntris; + par_msquares_quaternary_triangle_table[i][0] = vals++; + for (int j = 0; j < ntris * 3; j++) { + int pt = *quaternary_token++ - '0'; + assert(pt >= 0 && pt < 9); + *vals++ = pt; + } + ntris = *quaternary_token++ - '0'; + *vals = ntris; + par_msquares_quaternary_triangle_table[i][1] = vals++; + for (int j = 0; j < ntris * 3; j++) { + int pt = *quaternary_token++ - '0'; + assert(pt >= 0 && pt < 9); + *vals++ = pt; + } + ntris = *quaternary_token++ - '0'; + *vals = ntris; + par_msquares_quaternary_triangle_table[i][2] = vals++; + for (int j = 0; j < ntris * 3; j++) { + int pt = *quaternary_token++ - '0'; + assert(pt >= 0 && pt < 9); + *vals++ = pt; + } + ntris = *quaternary_token++ - '0'; + *vals = ntris; + par_msquares_quaternary_triangle_table[i][3] = vals++; + for (int j = 0; j < ntris * 3; j++) { + int pt = *quaternary_token++ - '0'; + assert(pt >= 0 && pt < 9); + *vals++ = pt; + } + } + assert(vals == quaternary_values + strlen(QUATERNARY_TABLE)); + + char const* QUATERNARY_EDGES = + "0000" + "11313100113131001131310013501530" + "115151002188523881258830218852388125883013501530" + "218852388125883011515100218852388125883013501530" + "218852388125883021885238812588301151510015700175" + "2188723881258832788521357131017521357131017513701730" + "11717100218872388127883021887238812788302388702588327885" + "1172713515302188725881027885218872388125883278852388702588327885" + "11727135153021887238812588327885218872588102788515700175" + "213571310175218872388125883278852135713101752388702588327885" + "21887258810278851172713515302188723881258832788513701730" + "21887238812788301171710021887238812788302388702588327885" + "21887238812588327885117271351530218872588102788515700175" + "213571310175213571310175218872388125883278852388702588327885" + "2188725881027885218872388125883278851172713515302388702588327885" + "21887238812588327885218872588102788511727135153013701730" + "2188723881278830218872388127883011717100"; + quaternary_token = QUATERNARY_EDGES; + + quaternary_values = PAR_CALLOC(int, strlen(QUATERNARY_EDGES)); + vals = quaternary_values; + for (int i = 0; i < 64; i++) { + int nedges = *quaternary_token++ - '0'; + *vals = nedges; + par_msquares_quaternary_boundary_table[i][0] = vals++; + for (int j = 0; j < nedges * 2; j++) { + int pt = *quaternary_token++ - '0'; + assert(pt >= 0 && pt < 9); + *vals++ = pt; + } + nedges = *quaternary_token++ - '0'; + *vals = nedges; + par_msquares_quaternary_boundary_table[i][1] = vals++; + for (int j = 0; j < nedges * 2; j++) { + int pt = *quaternary_token++ - '0'; + assert(pt >= 0 && pt < 9); + *vals++ = pt; + } + nedges = *quaternary_token++ - '0'; + *vals = nedges; + par_msquares_quaternary_boundary_table[i][2] = vals++; + for (int j = 0; j < nedges * 2; j++) { + int pt = *quaternary_token++ - '0'; + assert(pt >= 0 && pt < 9); + *vals++ = pt; + } + nedges = *quaternary_token++ - '0'; + *vals = nedges; + par_msquares_quaternary_boundary_table[i][3] = vals++; + for (int j = 0; j < nedges * 2; j++) { + int pt = *quaternary_token++ - '0'; + assert(pt >= 0 && pt < 9); + *vals++ = pt; + } + } + assert(vals == quaternary_values + strlen(QUATERNARY_EDGES)); +} + +typedef struct { + float const* data; + float threshold; + float lower_bound; + float upper_bound; + int width; + int height; +} par_gray_context; + +static int gray_inside(int location, void* contextptr) +{ + par_gray_context* context = (par_gray_context*) contextptr; + return context->data[location] > context->threshold; +} + +static int gray_multi_inside(int location, void* contextptr) +{ + par_gray_context* context = (par_gray_context*) contextptr; + float val = context->data[location]; + float upper = context->upper_bound; + float lower = context->lower_bound; + return val >= lower && val < upper; +} + +static float gray_height(float x, float y, void* contextptr) +{ + par_gray_context* context = (par_gray_context*) contextptr; + int i = PAR_CLAMP(context->width * x, 0, context->width - 1); + int j = PAR_CLAMP(context->height * y, 0, context->height - 1); + return context->data[i + j * context->width]; +} + +typedef struct { + par_byte const* data; + par_byte color[4]; + int bpp; + int width; + int height; +} par_color_context; + +static int color_inside(int location, void* contextptr) +{ + par_color_context* context = (par_color_context*) contextptr; + par_byte const* data = context->data + location * context->bpp; + for (int i = 0; i < context->bpp; i++) { + if (data[i] != context->color[i]) { + return 0; + } + } + return 1; +} + +static float color_height(float x, float y, void* contextptr) +{ + par_color_context* context = (par_color_context*) contextptr; + assert(context->bpp == 4); + int i = PAR_CLAMP(context->width * x, 0, context->width - 1); + int j = PAR_CLAMP(context->height * y, 0, context->height - 1); + int k = i + j * context->width; + return context->data[k * 4 + 3] / 255.0; +} + +par_msquares_meshlist* par_msquares_color(par_byte const* data, int width, + int height, int cellsize, uint32_t color, int bpp, int flags) +{ + par_color_context context; + context.bpp = bpp; + if (flags & PAR_MSQUARES_SWIZZLE) { + context.color[0] = (color >> 0) & 0xff; + context.color[1] = (color >> 8) & 0xff; + context.color[2] = (color >> 16) & 0xff; + context.color[3] = (color >> 24) & 0xff; + } else { + context.color[0] = (color >> 16) & 0xff; + context.color[1] = (color >> 8) & 0xff; + context.color[2] = (color >> 0) & 0xff; + context.color[3] = (color >> 24) & 0xff; + } + context.data = data; + context.width = width; + context.height = height; + return par_msquares_function( + width, height, cellsize, flags, &context, color_inside, color_height); +} + +par_msquares_meshlist* par_msquares_grayscale(float const* data, int width, + int height, int cellsize, float threshold, int flags) +{ + par_gray_context context; + context.width = width; + context.height = height; + context.data = data; + context.threshold = threshold; + return par_msquares_function( + width, height, cellsize, flags, &context, gray_inside, gray_height); +} + +par_msquares_meshlist* par_msquares_grayscale_multi(float const* data, + int width, int height, int cellsize, float const* thresholds, + int nthresholds, int flags) +{ + par_msquares_meshlist* mlists[2]; + mlists[0] = PAR_CALLOC(par_msquares_meshlist, 1); + int connect = flags & PAR_MSQUARES_CONNECT; + int snap = flags & PAR_MSQUARES_SNAP; + int heights = flags & PAR_MSQUARES_HEIGHTS; + if (!heights) { + snap = connect = 0; + } + flags &= ~PAR_MSQUARES_INVERT; + flags &= ~PAR_MSQUARES_DUAL; + flags &= ~PAR_MSQUARES_CONNECT; + flags &= ~PAR_MSQUARES_SNAP; + par_gray_context context; + context.width = width; + context.height = height; + context.data = data; + context.lower_bound = -FLT_MAX; + for (int i = 0; i <= nthresholds; i++) { + int mergeconf = i > 0 ? connect : 0; + if (i == nthresholds) { + context.upper_bound = FLT_MAX; + mergeconf |= snap; + } else { + context.upper_bound = thresholds[i]; + } + mlists[1] = par_msquares_function(width, height, cellsize, flags, + &context, gray_multi_inside, gray_height); + mlists[0] = par_msquares__merge(mlists, 2, mergeconf); + context.lower_bound = context.upper_bound; + flags |= connect; + } + return mlists[0]; +} + +par_msquares_mesh const* par_msquares_get_mesh( + par_msquares_meshlist* mlist, int mindex) +{ + assert(mlist && mindex < mlist->nmeshes); + return (par_msquares_mesh const*) mlist->meshes[mindex]; +} + +int par_msquares_get_count(par_msquares_meshlist* mlist) +{ + assert(mlist); + return mlist->nmeshes; +} + +void par_msquares_free(par_msquares_meshlist* mlist) +{ + if (!mlist) { + return; + } + par_msquares__mesh** meshes = mlist->meshes; + for (int i = 0; i < mlist->nmeshes; i++) { + free(meshes[i]->points); + free(meshes[i]->triangles); + free(meshes[i]); + } + free(meshes); + free(mlist); +} + +// Combine multiple meshlists by moving mesh pointers, and optionally applying +// a snap operation that assigns a single Z value across all verts in each +// mesh. The Z value determined by the mesh's position in the final mesh list. +static par_msquares_meshlist* par_msquares__merge(par_msquares_meshlist** lists, + int count, int snap) +{ + par_msquares_meshlist* merged = PAR_CALLOC(par_msquares_meshlist, 1); + merged->nmeshes = 0; + for (int i = 0; i < count; i++) { + merged->nmeshes += lists[i]->nmeshes; + } + merged->meshes = PAR_CALLOC(par_msquares__mesh*, merged->nmeshes); + par_msquares__mesh** pmesh = merged->meshes; + for (int i = 0; i < count; i++) { + par_msquares_meshlist* meshlist = lists[i]; + for (int j = 0; j < meshlist->nmeshes; j++) { + *pmesh++ = meshlist->meshes[j]; + } + free(meshlist); + } + if (!snap) { + return merged; + } + pmesh = merged->meshes; + float zmin = FLT_MAX; + float zmax = -zmin; + for (int i = 0; i < merged->nmeshes; i++, pmesh++) { + float* pzed = (*pmesh)->points + 2; + for (int j = 0; j < (*pmesh)->npoints; j++, pzed += 3) { + zmin = PAR_MIN(*pzed, zmin); + zmax = PAR_MAX(*pzed, zmax); + } + } + float zextent = zmax - zmin; + pmesh = merged->meshes; + for (int i = 0; i < merged->nmeshes; i++, pmesh++) { + float* pzed = (*pmesh)->points + 2; + float zed = zmin + zextent * i / (merged->nmeshes - 1); + for (int j = 0; j < (*pmesh)->npoints; j++, pzed += 3) { + *pzed = zed; + } + } + if (!(snap & PAR_MSQUARES_CONNECT)) { + return merged; + } + for (int i = 1; i < merged->nmeshes; i++) { + par_msquares__mesh* mesh = merged->meshes[i]; + + // Find all extrusion points. This is tightly coupled to the + // tessellation code, which generates two "connector" triangles for each + // extruded edge. The first two verts of the second triangle are the + // verts that need to be displaced. + char* markers = PAR_CALLOC(char, mesh->npoints); + int tri = mesh->ntriangles - mesh->nconntriangles; + while (tri < mesh->ntriangles) { + markers[mesh->triangles[tri * 3 + 3]] = 1; + markers[mesh->triangles[tri * 3 + 4]] = 1; + tri += 2; + } + + // Displace all extrusion points down to the previous level. + float zed = zmin + zextent * (i - 1) / (merged->nmeshes - 1); + float* pzed = mesh->points + 2; + for (int j = 0; j < mesh->npoints; j++, pzed += 3) { + if (markers[j]) { + *pzed = zed; + } + } + free(markers); + } + return merged; +} + +static void par_remove_unreferenced_verts(par_msquares__mesh* mesh) +{ + if (mesh->npoints == 0) { + return; + } + char* markers = PAR_CALLOC(char, mesh->npoints); + PAR_MSQUARES_T const* ptris = mesh->triangles; + int newnpts = 0; + for (int i = 0; i < mesh->ntriangles * 3; i++, ptris++) { + if (!markers[*ptris]) { + newnpts++; + markers[*ptris] = 1; + } + } + float* newpts = PAR_CALLOC(float, newnpts * mesh->dim); + PAR_MSQUARES_T* mapping = PAR_CALLOC(PAR_MSQUARES_T, mesh->npoints); + float const* ppts = mesh->points; + float* pnewpts = newpts; + int j = 0; + if (mesh->dim == 3) { + for (int i = 0; i < mesh->npoints; i++, ppts += 3) { + if (markers[i]) { + *pnewpts++ = ppts[0]; + *pnewpts++ = ppts[1]; + *pnewpts++ = ppts[2]; + mapping[i] = j++; + } + } + } else { + for (int i = 0; i < mesh->npoints; i++, ppts += 2) { + if (markers[i]) { + *pnewpts++ = ppts[0]; + *pnewpts++ = ppts[1]; + mapping[i] = j++; + } + } + } + free(mesh->points); + free(markers); + mesh->points = newpts; + mesh->npoints = newnpts; + for (int i = 0; i < mesh->ntriangles * 3; i++) { + mesh->triangles[i] = mapping[mesh->triangles[i]]; + } + free(mapping); +} + +par_msquares_meshlist* par_msquares_function(int width, int height, + int cellsize, int flags, void* context, par_msquares_inside_fn insidefn, + par_msquares_height_fn heightfn) +{ + assert(width > 0 && width % cellsize == 0); + assert(height > 0 && height % cellsize == 0); + + if (flags & PAR_MSQUARES_DUAL) { + int connect = flags & PAR_MSQUARES_CONNECT; + int snap = flags & PAR_MSQUARES_SNAP; + int heights = flags & PAR_MSQUARES_HEIGHTS; + if (!heights) { + snap = connect = 0; + } + flags ^= PAR_MSQUARES_INVERT; + flags &= ~PAR_MSQUARES_DUAL; + flags &= ~PAR_MSQUARES_CONNECT; + par_msquares_meshlist* m[2]; + m[0] = par_msquares_function(width, height, cellsize, flags, + context, insidefn, heightfn); + flags ^= PAR_MSQUARES_INVERT; + if (connect) { + flags |= PAR_MSQUARES_CONNECT; + } + m[1] = par_msquares_function(width, height, cellsize, flags, + context, insidefn, heightfn); + return par_msquares__merge(m, 2, snap | connect); + } + + int invert = flags & PAR_MSQUARES_INVERT; + + // Create the two code tables if we haven't already. These are tables of + // fixed constants, so it's embarassing that we use dynamic memory + // allocation for them. However it's easy and it's one-time-only. + if (!par_msquares_binary_point_table) { + par_init_tables(); + } + + // Allocate the meshlist and the first mesh. + par_msquares_meshlist* mlist = PAR_CALLOC(par_msquares_meshlist, 1); + mlist->nmeshes = 1; + mlist->meshes = PAR_CALLOC(par_msquares__mesh*, 1); + mlist->meshes[0] = PAR_CALLOC(par_msquares__mesh, 1); + par_msquares__mesh* mesh = mlist->meshes[0]; + mesh->dim = (flags & PAR_MSQUARES_HEIGHTS) ? 3 : 2; + int ncols = width / cellsize; + int nrows = height / cellsize; + + // Worst case is four triangles and six verts per cell, so allocate that + // much. + int maxtris = ncols * nrows * 4; + int maxpts = ncols * nrows * 6; + int maxedges = ncols * nrows * 2; + + // However, if we include extrusion triangles for boundary edges, + // we need space for another 4 triangles and 4 points per cell. + PAR_MSQUARES_T* conntris = 0; + int nconntris = 0; + PAR_MSQUARES_T* edgemap = 0; + if (flags & PAR_MSQUARES_CONNECT) { + conntris = PAR_CALLOC(PAR_MSQUARES_T, maxedges * 6); + maxtris += maxedges * 2; + maxpts += maxedges * 2; + edgemap = PAR_CALLOC(PAR_MSQUARES_T, maxpts); + for (int i = 0; i < maxpts; i++) { + edgemap[i] = 0xffff; + } + } + PAR_MSQUARES_T* tris = PAR_CALLOC(PAR_MSQUARES_T, maxtris * 3); + int ntris = 0; + float* pts = PAR_CALLOC(float, maxpts * mesh->dim); + int npts = 0; + + // The "verts" x/y/z arrays are the 4 corners and 4 midpoints around the + // square, in counter-clockwise order. The origin of "triangle space" is at + // the lower-left, although we expect the image data to be in raster order + // (starts at top-left). + float vertsx[8], vertsy[8]; + float normalization = 1.0f / PAR_MAX(width, height); + float normalized_cellsize = cellsize * normalization; + int maxrow = (height - 1) * width; + PAR_MSQUARES_T* ptris = tris; + PAR_MSQUARES_T* pconntris = conntris; + float* ppts = pts; + uint8_t* prevrowmasks = PAR_CALLOC(uint8_t, ncols); + int* prevrowinds = PAR_CALLOC(int, ncols * 3); + + // If simplification is enabled, we need to track all 'F' cells and their + // respective triangle indices. + uint8_t* simplification_codes = 0; + PAR_MSQUARES_T* simplification_tris = 0; + uint8_t* simplification_ntris = 0; + if (flags & PAR_MSQUARES_SIMPLIFY) { + simplification_codes = PAR_CALLOC(uint8_t, nrows * ncols); + simplification_tris = PAR_CALLOC(PAR_MSQUARES_T, nrows * ncols); + simplification_ntris = PAR_CALLOC(uint8_t, nrows * ncols); + } + + // Do the march! + for (int row = 0; row < nrows; row++) { + vertsx[0] = vertsx[6] = vertsx[7] = 0; + vertsx[1] = vertsx[5] = 0.5 * normalized_cellsize; + vertsx[2] = vertsx[3] = vertsx[4] = normalized_cellsize; + vertsy[0] = vertsy[1] = vertsy[2] = normalized_cellsize * (row + 1); + vertsy[4] = vertsy[5] = vertsy[6] = normalized_cellsize * row; + vertsy[3] = vertsy[7] = normalized_cellsize * (row + 0.5); + + int northi = row * cellsize * width; + int southi = PAR_MIN(northi + cellsize * width, maxrow); + int northwest = invert ^ insidefn(northi, context); + int southwest = invert ^ insidefn(southi, context); + int previnds[8] = {0}; + uint8_t prevmask = 0; + + for (int col = 0; col < ncols; col++) { + northi += cellsize; + southi += cellsize; + if (col == ncols - 1) { + northi--; + southi--; + } + + int northeast = invert ^ insidefn(northi, context); + int southeast = invert ^ insidefn(southi, context); + int code = southwest | (southeast << 1) | (northwest << 2) | + (northeast << 3); + + int const* pointspec = par_msquares_binary_point_table[code]; + int ptspeclength = *pointspec++; + int currinds[8] = {0}; + uint8_t mask = 0; + uint8_t prevrowmask = prevrowmasks[col]; + while (ptspeclength--) { + int midp = *pointspec++; + int bit = 1 << midp; + mask |= bit; + + // The following six conditionals perform welding to reduce the + // number of vertices. The first three perform welding with the + // cell to the west; the latter three perform welding with the + // cell to the north. + if (bit == 1 && (prevmask & 4)) { + currinds[midp] = previnds[2]; + continue; + } + if (bit == 128 && (prevmask & 8)) { + currinds[midp] = previnds[3]; + continue; + } + if (bit == 64 && (prevmask & 16)) { + currinds[midp] = previnds[4]; + continue; + } + if (bit == 16 && (prevrowmask & 4)) { + currinds[midp] = prevrowinds[col * 3 + 2]; + continue; + } + if (bit == 32 && (prevrowmask & 2)) { + currinds[midp] = prevrowinds[col * 3 + 1]; + continue; + } + if (bit == 64 && (prevrowmask & 1)) { + currinds[midp] = prevrowinds[col * 3 + 0]; + continue; + } + + ppts[0] = vertsx[midp]; + ppts[1] = vertsy[midp]; + + // Adjust the midpoints to a more exact crossing point. + if (midp == 1) { + int begin = southi - cellsize / 2; + int previous = 0; + for (int i = 0; i < cellsize; i++) { + int offset = begin + i / 2 * ((i % 2) ? -1 : 1); + int inside = insidefn(offset, context); + if (i > 0 && inside != previous) { + ppts[0] = normalization * + (col * cellsize + offset - southi + cellsize); + break; + } + previous = inside; + } + } else if (midp == 5) { + int begin = northi - cellsize / 2; + int previous = 0; + for (int i = 0; i < cellsize; i++) { + int offset = begin + i / 2 * ((i % 2) ? -1 : 1); + int inside = insidefn(offset, context); + if (i > 0 && inside != previous) { + ppts[0] = normalization * + (col * cellsize + offset - northi + cellsize); + break; + } + previous = inside; + } + } else if (midp == 3) { + int begin = northi + width * cellsize / 2; + int previous = 0; + for (int i = 0; i < cellsize; i++) { + int offset = begin + + width * (i / 2 * ((i % 2) ? -1 : 1)); + int inside = insidefn(offset, context); + if (i > 0 && inside != previous) { + ppts[1] = normalization * + (row * cellsize + + (offset - northi) / (float) width); + break; + } + previous = inside; + } + } else if (midp == 7) { + int begin = northi + width * cellsize / 2 - cellsize; + int previous = 0; + for (int i = 0; i < cellsize; i++) { + int offset = begin + + width * (i / 2 * ((i % 2) ? -1 : 1)); + int inside = insidefn(offset, context); + if (i > 0 && inside != previous) { + ppts[1] = normalization * + (row * cellsize + + (offset - northi - cellsize) / (float) width); + break; + } + previous = inside; + } + } + + if (mesh->dim == 3) { + if (width > height) { + ppts[2] = heightfn(ppts[0], ppts[1] * width / height, + context); + } else { + ppts[2] = heightfn(ppts[0] * height / width, ppts[1], + context); + } + } + + ppts += mesh->dim; + currinds[midp] = npts++; + } + + int const* trianglespec = par_msquares_binary_triangle_table[code]; + int trispeclength = *trianglespec++; + + if (flags & PAR_MSQUARES_SIMPLIFY) { + simplification_codes[ncols * row + col] = code; + simplification_tris[ncols * row + col] = ntris; + simplification_ntris[ncols * row + col] = trispeclength; + } + + // Add triangles. + while (trispeclength--) { + int a = *trianglespec++; + int b = *trianglespec++; + int c = *trianglespec++; + *ptris++ = currinds[c]; + *ptris++ = currinds[b]; + *ptris++ = currinds[a]; + ntris++; + } + + // Create two extrusion triangles for each boundary edge. + if (flags & PAR_MSQUARES_CONNECT) { + trianglespec = par_msquares_binary_triangle_table[code]; + trispeclength = *trianglespec++; + while (trispeclength--) { + int a = *trianglespec++; + int b = *trianglespec++; + int c = *trianglespec++; + int i = currinds[a]; + int j = currinds[b]; + int k = currinds[c]; + int u = 0, v = 0, w = 0; + if ((a % 2) && (b % 2)) { + u = v = 1; + } else if ((a % 2) && (c % 2)) { + u = w = 1; + } else if ((b % 2) && (c % 2)) { + v = w = 1; + } else { + continue; + } + if (u && edgemap[i] == 0xffff) { + for (int d = 0; d < mesh->dim; d++) { + *ppts++ = pts[i * mesh->dim + d]; + } + edgemap[i] = npts++; + } + if (v && edgemap[j] == 0xffff) { + for (int d = 0; d < mesh->dim; d++) { + *ppts++ = pts[j * mesh->dim + d]; + } + edgemap[j] = npts++; + } + if (w && edgemap[k] == 0xffff) { + for (int d = 0; d < mesh->dim; d++) { + *ppts++ = pts[k * mesh->dim + d]; + } + edgemap[k] = npts++; + } + if ((a % 2) && (b % 2)) { + *pconntris++ = i; + *pconntris++ = j; + *pconntris++ = edgemap[j]; + *pconntris++ = edgemap[j]; + *pconntris++ = edgemap[i]; + *pconntris++ = i; + } else if ((a % 2) && (c % 2)) { + *pconntris++ = edgemap[k]; + *pconntris++ = k; + *pconntris++ = i; + *pconntris++ = edgemap[i]; + *pconntris++ = edgemap[k]; + *pconntris++ = i; + } else if ((b % 2) && (c % 2)) { + *pconntris++ = j; + *pconntris++ = k; + *pconntris++ = edgemap[k]; + *pconntris++ = edgemap[k]; + *pconntris++ = edgemap[j]; + *pconntris++ = j; + } + nconntris += 2; + } + } + + // Prepare for the next cell. + prevrowmasks[col] = mask; + prevrowinds[col * 3 + 0] = currinds[0]; + prevrowinds[col * 3 + 1] = currinds[1]; + prevrowinds[col * 3 + 2] = currinds[2]; + prevmask = mask; + northwest = northeast; + southwest = southeast; + for (int i = 0; i < 8; i++) { + previnds[i] = currinds[i]; + vertsx[i] += normalized_cellsize; + } + } + } + free(edgemap); + free(prevrowmasks); + free(prevrowinds); + + // Perform quick-n-dirty simplification by iterating two rows at a time. + // In no way does this create the simplest possible mesh, but at least it's + // fast and easy. + if (flags & PAR_MSQUARES_SIMPLIFY) { + int in_run = 0, start_run; + + // First figure out how many triangles we can eliminate. + int neliminated_triangles = 0; + for (int row = 0; row < nrows - 1; row += 2) { + for (int col = 0; col < ncols; col++) { + int a = simplification_codes[ncols * row + col] == 0xf; + int b = simplification_codes[ncols * row + col + ncols] == 0xf; + if (a && b) { + if (!in_run) { + in_run = 1; + start_run = col; + } + continue; + } + if (in_run) { + in_run = 0; + int run_width = col - start_run; + neliminated_triangles += run_width * 4 - 2; + } + } + if (in_run) { + in_run = 0; + int run_width = ncols - start_run; + neliminated_triangles += run_width * 4 - 2; + } + } + + // Build a new index array cell-by-cell. If any given cell is 'F' and + // its neighbor to the south is also 'F', then it's part of a run. + int nnewtris = ntris + nconntris - neliminated_triangles; + PAR_MSQUARES_T* newtris = PAR_CALLOC(PAR_MSQUARES_T, nnewtris * 3); + PAR_MSQUARES_T* pnewtris = newtris; + in_run = 0; + for (int row = 0; row < nrows - 1; row += 2) { + for (int col = 0; col < ncols; col++) { + int cell = ncols * row + col; + int south = cell + ncols; + int a = simplification_codes[cell] == 0xf; + int b = simplification_codes[south] == 0xf; + if (a && b) { + if (!in_run) { + in_run = 1; + start_run = col; + } + continue; + } + if (in_run) { + in_run = 0; + int nw_cell = ncols * row + start_run; + int ne_cell = ncols * row + col - 1; + int sw_cell = nw_cell + ncols; + int se_cell = ne_cell + ncols; + int nw_tri = simplification_tris[nw_cell]; + int ne_tri = simplification_tris[ne_cell]; + int sw_tri = simplification_tris[sw_cell]; + int se_tri = simplification_tris[se_cell]; + int nw_corner = nw_tri * 3 + 4; + int ne_corner = ne_tri * 3 + 0; + int sw_corner = sw_tri * 3 + 2; + int se_corner = se_tri * 3 + 1; + *pnewtris++ = tris[se_corner]; + *pnewtris++ = tris[sw_corner]; + *pnewtris++ = tris[nw_corner]; + *pnewtris++ = tris[nw_corner]; + *pnewtris++ = tris[ne_corner]; + *pnewtris++ = tris[se_corner]; + } + int ncelltris = simplification_ntris[cell]; + int celltri = simplification_tris[cell]; + for (int t = 0; t < ncelltris; t++, celltri++) { + *pnewtris++ = tris[celltri * 3]; + *pnewtris++ = tris[celltri * 3 + 1]; + *pnewtris++ = tris[celltri * 3 + 2]; + } + ncelltris = simplification_ntris[south]; + celltri = simplification_tris[south]; + for (int t = 0; t < ncelltris; t++, celltri++) { + *pnewtris++ = tris[celltri * 3]; + *pnewtris++ = tris[celltri * 3 + 1]; + *pnewtris++ = tris[celltri * 3 + 2]; + } + } + if (in_run) { + in_run = 0; + int nw_cell = ncols * row + start_run; + int ne_cell = ncols * row + ncols - 1; + int sw_cell = nw_cell + ncols; + int se_cell = ne_cell + ncols; + int nw_tri = simplification_tris[nw_cell]; + int ne_tri = simplification_tris[ne_cell]; + int sw_tri = simplification_tris[sw_cell]; + int se_tri = simplification_tris[se_cell]; + int nw_corner = nw_tri * 3 + 4; + int ne_corner = ne_tri * 3 + 0; + int sw_corner = sw_tri * 3 + 2; + int se_corner = se_tri * 3 + 1; + *pnewtris++ = tris[se_corner]; + *pnewtris++ = tris[sw_corner]; + *pnewtris++ = tris[nw_corner]; + *pnewtris++ = tris[nw_corner]; + *pnewtris++ = tris[ne_corner]; + *pnewtris++ = tris[se_corner]; + } + } + ptris = pnewtris; + ntris -= neliminated_triangles; + free(tris); + tris = newtris; + free(simplification_codes); + free(simplification_tris); + free(simplification_ntris); + + // Remove unreferenced points. + char* markers = PAR_CALLOC(char, npts); + ptris = tris; + int newnpts = 0; + for (int i = 0; i < ntris * 3; i++, ptris++) { + if (!markers[*ptris]) { + newnpts++; + markers[*ptris] = 1; + } + } + for (int i = 0; i < nconntris * 3; i++) { + if (!markers[conntris[i]]) { + newnpts++; + markers[conntris[i]] = 1; + } + } + float* newpts = PAR_CALLOC(float, newnpts * mesh->dim); + PAR_MSQUARES_T* mapping = PAR_CALLOC(PAR_MSQUARES_T, npts); + ppts = pts; + float* pnewpts = newpts; + int j = 0; + if (mesh->dim == 3) { + for (int i = 0; i < npts; i++, ppts += 3) { + if (markers[i]) { + *pnewpts++ = ppts[0]; + *pnewpts++ = ppts[1]; + *pnewpts++ = ppts[2]; + mapping[i] = j++; + } + } + } else { + for (int i = 0; i < npts; i++, ppts += 2) { + if (markers[i]) { + *pnewpts++ = ppts[0]; + *pnewpts++ = ppts[1]; + mapping[i] = j++; + } + } + } + free(pts); + free(markers); + pts = newpts; + npts = newnpts; + for (int i = 0; i < ntris * 3; i++) { + tris[i] = mapping[tris[i]]; + } + for (int i = 0; i < nconntris * 3; i++) { + conntris[i] = mapping[conntris[i]]; + } + free(mapping); + } + + // Append all extrusion triangles to the main triangle array. + // We need them to be last so that they form a contiguous sequence. + pconntris = conntris; + for (int i = 0; i < nconntris; i++) { + *ptris++ = *pconntris++; + *ptris++ = *pconntris++; + *ptris++ = *pconntris++; + ntris++; + } + free(conntris); + + // Final cleanup and return. + assert(npts <= maxpts); + assert(ntris <= maxtris); + mesh->npoints = npts; + mesh->points = pts; + mesh->ntriangles = ntris; + mesh->triangles = tris; + mesh->nconntriangles = nconntris; + return mlist; +} + +typedef struct { + PAR_MSQUARES_T outera; + PAR_MSQUARES_T outerb; + PAR_MSQUARES_T innera; + PAR_MSQUARES_T innerb; + char i; + char j; + par_msquares__mesh* mesh; + int mesh_index; +} par_connector; + +static par_connector* par_conn_find(par_connector* conns, int nconns, + char i, char j) +{ + for (int c = 0; c < nconns; c++) { + if (conns[c].i == i && conns[c].j == j) { + return conns + c; + } + } + return 0; +} + +static int par_msquares_cmp(const void *a, const void *b) +{ + uint32_t arg1 = *((uint32_t const*) a); + uint32_t arg2 = *((uint32_t const*) b); + if (arg1 < arg2) return -1; + if (arg1 > arg2) return 1; + return 0; +} + +typedef int (*par_msquares_code_fn)(int, int, int, int, void*); + +static int par_msquares_multi_code(int sw, int se, int ne, int nw) +{ + int code[4]; + int ncols = 0; + code[0] = ncols++; + if (se == sw) { + code[1] = code[0]; + } else { + code[1] = ncols++; + } + if (ne == se) { + code[2] = code[1]; + } else if (ne == sw) { + code[2] = code[0]; + } else { + code[2] = ncols++; + } + if (nw == ne) { + code[3] = code[2]; + } else if (nw == se) { + code[3] = code[1]; + } else if (nw == sw) { + code[3] = code[0]; + } else { + code[3] = ncols++; + } + return code[0] | (code[1] << 2) | (code[2] << 4) | (code[3] << 6); +} + +static uint32_t par_msquares_argb(par_byte const* pdata, int bpp) +{ + uint32_t color = 0; + if (bpp == 4) { + color |= pdata[2]; + color |= pdata[1] << 8; + color |= pdata[0] << 16; + color |= pdata[3] << 24; + return color; + } + for (int j = 0; j < bpp; j++) { + color <<= 8; + color |= pdata[j]; + } + return color; +} + +// Merge connective triangles into the primary triangle list. +static void par_msquares__finalize(par_msquares_meshlist* mlist) +{ + if (mlist->nmeshes < 2 || mlist->meshes[1]->nconntriangles == 0) { + return; + } + for (int m = 1; m < mlist->nmeshes; m++) { + par_msquares__mesh* mesh = mlist->meshes[m]; + int ntris = mesh->ntriangles + mesh->nconntriangles; + PAR_MSQUARES_T* triangles = PAR_CALLOC(PAR_MSQUARES_T, ntris * 3); + PAR_MSQUARES_T* dst = triangles; + PAR_MSQUARES_T const* src = mesh->triangles; + for (int t = 0; t < mesh->ntriangles; t++) { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + } + src = mesh->conntri; + for (int t = 0; t < mesh->nconntriangles; t++) { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + } + free(mesh->triangles); + free(mesh->conntri); + mesh->triangles = triangles; + mesh->ntriangles = ntris; + mesh->conntri = 0; + mesh->nconntriangles = 0; + } +} + +static par__uint16list* par__uint16list_create() +{ + par__uint16list* list = PAR_CALLOC(par__uint16list, 1); + list->count = 0; + list->capacity = 32; + list->values = PAR_CALLOC(PAR_MSQUARES_T, list->capacity); + return list; +} + +static void par__uint16list_add3(par__uint16list* list, + PAR_MSQUARES_T a, PAR_MSQUARES_T b, PAR_MSQUARES_T c) +{ + if (list->count + 3 > list->capacity) { + list->capacity *= 2; + list->values = PAR_REALLOC(PAR_MSQUARES_T, list->values, list->capacity); + } + list->values[list->count++] = a; + list->values[list->count++] = b; + list->values[list->count++] = c; +} + +static void par__uint16list_free(par__uint16list* list) +{ + if (list) { + PAR_FREE(list->values); + PAR_FREE(list); + } +} + +static void par_msquares__repair_tjunctions(par_msquares_meshlist* mlist) +{ + for (int m = 0; m < mlist->nmeshes; m++) { + par_msquares__mesh* mesh = mlist->meshes[m]; + par__uint16list* tjunctions = mesh->tjunctions; + int njunctions = (int) tjunctions->count / 3; + if (njunctions == 0) { + continue; + } + int ntriangles = mesh->ntriangles + njunctions; + mesh->triangles = PAR_REALLOC(PAR_MSQUARES_T, mesh->triangles, + ntriangles * 3); + PAR_MSQUARES_T const* jun = tjunctions->values; + PAR_MSQUARES_T* new_triangles = mesh->triangles + mesh->ntriangles * 3; + int ncreated = 0; + for (int j = 0; j < njunctions; j++, jun += 3) { + PAR_MSQUARES_T* tri = mesh->triangles; + int t; + for (t = 0; t < mesh->ntriangles; t++, tri += 3) { + int i = -1; + if (tri[0] == jun[0] && tri[1] == jun[1]) { + i = 0; + } else if (tri[1] == jun[0] && tri[2] == jun[1]) { + i = 1; + } else if (tri[2] == jun[0] && tri[0] == jun[1]) { + i = 2; + } else { + continue; + } + new_triangles[0] = tri[(i + 0) % 3]; + new_triangles[1] = jun[2]; + new_triangles[2] = tri[(i + 2) % 3]; + tri[(i + 0) % 3] = jun[2]; + new_triangles += 3; + ncreated++; + break; + } + // TODO: Need to investigate the "msquares_multi_diagram.obj" test. + assert(t != mesh->ntriangles && + "Error with T-Junction repair; please disable the CLEAN flag."); + } + mesh->ntriangles += ncreated; + } +} + +par_msquares_meshlist* par_msquares_color_multi(par_byte const* data, int width, + int height, int cellsize, int bpp, int flags) +{ + if (!par_msquares_binary_point_table) { + par_init_tables(); + } + const int ncols = width / cellsize; + const int nrows = height / cellsize; + const int maxrow = (height - 1) * width; + const int ncells = ncols * nrows; + const int dim = (flags & PAR_MSQUARES_HEIGHTS) ? 3 : 2; + const int west_to_east[9] = { 2, -1, -1, -1, -1, -1, 4, 3, -1 }; + const int north_to_south[9] = { -1, -1, -1, -1, 2, 1, 0, -1, -1 }; + assert(!(flags & PAR_MSQUARES_HEIGHTS) || bpp == 4); + assert(bpp > 0 && bpp <= 4 && "Bytes per pixel must be 1, 2, 3, or 4."); + assert(!(flags & PAR_MSQUARES_CLEAN) || !(flags & PAR_MSQUARES_SIMPLIFY)); + assert(!(flags & PAR_MSQUARES_SNAP) && + "SNAP is not supported with color_multi"); + assert(!(flags & PAR_MSQUARES_INVERT) && + "INVERT is not supported with color_multi"); + assert(!(flags & PAR_MSQUARES_DUAL) && + "DUAL is not supported with color_multi"); + + // Find all unique colors and ensure there are no more than 256 colors. + uint32_t colors[256]; + int ncolors = 0; + par_byte const* pdata = data; + for (int i = 0; i < width * height; i++, pdata += bpp) { + uint32_t color = par_msquares_argb(pdata, bpp); + if (0 == bsearch(&color, colors, ncolors, 4, par_msquares_cmp)) { + assert(ncolors < 256); + colors[ncolors++] = color; + qsort(colors, ncolors, sizeof(uint32_t), par_msquares_cmp); + } + } + + // Convert the color image to grayscale using the mapping table. + par_byte* pixels = PAR_CALLOC(par_byte, width * height); + pdata = data; + for (int i = 0; i < width * height; i++, pdata += bpp) { + uint32_t color = par_msquares_argb(pdata, bpp); + void* result = bsearch(&color, colors, ncolors, 4, par_msquares_cmp); + pixels[i] = (uint32_t*) result - &colors[0]; + } + + // Allocate 1 mesh for each color. + par_msquares_meshlist* mlist = PAR_CALLOC(par_msquares_meshlist, 1); + mlist->nmeshes = ncolors; + mlist->meshes = PAR_CALLOC(par_msquares__mesh*, ncolors); + par_msquares__mesh* mesh; + int maxtris_per_cell = 6; + int maxpts_per_cell = 9; + if (flags & PAR_MSQUARES_CONNECT) { + maxpts_per_cell += 6; + } + for (int i = 0; i < ncolors; i++) { + mesh = mlist->meshes[i] = PAR_CALLOC(par_msquares__mesh, 1); + mesh->color = colors[i]; + mesh->points = PAR_CALLOC(float, ncells * maxpts_per_cell * dim); + mesh->triangles = PAR_CALLOC(PAR_MSQUARES_T, ncells * maxtris_per_cell * 3); + mesh->dim = dim; + mesh->tjunctions = par__uint16list_create(); + if (flags & PAR_MSQUARES_CONNECT) { + mesh->conntri = PAR_CALLOC(PAR_MSQUARES_T, ncells * 8 * 3); + } + } + + // The "verts" x/y/z arrays are the 4 corners and 4 midpoints around the + // square, in counter-clockwise order, starting at the lower-left. The + // ninth vert is the center point. + + float vertsx[9], vertsy[9]; + float normalization = 1.0f / PAR_MAX(width, height); + float normalized_cellsize = cellsize * normalization; + uint8_t cella[256]; + uint8_t cellb[256]; + uint8_t* currcell = cella; + uint8_t* prevcell = cellb; + PAR_MSQUARES_T inds0[256 * 9]; + PAR_MSQUARES_T inds1[256 * 9]; + PAR_MSQUARES_T* currinds = inds0; + PAR_MSQUARES_T* previnds = inds1; + PAR_MSQUARES_T* rowindsa = PAR_CALLOC(PAR_MSQUARES_T, ncols * 3 * 256); + uint8_t* rowcellsa = PAR_CALLOC(uint8_t, ncols * 256); + PAR_MSQUARES_T* rowindsb = PAR_CALLOC(PAR_MSQUARES_T, ncols * 3 * 256); + uint8_t* rowcellsb = PAR_CALLOC(uint8_t, ncols * 256); + PAR_MSQUARES_T* prevrowinds = rowindsa; + PAR_MSQUARES_T* currrowinds = rowindsb; + uint8_t* prevrowcells = rowcellsa; + uint8_t* currrowcells = rowcellsb; + uint32_t* simplification_words = 0; + if (flags & PAR_MSQUARES_SIMPLIFY) { + simplification_words = PAR_CALLOC(uint32_t, 2 * nrows * ncols); + } + + // Do the march! + for (int row = 0; row < nrows; row++) { + vertsx[0] = vertsx[6] = vertsx[7] = 0; + vertsx[1] = vertsx[5] = vertsx[8] = 0.5 * normalized_cellsize; + vertsx[2] = vertsx[3] = vertsx[4] = normalized_cellsize; + vertsy[0] = vertsy[1] = vertsy[2] = normalized_cellsize * (row + 1); + vertsy[4] = vertsy[5] = vertsy[6] = normalized_cellsize * row; + vertsy[3] = vertsy[7] = vertsy[8] = normalized_cellsize * (row + 0.5); + int northi = row * cellsize * width; + int southi = PAR_MIN(northi + cellsize * width, maxrow); + int nwval = pixels[northi]; + int swval = pixels[southi]; + memset(currrowcells, 0, ncols * 256); + + for (int col = 0; col < ncols; col++) { + northi += cellsize; + southi += cellsize; + if (col == ncols - 1) { + northi--; + southi--; + } + + // Obtain 8-bit code and grab the four corresponding triangle lists. + int neval = pixels[northi]; + int seval = pixels[southi]; + int code = par_msquares_multi_code(swval, seval, neval, nwval) >> 2; + int const* trispecs[4] = { + par_msquares_quaternary_triangle_table[code][0], + par_msquares_quaternary_triangle_table[code][1], + par_msquares_quaternary_triangle_table[code][2], + par_msquares_quaternary_triangle_table[code][3] + }; + int ntris[4] = { + *trispecs[0]++, + *trispecs[1]++, + *trispecs[2]++, + *trispecs[3]++ + }; + int const* edgespecs[4] = { + par_msquares_quaternary_boundary_table[code][0], + par_msquares_quaternary_boundary_table[code][1], + par_msquares_quaternary_boundary_table[code][2], + par_msquares_quaternary_boundary_table[code][3] + }; + int nedges[4] = { + *edgespecs[0]++, + *edgespecs[1]++, + *edgespecs[2]++, + *edgespecs[3]++ + }; + int vals[4] = { swval, seval, neval, nwval }; + + // Gather topology information. + par_connector edges[16]; + int ncedges = 0; + for (int c = 0; c < 4; c++) { + int color = vals[c]; + par_msquares__mesh* mesh = mlist->meshes[color]; + par_connector edge; + for (int e = 0; e < nedges[c]; e++) { + char previndex = edgespecs[c][e * 2]; + char currindex = edgespecs[c][e * 2 + 1]; + edge.i = previndex; + edge.j = currindex; + edge.mesh_index = color; + edge.mesh = mesh; + edges[ncedges++] = edge; + } + } + assert(ncedges < 16); + + // Push triangles and points into the four affected meshes. + for (int m = 0; m < ncolors; m++) { + currcell[m] = 0; + } + uint32_t colors = 0; + uint32_t counts = 0; + PAR_MSQUARES_T* conntris_start[4]; + for (int c = 0; c < 4; c++) { + int color = vals[c]; + colors |= color << (8 * c); + counts |= ntris[c] << (8 * c); + par_msquares__mesh* mesh = mlist->meshes[color]; + float height = (mesh->color >> 24) / 255.0; + conntris_start[c] = mesh->conntri + mesh->nconntriangles * 3; + int usedpts[9] = {0}; + PAR_MSQUARES_T* pcurrinds = currinds + 9 * color; + PAR_MSQUARES_T const* pprevinds = previnds + 9 * color; + PAR_MSQUARES_T const* pprevrowinds = + prevrowinds + ncols * 3 * color + col * 3; + uint8_t prevrowcell = prevrowcells[color * ncols + col]; + float* pdst = mesh->points + mesh->npoints * mesh->dim; + int previndex, prevflag; + for (int t = 0; t < ntris[c] * 3; t++) { + PAR_MSQUARES_T index = trispecs[c][t]; + if (usedpts[index]) { + continue; + } + usedpts[index] = 1; + if (index < 8) { + currcell[color] |= 1 << index; + } + + // Vertical welding. + previndex = north_to_south[index]; + prevflag = (previndex > -1) ? (1 << previndex) : 0; + if (row > 0 && (prevrowcell & prevflag)) { + pcurrinds[index] = pprevrowinds[previndex]; + continue; + } + + // Horizontal welding. + previndex = west_to_east[index]; + prevflag = (previndex > -1) ? (1 << previndex) : 0; + if (col > 0 && (prevcell[color] & prevflag)) { + pcurrinds[index] = pprevinds[previndex]; + continue; + } + + // Insert brand new point. + float* vertex = pdst; + *pdst++ = vertsx[index]; + *pdst++ = 1 - vertsy[index]; + if (mesh->dim == 3) { + *pdst++ = height; + } + pcurrinds[index] = mesh->npoints++; + + // If this is a midpoint, nudge it to the intersection. + if (index == 1) { + int begin = southi - cellsize; + for (int i = 1; i < cellsize + 1; i++) { + int val = pixels[begin + i]; + if (val != pixels[begin]) { + vertex[0] = vertsx[0] + normalized_cellsize * + (float) i / cellsize; + break; + } + } + } else if (index == 3) { + int begin = northi; + for (int i = 1; i < cellsize + 1; i++) { + int val = pixels[begin + i * width]; + if (val != pixels[begin]) { + vertex[1] = (1 - vertsy[4]) - + normalized_cellsize * (float) i / cellsize; + break; + } + } + } + } + + // Look for T junctions and note them for later repairs. + uint8_t prc = prevrowcell; + if (usedpts[4] && !usedpts[5] && usedpts[6] && (prc & 2)) { + // Above cell had a middle vert, current cell straddles it. + par__uint16list_add3(mesh->tjunctions, + pcurrinds[4], pcurrinds[6], pprevrowinds[1]); + } else if ((prc & 1) && !(prc & 2) && (prc & 4) && usedpts[5]) { + // Current cell has a middle vert, above cell straddles it. + par__uint16list_add3(mesh->tjunctions, + pprevrowinds[0], pprevrowinds[2], pcurrinds[5]); + } + uint8_t pcc = col > 0 ? prevcell[color] : 0; + if (usedpts[0] && !usedpts[7] && usedpts[6] && (pcc & 8)) { + // Left cell had a middle vert, current cell straddles it. + par__uint16list_add3(mesh->tjunctions, + pcurrinds[6], pcurrinds[0], pprevinds[3]); + } + if ((pcc & 4) && !(pcc & 8) && (pcc & 16) && usedpts[7]) { + // Current cell has a middle vert, left cell straddles it. + par__uint16list_add3(mesh->tjunctions, + pprevinds[2], pprevinds[4], pcurrinds[7]); + } + + // Stamp out the cell's triangle indices for this color. + PAR_MSQUARES_T* tdst = mesh->triangles + mesh->ntriangles * 3; + mesh->ntriangles += ntris[c]; + for (int t = 0; t < ntris[c] * 3; t++) { + PAR_MSQUARES_T index = trispecs[c][t]; + *tdst++ = pcurrinds[index]; + } + + // Add extrusion points and connective triangles if requested. + if (!(flags & PAR_MSQUARES_CONNECT)) { + continue; + } + for (int e = 0; e < nedges[c]; e++) { + int previndex = edgespecs[c][e * 2]; + int currindex = edgespecs[c][e * 2 + 1]; + par_connector* thisedge = par_conn_find(edges, + ncedges, previndex, currindex); + thisedge->innera = pcurrinds[previndex]; + thisedge->innerb = pcurrinds[currindex]; + thisedge->outera = mesh->npoints; + thisedge->outerb = mesh->npoints + 1; + par_connector* oppedge = par_conn_find(edges, + ncedges, currindex, previndex); + if (oppedge->mesh_index > color) continue; + *pdst++ = vertsx[previndex]; + *pdst++ = 1 - vertsy[previndex]; + if (mesh->dim == 3) { + *pdst++ = height; + } + mesh->npoints++; + *pdst++ = vertsx[currindex]; + *pdst++ = 1 - vertsy[currindex]; + if (mesh->dim == 3) { + *pdst++ = height; + } + mesh->npoints++; + PAR_MSQUARES_T i0 = mesh->npoints - 1; + PAR_MSQUARES_T i1 = mesh->npoints - 2; + PAR_MSQUARES_T i2 = pcurrinds[previndex]; + PAR_MSQUARES_T i3 = pcurrinds[currindex]; + PAR_MSQUARES_T* ptr = mesh->conntri + + mesh->nconntriangles * 3; + *ptr++ = i2; *ptr++ = i1; *ptr++ = i0; + *ptr++ = i0; *ptr++ = i3; *ptr++ = i2; + mesh->nconntriangles += 2; + } + } + + // Adjust the positions of the extrusion verts. + if (flags & PAR_MSQUARES_CONNECT) { + for (int c = 0; c < 4; c++) { + int color = vals[c]; + PAR_MSQUARES_T* pconninds = conntris_start[c]; + par_msquares__mesh* mesh = mlist->meshes[color]; + for (int e = 0; e < nedges[c]; e++) { + int previndex = edgespecs[c][e * 2]; + int currindex = edgespecs[c][e * 2 + 1]; + PAR_MSQUARES_T i1 = pconninds[1]; + PAR_MSQUARES_T i0 = pconninds[2]; + par_connector const* oppedge = par_conn_find(edges, + ncedges, currindex, previndex); + if (oppedge->mesh_index > color) continue; + int d = mesh->dim; + float* dst = mesh->points; + float const* src = oppedge->mesh->points; + dst[i0 * d + 0] = src[oppedge->innera * d + 0]; + dst[i0 * d + 1] = src[oppedge->innera * d + 1]; + dst[i1 * d + 0] = src[oppedge->innerb * d + 0]; + dst[i1 * d + 1] = src[oppedge->innerb * d + 1]; + if (d == 3) { + dst[i0 * d + 2] = src[oppedge->innera * d + 2]; + dst[i1 * d + 2] = src[oppedge->innerb * d + 2]; + } + pconninds += 6; + } + } + } + + // Stash the bottom indices for each mesh in this cell to enable + // vertical as-you-go welding. + uint8_t* pcurrrowcells = currrowcells; + PAR_MSQUARES_T* pcurrrowinds = currrowinds; + PAR_MSQUARES_T const* pcurrinds = currinds; + for (int color = 0; color < ncolors; color++) { + pcurrrowcells[col] = currcell[color]; + pcurrrowcells += ncols; + pcurrrowinds[col * 3 + 0] = pcurrinds[0]; + pcurrrowinds[col * 3 + 1] = pcurrinds[1]; + pcurrrowinds[col * 3 + 2] = pcurrinds[2]; + pcurrrowinds += ncols * 3; + pcurrinds += 9; + } + + // Stash some information later used by simplification. + if (flags & PAR_MSQUARES_SIMPLIFY) { + int cell = col + row * ncols; + simplification_words[cell * 2] = colors; + simplification_words[cell * 2 + 1] = counts; + } + + // Advance the cursor. + nwval = neval; + swval = seval; + for (int i = 0; i < 9; i++) { + vertsx[i] += normalized_cellsize; + } + PAR_SWAP(uint8_t*, prevcell, currcell); + PAR_SWAP(PAR_MSQUARES_T*, previnds, currinds); + } + PAR_SWAP(uint8_t*, prevrowcells, currrowcells); + PAR_SWAP(PAR_MSQUARES_T*, prevrowinds, currrowinds); + } + free(prevrowinds); + free(prevrowcells); + free(pixels); + + if (flags & PAR_MSQUARES_CLEAN) { + par_msquares__repair_tjunctions(mlist); + } + for (int m = 0; m < mlist->nmeshes; m++) { + par_msquares__mesh* mesh = mlist->meshes[m]; + par__uint16list_free(mesh->tjunctions); + } + if (!(flags & PAR_MSQUARES_SIMPLIFY)) { + par_msquares__finalize(mlist); + return mlist; + } + + uint8_t* simplification_blocks = PAR_CALLOC(uint8_t, nrows * ncols); + uint32_t* simplification_tris = PAR_CALLOC(uint32_t, nrows * ncols); + uint8_t* simplification_ntris = PAR_CALLOC(uint8_t, nrows * ncols); + + // Perform quick-n-dirty simplification by iterating two rows at a time. + // In no way does this create the simplest possible mesh, but at least it's + // fast and easy. + for (uint32_t color = 0; color < (uint32_t) ncolors; color++) { + par_msquares__mesh* mesh = mlist->meshes[color]; + + // Populate the per-mesh info grids. + int ntris = 0; + for (int row = 0; row < nrows; row++) { + for (int col = 0; col < ncols; col++) { + int cell = ncols * row + col; + uint32_t colors = simplification_words[cell * 2]; + uint32_t counts = simplification_words[cell * 2 + 1]; + int ncelltris = 0; + int ncorners = 0; + if ((colors & 0xff) == color) { + ncelltris = counts & 0xff; + ncorners++; + } + if (((colors >> 8) & 0xff) == color) { + ncelltris += (counts >> 8) & 0xff; + ncorners++; + } + if (((colors >> 16) & 0xff) == color) { + ncelltris += (counts >> 16) & 0xff; + ncorners++; + } + if (((colors >> 24) & 0xff) == color) { + ncelltris += (counts >> 24) & 0xff; + ncorners++; + } + simplification_ntris[cell] = ncelltris; + simplification_tris[cell] = ntris; + simplification_blocks[cell] = ncorners == 4; + ntris += ncelltris; + } + } + + // First figure out how many triangles we can eliminate. + int in_run = 0, start_run; + int neliminated_triangles = 0; + for (int row = 0; row < nrows - 1; row += 2) { + for (int col = 0; col < ncols; col++) { + int cell = ncols * row + col; + int a = simplification_blocks[cell]; + int b = simplification_blocks[cell + ncols]; + if (a && b) { + if (!in_run) { + in_run = 1; + start_run = col; + } + continue; + } + if (in_run) { + in_run = 0; + int run_width = col - start_run; + neliminated_triangles += run_width * 4 - 2; + } + } + if (in_run) { + in_run = 0; + int run_width = ncols - start_run; + neliminated_triangles += run_width * 4 - 2; + } + } + if (neliminated_triangles == 0) { + continue; + } + + // Build a new index array cell-by-cell. If any given cell is 'F' and + // its neighbor to the south is also 'F', then it's part of a run. + int nnewtris = mesh->ntriangles - neliminated_triangles; + PAR_MSQUARES_T* newtris = PAR_CALLOC(PAR_MSQUARES_T, nnewtris * 3); + PAR_MSQUARES_T* pnewtris = newtris; + in_run = 0; + PAR_MSQUARES_T* tris = mesh->triangles; + for (int row = 0; row < nrows - 1; row += 2) { + for (int col = 0; col < ncols; col++) { + int cell = ncols * row + col; + int south = cell + ncols; + int a = simplification_blocks[cell]; + int b = simplification_blocks[south]; + if (a && b) { + if (!in_run) { + in_run = 1; + start_run = col; + } + continue; + } + if (in_run) { + in_run = 0; + int nw_cell = ncols * row + start_run; + int ne_cell = ncols * row + col - 1; + int sw_cell = nw_cell + ncols; + int se_cell = ne_cell + ncols; + int nw_tri = simplification_tris[nw_cell]; + int ne_tri = simplification_tris[ne_cell]; + int sw_tri = simplification_tris[sw_cell]; + int se_tri = simplification_tris[se_cell]; + int nw_corner = nw_tri * 3 + 5; + int ne_corner = ne_tri * 3 + 2; + int sw_corner = sw_tri * 3 + 0; + int se_corner = se_tri * 3 + 1; + *pnewtris++ = tris[nw_corner]; + *pnewtris++ = tris[sw_corner]; + *pnewtris++ = tris[se_corner]; + *pnewtris++ = tris[se_corner]; + *pnewtris++ = tris[ne_corner]; + *pnewtris++ = tris[nw_corner]; + } + int ncelltris = simplification_ntris[cell]; + int celltri = simplification_tris[cell]; + for (int t = 0; t < ncelltris; t++, celltri++) { + *pnewtris++ = tris[celltri * 3]; + *pnewtris++ = tris[celltri * 3 + 1]; + *pnewtris++ = tris[celltri * 3 + 2]; + } + ncelltris = simplification_ntris[south]; + celltri = simplification_tris[south]; + for (int t = 0; t < ncelltris; t++, celltri++) { + *pnewtris++ = tris[celltri * 3]; + *pnewtris++ = tris[celltri * 3 + 1]; + *pnewtris++ = tris[celltri * 3 + 2]; + } + } + if (in_run) { + in_run = 0; + int nw_cell = ncols * row + start_run; + int ne_cell = ncols * row + ncols - 1; + int sw_cell = nw_cell + ncols; + int se_cell = ne_cell + ncols; + int nw_tri = simplification_tris[nw_cell]; + int ne_tri = simplification_tris[ne_cell]; + int sw_tri = simplification_tris[sw_cell]; + int se_tri = simplification_tris[se_cell]; + int nw_corner = nw_tri * 3 + 5; + int ne_corner = ne_tri * 3 + 2; + int sw_corner = sw_tri * 3 + 0; + int se_corner = se_tri * 3 + 1; + *pnewtris++ = tris[nw_corner]; + *pnewtris++ = tris[sw_corner]; + *pnewtris++ = tris[se_corner]; + *pnewtris++ = tris[se_corner]; + *pnewtris++ = tris[ne_corner]; + *pnewtris++ = tris[nw_corner]; + } + } + mesh->ntriangles -= neliminated_triangles; + free(mesh->triangles); + mesh->triangles = newtris; + } + + free(simplification_blocks); + free(simplification_ntris); + free(simplification_tris); + free(simplification_words); + + par_msquares__finalize(mlist); + for (int i = 0; i < mlist->nmeshes; i++) { + par_remove_unreferenced_verts(mlist->meshes[i]); + } + return mlist; +} + +void par_msquares_free_boundary(par_msquares_boundary* polygon) +{ + free(polygon->points); + free(polygon->chains); + free(polygon->lengths); + free(polygon); +} + +typedef struct par__hedge_s { + uint32_t key; + struct par__hvert_s* endvert; + struct par__hedge_s* opposite; + struct par__hedge_s* next; + struct par__hedge_s* prev; +} par__hedge; + +typedef struct par__hvert_s { + par__hedge* incoming; +} par__hvert; + +typedef struct { + par_msquares_mesh const* mesh; + par__hvert* verts; + par__hedge* edges; + par__hedge** sorted_edges; +} par__hemesh; + +static int par__hedge_cmp(const void *arg0, const void *arg1) +{ + par__hedge* he0 = *((par__hedge**) arg0); + par__hedge* he1 = *((par__hedge**) arg1); + if (he0->key < he1->key) return -1; + if (he0->key > he1->key) return 1; + return 0; +} + +static par__hedge* par__hedge_find(par__hemesh* hemesh, uint32_t key) +{ + par__hedge target = {0}; + target.key = key; + par__hedge* ptarget = ⌖ + int nedges = hemesh->mesh->ntriangles * 3; + par__hedge** result = (par__hedge**) bsearch(&ptarget, hemesh->sorted_edges, + nedges, sizeof(par__hedge*), par__hedge_cmp); + return result ? *result : 0; +} + +static uint32_t par__hedge_key(par__hvert* a, par__hvert* b, par__hvert* s) +{ + uint32_t ai = a - s; + uint32_t bi = b - s; + return (ai << 16) | bi; +} + +par_msquares_boundary* par_msquares_extract_boundary( + par_msquares_mesh const* mesh) +{ + par_msquares_boundary* result = PAR_CALLOC(par_msquares_boundary, 1); + par__hemesh hemesh = {0}; + hemesh.mesh = mesh; + int nedges = mesh->ntriangles * 3; + + // Populate all fields of verts and edges, except opposite. + hemesh.edges = PAR_CALLOC(par__hedge, nedges); + par__hvert* hverts = hemesh.verts = PAR_CALLOC(par__hvert, mesh->npoints); + par__hedge* edge = hemesh.edges; + PAR_MSQUARES_T const* tri = mesh->triangles; + for (int n = 0; n < mesh->ntriangles; n++, edge += 3, tri += 3) { + edge[0].endvert = hverts + tri[1]; + edge[1].endvert = hverts + tri[2]; + edge[2].endvert = hverts + tri[0]; + hverts[tri[1]].incoming = edge + 0; + hverts[tri[2]].incoming = edge + 1; + hverts[tri[0]].incoming = edge + 2; + edge[0].next = edge + 1; + edge[1].next = edge + 2; + edge[2].next = edge + 0; + edge[0].prev = edge + 2; + edge[1].prev = edge + 0; + edge[2].prev = edge + 1; + edge[0].key = par__hedge_key(edge[2].endvert, edge[0].endvert, hverts); + edge[1].key = par__hedge_key(edge[0].endvert, edge[1].endvert, hverts); + edge[2].key = par__hedge_key(edge[1].endvert, edge[2].endvert, hverts); + } + + // Sort the edges according to their key. + hemesh.sorted_edges = PAR_CALLOC(par__hedge*, mesh->ntriangles * 3); + for (int n = 0; n < nedges; n++) { + hemesh.sorted_edges[n] = hemesh.edges + n; + } + qsort(hemesh.sorted_edges, nedges, sizeof(par__hedge*), par__hedge_cmp); + + // Populate the "opposite" field in each edge. + for (int n = 0; n < nedges; n++) { + par__hedge* edge = hemesh.edges + n; + par__hedge* prev = edge->prev; + par__hvert* start = edge->endvert; + par__hvert* end = prev->endvert; + uint32_t key = par__hedge_key(start, end, hverts); + edge->opposite = par__hedge_find(&hemesh, key); + } + + // Re-use the sorted_edges array, filling it with boundary edges only. + // Also create a mapping table to consolidate all boundary verts. + int nborders = 0; + for (int n = 0; n < nedges; n++) { + par__hedge* edge = hemesh.edges + n; + if (!edge->opposite) { + hemesh.sorted_edges[nborders++] = edge; + } + } + + // Allocate for the worst case (all separate triangles). + // We'll adjust the lengths later. + result->nchains = nborders / 3; + result->npoints = nborders + result->nchains; + result->points = PAR_CALLOC(float, 2 * result->npoints); + result->chains = PAR_CALLOC(float*, result->nchains); + result->lengths = PAR_CALLOC(PAR_MSQUARES_T, result->nchains); + + // Iterate over each polyline. + edge = hemesh.sorted_edges[0]; + int pt = 0; + int nwritten = 0; + int nchains = 0; + while (1) { + float* points = result->points; + par__hedge* orig = edge; + PAR_MSQUARES_T index = edge->prev->endvert - hverts; + result->chains[nchains] = points + pt; + result->lengths[nchains]++; + points[pt++] = mesh->points[index * mesh->dim]; + points[pt++] = mesh->points[index * mesh->dim + 1]; + while (1) { + index = edge->endvert - hverts; + edge->key = 0; + nwritten++; + result->lengths[nchains]++; + points[pt++] = mesh->points[index * mesh->dim]; + points[pt++] = mesh->points[index * mesh->dim + 1]; + par__hedge* next = edge->next; + while (next != edge) { + if (!next->opposite) { + break; + } + next = next->opposite->next; + } + edge = next; + if (edge == orig) { + break; + } + } + nchains++; + if (nwritten >= nborders) { + break; + } + for (int i = 0; i < nborders; i++) { + edge = hemesh.sorted_edges[i]; + if (edge->key) { + break; + } + } + } + + result->npoints = pt / 2; + result->nchains = nchains; + free(hemesh.verts); + free(hemesh.edges); + free(hemesh.sorted_edges); + return result; +} + +#endif // PAR_MSQUARES_IMPLEMENTATION +#endif // PAR_MSQUARES_H + +// par_msquares is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_octasphere.h b/source/engine/thirdparty/par/par_octasphere.h new file mode 100644 index 0000000..6f26e4b --- /dev/null +++ b/source/engine/thirdparty/par/par_octasphere.h @@ -0,0 +1,577 @@ +// OCTASPHERE :: https://prideout.net/blog/octasphere +// Tiny malloc-free library that generates triangle meshes for spheres, rounded boxes, and capsules. +// +// This library proffers the following functions: +// +// - par_octasphere_get_counts +// - par_octasphere_populate +// +// Usage example: +// +// /* Specify a 100x100x20 rounded box. */ +// const par_octasphere_config cfg = { +// .corner_radius = 5, +// .width = 100, +// .height = 100, +// .depth = 20, +// .num_subdivisions = 3, +// }; +// +// /* Allocate memory for the mesh and opt-out of normals. */ +// uint32_t num_indices; +// uint32_t num_vertices; +// par_octasphere_get_counts(&cfg, &num_indices, &num_vertices); +// par_octasphere_mesh mesh = { +// .positions = malloc(sizeof(float) * 3 * num_vertices), +// .normals = NULL, +// .texcoords = malloc(sizeof(float) * 2 * num_vertices), +// .indices = malloc(sizeof(uint16_t) * num_indices), +// }; +// +// /* Generate vertex coordinates, UV's, and triangle indices. */ +// par_octasphere_populate(&cfg, &mesh); +// +// To generate a sphere: set width, height, and depth to 0 in your configuration. +// To generate a capsule shape: set only two of these dimensions to 0. +// +// Distributed under the MIT License, see bottom of file. + +#ifndef PAR_OCTASPHERE_H +#define PAR_OCTASPHERE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PAR_OCTASPHERE_MAX_SUBDIVISIONS 5 + +typedef enum { + PAR_OCTASPHERE_UV_LATLONG, +} par_octasphere_uv_mode; + +typedef enum { + PAR_OCTASPHERE_NORMALS_SMOOTH, +} par_octasphere_normals_mode; + +typedef struct { + float corner_radius; + float width; + float height; + float depth; + int num_subdivisions; + par_octasphere_uv_mode uv_mode; + par_octasphere_normals_mode normals_mode; +} par_octasphere_config; + +typedef struct { + float* positions; + float* normals; + float* texcoords; + uint16_t* indices; + uint32_t num_indices; + uint32_t num_vertices; +} par_octasphere_mesh; + +// Computes the maximum possible number of indices and vertices for the given octasphere config. +void par_octasphere_get_counts(const par_octasphere_config* config, uint32_t* num_indices, + uint32_t* num_vertices); + +// Populates a pre-allocated mesh structure with indices and vertices. +void par_octasphere_populate(const par_octasphere_config* config, par_octasphere_mesh* mesh); + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- +#ifdef PAR_OCTASPHERE_IMPLEMENTATION + +#include +#include +#include // for memcpy + +#define PARO_PI (3.14159265359) +#define PARO_MIN(a, b) (a > b ? b : a) +#define PARO_MAX(a, b) (a > b ? a : b) +#define PARO_CLAMP(v, lo, hi) PARO_MAX(lo, PARO_MIN(hi, v)) +#define PARO_MAX_BOUNDARY_LENGTH ((1 << PAR_OCTASPHERE_MAX_SUBDIVISIONS) + 1) + +#ifndef PARO_CONSTANT_TOPOLOGY +#define PARO_CONSTANT_TOPOLOGY 1 +#endif + +static uint16_t* paro_write_quad(uint16_t* dst, uint16_t a, uint16_t b, uint16_t c, uint16_t d) { + *dst++ = a; + *dst++ = b; + *dst++ = c; + *dst++ = c; + *dst++ = d; + *dst++ = a; + return dst; +} + +static void paro_write_ui3(uint16_t* dst, int index, uint16_t a, uint16_t b, uint16_t c) { + dst[index * 3 + 0] = a; + dst[index * 3 + 1] = b; + dst[index * 3 + 2] = c; +} + +static float* paro_write_f3(float* dst, const float src[3]) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + return dst + 3; +} + +static void paro_copy(float dst[3], const float src[3]) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; +} + +static float paro_dot(const float a[3], const float b[3]) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +static void paro_add(float result[3], float const a[3], float const b[3]) { + result[0] = a[0] + b[0]; + result[1] = a[1] + b[1]; + result[2] = a[2] + b[2]; +} + +static void paro_normalize(float v[3]) { + float lsqr = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + if (lsqr > 0) { + v[0] /= lsqr; + v[1] /= lsqr; + v[2] /= lsqr; + } +} + +static void paro_cross(float result[3], float const a[3], float const b[3]) { + float x = (a[1] * b[2]) - (a[2] * b[1]); + float y = (a[2] * b[0]) - (a[0] * b[2]); + float z = (a[0] * b[1]) - (a[1] * b[0]); + result[0] = x; + result[1] = y; + result[2] = z; +} + +static void paro_scale(float dst[3], float v) { + dst[0] *= v; + dst[1] *= v; + dst[2] *= v; +} + +static void paro_scaled(float dst[3], const float src[3], float v) { + dst[0] = src[0] * v; + dst[1] = src[1] * v; + dst[2] = src[2] * v; +} + +static void paro_quat_from_rotation(float quat[4], const float axis[3], float radians) { + paro_copy(quat, axis); + paro_normalize(quat); + paro_scale(quat, sin(0.5 * radians)); + quat[3] = cos(0.5 * radians); +} + +static void paro_quat_from_eulers(float quat[4], const float eulers[3]) { + const float roll = eulers[0]; + const float pitch = eulers[1]; + const float yaw = eulers[2]; + const float halfRoll = roll * 0.5; + const float sR = sin(halfRoll); + const float cR = cos(halfRoll); + const float halfPitch = pitch * 0.5; + const float sP = sin(halfPitch); + const float cP = cos(halfPitch); + const float halfYaw = yaw * 0.5; + const float sY = sin(halfYaw); + const float cY = cos(halfYaw); + quat[0] = (sR * cP * cY) + (cR * sP * sY); + quat[1] = (cR * sP * cY) - (sR * cP * sY); + quat[2] = (cR * cP * sY) + (sR * sP * cY); + quat[3] = (cR * cP * cY) - (sR * sP * sY); +} + +static void paro_quat_rotate_vector(float dst[3], const float quat[4], const float src[3]) { + float t[3]; + paro_cross(t, quat, src); + paro_scale(t, 2.0); + + float p[3]; + paro_cross(p, quat, t); + + paro_scaled(dst, t, quat[3]); + paro_add(dst, dst, src); + paro_add(dst, dst, p); +} + +static float* paro_write_geodesic(float* dst, const float point_a[3], const float point_b[3], + int num_segments) { + dst = paro_write_f3(dst, point_a); + if (num_segments == 0) { + return dst; + } + const float angle_between_endpoints = acos(paro_dot(point_a, point_b)); + const float dtheta = angle_between_endpoints / num_segments; + float rotation_axis[3], quat[4]; + paro_cross(rotation_axis, point_a, point_b); + paro_normalize(rotation_axis); + for (int point_index = 1; point_index < num_segments; point_index++, dst += 3) { + paro_quat_from_rotation(quat, rotation_axis, dtheta * point_index); + paro_quat_rotate_vector(dst, quat, point_a); + } + return paro_write_f3(dst, point_b); +} + +void paro_add_quads(const par_octasphere_config* config, par_octasphere_mesh* mesh) { + const int ndivisions = PARO_CLAMP(config->num_subdivisions, 0, PAR_OCTASPHERE_MAX_SUBDIVISIONS); + const int n = (1 << ndivisions) + 1; + const int verts_per_patch = n * (n + 1) / 2; + const float r2 = config->corner_radius * 2; + const float w = PARO_MAX(config->width, r2); + const float h = PARO_MAX(config->height, r2); + const float d = PARO_MAX(config->depth, r2); + const float tx = (w - r2) / 2, ty = (h - r2) / 2, tz = (d - r2) / 2; + + // Find the vertex indices along each of the patch's 3 edges. + uint16_t boundaries[3][PARO_MAX_BOUNDARY_LENGTH]; + int a = 0, b = 0, c = 0, row; + uint16_t j0 = 0; + for (int col_index = 0; col_index < n - 1; col_index++) { + int col_height = n - 1 - col_index; + uint16_t j1 = j0 + 1; + boundaries[0][a++] = j0; + for (row = 0; row < col_height - 1; row++) { + if (col_height == n - 1) { + boundaries[2][c++] = j0 + row; + } + } + if (col_height == n - 1) { + boundaries[2][c++] = j0 + row; + boundaries[2][c++] = j1 + row; + } + boundaries[1][b++] = j1 + row; + j0 += col_height + 1; + } + boundaries[0][a] = boundaries[1][b] = j0 + row; + + // If there is no rounding (i.e. this is a plain box), then clobber the existing indices. + if (!PARO_CONSTANT_TOPOLOGY && config->corner_radius == 0) { + mesh->num_indices = 0; + } + uint16_t* write_ptr = mesh->indices + mesh->num_indices; + const uint16_t* begin_ptr = write_ptr; + + if (PARO_CONSTANT_TOPOLOGY || config->corner_radius > 0) { + // Go around the top half. + for (int patch = 0; patch < 4; patch++) { + if (!PARO_CONSTANT_TOPOLOGY && (patch % 2) == 0 && tz == 0) continue; + if (!PARO_CONSTANT_TOPOLOGY && (patch % 2) == 1 && tx == 0) continue; + const int next_patch = (patch + 1) % 4; + const uint16_t* boundary_a = boundaries[1]; + const uint16_t* boundary_b = boundaries[0]; + const uint16_t offset_a = verts_per_patch * patch; + const uint16_t offset_b = verts_per_patch * next_patch; + for (int i = 0; i < n - 1; i++) { + const uint16_t a = boundary_a[i] + offset_a; + const uint16_t b = boundary_b[i] + offset_b; + const uint16_t c = boundary_a[i + 1] + offset_a; + const uint16_t d = boundary_b[i + 1] + offset_b; + write_ptr = paro_write_quad(write_ptr, a, b, d, c); + } + } + // Go around the bottom half. + for (int patch = 4; patch < 8; patch++) { + if (!PARO_CONSTANT_TOPOLOGY && (patch % 2) == 0 && tx == 0) continue; + if (!PARO_CONSTANT_TOPOLOGY && (patch % 2) == 1 && tz == 0) continue; + const int next_patch = 4 + (patch + 1) % 4; + const uint16_t* boundary_a = boundaries[0]; + const uint16_t* boundary_b = boundaries[2]; + const uint16_t offset_a = verts_per_patch * patch; + const uint16_t offset_b = verts_per_patch * next_patch; + for (int i = 0; i < n - 1; i++) { + const uint16_t a = boundary_a[i] + offset_a; + const uint16_t b = boundary_b[i] + offset_b; + const uint16_t c = boundary_a[i + 1] + offset_a; + const uint16_t d = boundary_b[i + 1] + offset_b; + write_ptr = paro_write_quad(write_ptr, d, b, a, c); + } + } + // Connect the top and bottom halves. + if (PARO_CONSTANT_TOPOLOGY || ty > 0) { + for (int patch = 0; patch < 4; patch++) { + const int next_patch = 4 + (4 - patch) % 4; + const uint16_t* boundary_a = boundaries[2]; + const uint16_t* boundary_b = boundaries[1]; + const uint16_t offset_a = verts_per_patch * patch; + const uint16_t offset_b = verts_per_patch * next_patch; + for (int i = 0; i < n - 1; i++) { + const uint16_t a = boundary_a[i] + offset_a; + const uint16_t b = boundary_b[n - 1 - i] + offset_b; + const uint16_t c = boundary_a[i + 1] + offset_a; + const uint16_t d = boundary_b[n - 1 - i - 1] + offset_b; + write_ptr = paro_write_quad(write_ptr, a, b, d, c); + } + } + } + } + + // Fill in the top and bottom holes. + if (PARO_CONSTANT_TOPOLOGY || tx > 0 || ty > 0) { + uint16_t a, b, c, d; + a = boundaries[0][n - 1]; + b = a + verts_per_patch; + c = b + verts_per_patch; + d = c + verts_per_patch; + write_ptr = paro_write_quad(write_ptr, a, b, c, d); + a = boundaries[2][0] + verts_per_patch * 4; + b = a + verts_per_patch; + c = b + verts_per_patch; + d = c + verts_per_patch; + write_ptr = paro_write_quad(write_ptr, a, b, c, d); + } + + // Fill in the side holes. + if (PARO_CONSTANT_TOPOLOGY || ty > 0) { + const int sides[4][2] = {{7, 0}, {1, 2}, {3, 4}, {5, 6}}; + for (int side = 0; side < 4; side++) { + int patch_index, patch, next_patch; + uint16_t *boundary_a, *boundary_b; + uint16_t offset_a, offset_b; + + uint16_t a, b; + patch_index = sides[side][0]; + patch = patch_index / 2; + next_patch = 4 + (4 - patch) % 4; + offset_a = verts_per_patch * patch; + offset_b = verts_per_patch * next_patch; + boundary_a = boundaries[2]; + boundary_b = boundaries[1]; + if (patch_index % 2 == 0) { + a = boundary_a[0] + offset_a; + b = boundary_b[n - 1] + offset_b; + } else { + a = boundary_a[n - 1] + offset_a; + b = boundary_b[0] + offset_b; + } + + uint16_t c, d; + patch_index = sides[side][1]; + patch = patch_index / 2; + next_patch = 4 + (4 - patch) % 4; + offset_a = verts_per_patch * patch; + offset_b = verts_per_patch * next_patch; + boundary_a = boundaries[2]; + boundary_b = boundaries[1]; + if (patch_index % 2 == 0) { + c = boundary_a[0] + offset_a; + d = boundary_b[n - 1] + offset_b; + } else { + c = boundary_a[n - 1] + offset_a; + d = boundary_b[0] + offset_b; + } + write_ptr = paro_write_quad(write_ptr, a, b, d, c); + } + } + + mesh->num_indices += write_ptr - begin_ptr; + +#ifndef NDEBUG + uint32_t expected_indices; + uint32_t expected_vertices; + par_octasphere_get_counts(config, &expected_indices, &expected_vertices); + assert(mesh->num_indices <= expected_indices); +#endif +} + +void par_octasphere_get_counts(const par_octasphere_config* config, uint32_t* num_indices, + uint32_t* num_vertices) { + const int ndivisions = PARO_CLAMP(config->num_subdivisions, 0, PAR_OCTASPHERE_MAX_SUBDIVISIONS); + const int n = (1 << ndivisions) + 1; + const int verts_per_patch = n * (n + 1) / 2; + const float r2 = config->corner_radius * 2; + const float w = PARO_MAX(config->width, r2); + const float h = PARO_MAX(config->height, r2); + const float d = PARO_MAX(config->depth, r2); + const float tx = (w - r2) / 2, ty = (h - r2) / 2, tz = (d - r2) / 2; + const int triangles_per_patch = (n - 2) * (n - 1) + n - 1; + + // If this is a sphere, return early. + if (tx == 0 && ty == 0 && tz == 0) { + *num_indices = triangles_per_patch * 8 * 3; + *num_vertices = verts_per_patch * 8; + return; + } + + // This is a cuboid, so account for the maximum number of possible quads. + // - 4*(n-1) quads between the 4 top patches. + // - 4*(n-1) quads between the 4 bottom patches. + // - 4*(n-1) quads between the top and bottom patches. + // - 6 quads to fill "holes" in each cuboid face. + const int num_connection_quads = (4 + 4 + 4) * (n - 1) + 6; + + *num_indices = (triangles_per_patch * 8 + num_connection_quads * 2) * 3; + *num_vertices = verts_per_patch * 8; +} + +void par_octasphere_populate(const par_octasphere_config* config, par_octasphere_mesh* mesh) { + const int ndivisions = PARO_CLAMP(config->num_subdivisions, 0, PAR_OCTASPHERE_MAX_SUBDIVISIONS); + const int n = (1 << ndivisions) + 1; + const int verts_per_patch = n * (n + 1) / 2; + const float r2 = config->corner_radius * 2; + const float w = PARO_MAX(config->width, r2); + const float h = PARO_MAX(config->height, r2); + const float d = PARO_MAX(config->depth, r2); + const float tx = (w - r2) / 2, ty = (h - r2) / 2, tz = (d - r2) / 2; + const int triangles_per_patch = (n - 2) * (n - 1) + n - 1; + const int total_vertices = verts_per_patch * 8; + + // START TESSELLATION OF SINGLE PATCH (one-eighth of the octasphere) + float* write_ptr = mesh->positions; + for (int i = 0; i < n; i++) { + const float theta = PARO_PI * 0.5 * i / (n - 1); + const float point_a[] = {0, sinf(theta), cosf(theta)}; + const float point_b[] = {cosf(theta), sinf(theta), 0}; + const int num_segments = n - 1 - i; + write_ptr = paro_write_geodesic(write_ptr, point_a, point_b, num_segments); + } + int f = 0, j0 = 0; + uint16_t* faces = mesh->indices; + for (int col_index = 0; col_index < n - 1; col_index++) { + const int col_height = n - 1 - col_index; + const int j1 = j0 + 1; + const int j2 = j0 + col_height + 1; + const int j3 = j0 + col_height + 2; + for (int row = 0; row < col_height - 1; row++) { + paro_write_ui3(faces, f++, j0 + row, j1 + row, j2 + row); + paro_write_ui3(faces, f++, j2 + row, j1 + row, j3 + row); + } + const int row = col_height - 1; + paro_write_ui3(faces, f++, j0 + row, j1 + row, j2 + row); + j0 = j2; + } + // END TESSELLATION OF SINGLE PATCH + + // START 8-WAY CLONE OF PATCH + // clang-format off + float euler_angles[8][3] = { + {0, 0, 0}, {0, 1, 0}, {0, 2, 0}, {0, 3, 0}, + {1, 0, 0}, {1, 0, 1}, {1, 0, 2}, {1, 0, 3}, + }; + // clang-format on + for (int octant = 1; octant < 8; octant++) { + paro_scale(euler_angles[octant], PARO_PI * 0.5); + float quat[4]; + paro_quat_from_eulers(quat, euler_angles[octant]); + float* dst = mesh->positions + octant * verts_per_patch * 3; + const float* src = mesh->positions; + for (int vindex = 0; vindex < verts_per_patch; vindex++, dst += 3, src += 3) { + paro_quat_rotate_vector(dst, quat, src); + } + } + for (int octant = 1; octant < 8; octant++) { + const int indices_per_patch = triangles_per_patch * 3; + uint16_t* dst = mesh->indices + octant * indices_per_patch; + const uint16_t* src = mesh->indices; + const uint16_t offset = verts_per_patch * octant; + for (int iindex = 0; iindex < indices_per_patch; ++iindex) { + dst[iindex] = src[iindex] + offset; + } + } + // END 8-WAY CLONE OF PATCH + + if (mesh->texcoords && config->uv_mode == PAR_OCTASPHERE_UV_LATLONG) { + for (int i = 0; i < total_vertices; i++) { + const int octant = i / verts_per_patch; + const int relative_index = i % verts_per_patch; + float* uv = mesh->texcoords + i * 2; + const float* xyz = mesh->positions + i * 3; + const float x = xyz[0], y = xyz[1], z = xyz[2]; + const float phi = -atan2(z, x); + const float theta = acos(y); + uv[0] = 0.5 * (phi / PARO_PI + 1.0); + uv[1] = theta / PARO_PI; + // Special case for the north pole. + if (octant < 4 && relative_index == verts_per_patch - 1) { + uv[0] = fmod(0.375 + 0.25 * octant, 1.0); + uv[1] = 0; + } + // Special case for the south pole. + if (octant >= 4 && relative_index == 0) { + uv[0] = 0.375 - 0.25 * (octant - 4); + uv[0] = uv[0] + uv[0] < 0 ? 1.0 : 0.0; + uv[1] = 1.0; + } + // Adjust the prime meridian for proper wrapping. + if ((octant == 2 || octant == 6) && uv[0] < 0.5) { + uv[0] += 1.0; + } + } + } + + if (mesh->normals && config->normals_mode == PAR_OCTASPHERE_NORMALS_SMOOTH) { + memcpy(mesh->normals, mesh->positions, sizeof(float) * 3 * total_vertices); + } + + if (config->corner_radius != 1.0) { + for (int i = 0; i < total_vertices; i++) { + float* xyz = mesh->positions + i * 3; + xyz[0] *= config->corner_radius; + xyz[1] *= config->corner_radius; + xyz[2] *= config->corner_radius; + } + } + + mesh->num_indices = triangles_per_patch * 8 * 3; + mesh->num_vertices = total_vertices; + + if (tx == 0 && ty == 0 && tz == 0) { + return; + } + + for (int i = 0; i < total_vertices; i++) { + float* xyz = mesh->positions + i * 3; + const int octant = i / verts_per_patch; + const float sx = (octant < 2 || octant == 4 || octant == 7) ? +1 : -1; + const float sy = octant < 4 ? +1 : -1; + const float sz = (octant == 0 || octant == 3 || octant == 4 || octant == 5) ? +1 : -1; + xyz[0] += tx * sx; + xyz[1] += ty * sy; + xyz[2] += tz * sz; + } + + paro_add_quads(config, mesh); +} + +#endif // PAR_OCTASPHERE_IMPLEMENTATION +#endif // PAR_OCTASPHERE_H + +// par_octasphere is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_shapes.h b/source/engine/thirdparty/par/par_shapes.h new file mode 100644 index 0000000..beae160 --- /dev/null +++ b/source/engine/thirdparty/par/par_shapes.h @@ -0,0 +1,2153 @@ +// SHAPES :: https://github.com/prideout/par +// Simple C library for creation and manipulation of triangle meshes. +// +// The API is divided into three sections: +// +// - Generators. Create parametric surfaces, platonic solids, etc. +// - Queries. Ask a mesh for its axis-aligned bounding box, etc. +// - Transforms. Rotate a mesh, merge it with another, add normals, etc. +// +// In addition to the comment block above each function declaration, the API +// has informal documentation here: +// +// https://prideout.net/shapes +// +// For our purposes, a "mesh" is a list of points and a list of triangles; the +// former is a flattened list of three-tuples (32-bit floats) and the latter is +// also a flattened list of three-tuples (16-bit uints). Triangles are always +// oriented such that their front face winds counter-clockwise. +// +// Optionally, meshes can contain 3D normals (one per vertex), and 2D texture +// coordinates (one per vertex). That's it! If you need something fancier, +// look elsewhere. +// +// Distributed under the MIT License, see bottom of file. + +#ifndef PAR_SHAPES_H +#define PAR_SHAPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#if !defined(_MSC_VER) +# include +#else // MSVC +# if _MSC_VER >= 1800 +# include +# else // stdbool.h missing prior to MSVC++ 12.0 (VS2013) +# define bool int +# define true 1 +# define false 0 +# endif +#endif + +#ifndef PAR_SHAPES_T +#define PAR_SHAPES_T uint16_t +#endif + +typedef struct par_shapes_mesh_s { + float* points; // Flat list of 3-tuples (X Y Z X Y Z...) + int npoints; // Number of points + PAR_SHAPES_T* triangles; // Flat list of 3-tuples (I J K I J K...) + int ntriangles; // Number of triangles + float* normals; // Optional list of 3-tuples (X Y Z X Y Z...) + float* tcoords; // Optional list of 2-tuples (U V U V U V...) +} par_shapes_mesh; + +void par_shapes_free_mesh(par_shapes_mesh*); + +// Generators ------------------------------------------------------------------ + +// Instance a cylinder that sits on the Z=0 plane using the given tessellation +// levels across the UV domain. Think of "slices" like a number of pizza +// slices, and "stacks" like a number of stacked rings. Height and radius are +// both 1.0, but they can easily be changed with par_shapes_scale. +par_shapes_mesh* par_shapes_create_cylinder(int slices, int stacks); + +// Cone is similar to cylinder but the radius diminishes to zero as Z increases. +// Again, height and radius are 1.0, but can be changed with par_shapes_scale. +par_shapes_mesh* par_shapes_create_cone(int slices, int stacks); + +// Create a disk of radius 1.0 with texture coordinates and normals by squashing +// a cone flat on the Z=0 plane. +par_shapes_mesh* par_shapes_create_parametric_disk(int slices, int stacks); + +// Create a donut that sits on the Z=0 plane with the specified inner radius. +// The outer radius can be controlled with par_shapes_scale. +par_shapes_mesh* par_shapes_create_torus(int slices, int stacks, float radius); + +// Create a sphere with texture coordinates and small triangles near the poles. +par_shapes_mesh* par_shapes_create_parametric_sphere(int slices, int stacks); + +// Approximate a sphere with a subdivided icosahedron, which produces a nice +// distribution of triangles, but no texture coordinates. Each subdivision +// level scales the number of triangles by four, so use a very low number. +par_shapes_mesh* par_shapes_create_subdivided_sphere(int nsubdivisions); + +// More parametric surfaces. +par_shapes_mesh* par_shapes_create_klein_bottle(int slices, int stacks); +par_shapes_mesh* par_shapes_create_trefoil_knot(int slices, int stacks, + float radius); +par_shapes_mesh* par_shapes_create_hemisphere(int slices, int stacks); +par_shapes_mesh* par_shapes_create_plane(int slices, int stacks); + +// Create a parametric surface from a callback function that consumes a 2D +// point in [0,1] and produces a 3D point. +typedef void (*par_shapes_fn)(float const*, float*, void*); +par_shapes_mesh* par_shapes_create_parametric(par_shapes_fn, int slices, + int stacks, void* userdata); + +// Generate points for a 20-sided polyhedron that fits in the unit sphere. +// Texture coordinates and normals are not generated. +par_shapes_mesh* par_shapes_create_icosahedron(); + +// Generate points for a 12-sided polyhedron that fits in the unit sphere. +// Again, texture coordinates and normals are not generated. +par_shapes_mesh* par_shapes_create_dodecahedron(); + +// More platonic solids. +par_shapes_mesh* par_shapes_create_octahedron(); +par_shapes_mesh* par_shapes_create_tetrahedron(); +par_shapes_mesh* par_shapes_create_cube(); + +// Generate an orientable disk shape in 3-space. Does not include normals or +// texture coordinates. +par_shapes_mesh* par_shapes_create_disk(float radius, int slices, + float const* center, float const* normal); + +// Create an empty shape. Useful for building scenes with merge_and_free. +par_shapes_mesh* par_shapes_create_empty(); + +// Generate a rock shape that sits on the Y=0 plane, and sinks into it a bit. +// This includes smooth normals but no texture coordinates. Each subdivision +// level scales the number of triangles by four, so use a very low number. +par_shapes_mesh* par_shapes_create_rock(int seed, int nsubdivisions); + +// Create trees or vegetation by executing a recursive turtle graphics program. +// The program is a list of command-argument pairs. See the unit test for +// an example. Texture coordinates and normals are not generated. +par_shapes_mesh* par_shapes_create_lsystem(char const* program, int slices, + int maxdepth); + +// Queries --------------------------------------------------------------------- + +// Dump out a text file conforming to the venerable OBJ format. +void par_shapes_export(par_shapes_mesh const*, char const* objfile); + +// Take a pointer to 6 floats and set them to min xyz, max xyz. +void par_shapes_compute_aabb(par_shapes_mesh const* mesh, float* aabb); + +// Make a deep copy of a mesh. To make a brand new copy, pass null to "target". +// To avoid memory churn, pass an existing mesh to "target". +par_shapes_mesh* par_shapes_clone(par_shapes_mesh const* mesh, + par_shapes_mesh* target); + +// Transformations ------------------------------------------------------------- + +void par_shapes_merge(par_shapes_mesh* dst, par_shapes_mesh const* src); +void par_shapes_translate(par_shapes_mesh*, float x, float y, float z); +void par_shapes_rotate(par_shapes_mesh*, float radians, float const* axis); +void par_shapes_scale(par_shapes_mesh*, float x, float y, float z); +void par_shapes_merge_and_free(par_shapes_mesh* dst, par_shapes_mesh* src); + +// Reverse the winding of a run of faces. Useful when drawing the inside of +// a Cornell Box. Pass 0 for nfaces to reverse every face in the mesh. +void par_shapes_invert(par_shapes_mesh*, int startface, int nfaces); + +// Remove all triangles whose area is less than minarea. +void par_shapes_remove_degenerate(par_shapes_mesh*, float minarea); + +// Dereference the entire index buffer and replace the point list. +// This creates an inefficient structure, but is useful for drawing facets. +// If create_indices is true, a trivial "0 1 2 3..." index buffer is generated. +void par_shapes_unweld(par_shapes_mesh* mesh, bool create_indices); + +// Merge colocated verts, build a new index buffer, and return the +// optimized mesh. Epsilon is the maximum distance to consider when +// welding vertices. The mapping argument can be null, or a pointer to +// npoints integers, which gets filled with the mapping from old vertex +// indices to new indices. +par_shapes_mesh* par_shapes_weld(par_shapes_mesh const*, float epsilon, + PAR_SHAPES_T* mapping); + +// Compute smooth normals by averaging adjacent facet normals. +void par_shapes_compute_normals(par_shapes_mesh* m); + +// Global Config --------------------------------------------------------------- + +void par_shapes_set_epsilon_welded_normals(float epsilon); +void par_shapes_set_epsilon_degenerate_sphere(float epsilon); + +// Advanced -------------------------------------------------------------------- + +void par_shapes__compute_welded_normals(par_shapes_mesh* m); +void par_shapes__connect(par_shapes_mesh* scene, par_shapes_mesh* cylinder, + int slices); + +#ifndef PAR_PI +#define PAR_PI (3.14159265359) +#define PAR_MIN(a, b) (a > b ? b : a) +#define PAR_MAX(a, b) (a > b ? a : b) +#define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) +#define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#define PAR_SQR(a) ((a) * (a)) +#endif + +#ifndef PAR_MALLOC +#define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) +#define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) +#define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N))) +#define PAR_FREE(BUF) free(BUF) +#endif + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- + +#ifdef PAR_SHAPES_IMPLEMENTATION +#include +#include +#include +#include +#include +#include +#include + +static float par_shapes__epsilon_welded_normals = 0.001; +static float par_shapes__epsilon_degenerate_sphere = 0.0001; + +static void par_shapes__sphere(float const* uv, float* xyz, void*); +static void par_shapes__hemisphere(float const* uv, float* xyz, void*); +static void par_shapes__plane(float const* uv, float* xyz, void*); +static void par_shapes__klein(float const* uv, float* xyz, void*); +static void par_shapes__cylinder(float const* uv, float* xyz, void*); +static void par_shapes__cone(float const* uv, float* xyz, void*); +static void par_shapes__torus(float const* uv, float* xyz, void*); +static void par_shapes__trefoil(float const* uv, float* xyz, void*); + +struct osn_context; +static int par__simplex_noise(int64_t seed, struct osn_context** ctx); +static void par__simplex_noise_free(struct osn_context* ctx); +static double par__simplex_noise2(struct osn_context* ctx, double x, double y); + +static void par_shapes__copy3(float* result, float const* a) +{ + result[0] = a[0]; + result[1] = a[1]; + result[2] = a[2]; +} + +static float par_shapes__dot3(float const* a, float const* b) +{ + return b[0] * a[0] + b[1] * a[1] + b[2] * a[2]; +} + +static void par_shapes__transform3(float* p, float const* x, float const* y, + float const* z) +{ + float px = par_shapes__dot3(p, x); + float py = par_shapes__dot3(p, y); + float pz = par_shapes__dot3(p, z); + p[0] = px; + p[1] = py; + p[2] = pz; +} + +static void par_shapes__cross3(float* result, float const* a, float const* b) +{ + float x = (a[1] * b[2]) - (a[2] * b[1]); + float y = (a[2] * b[0]) - (a[0] * b[2]); + float z = (a[0] * b[1]) - (a[1] * b[0]); + result[0] = x; + result[1] = y; + result[2] = z; +} + +static void par_shapes__mix3(float* d, float const* a, float const* b, float t) +{ + float x = b[0] * t + a[0] * (1 - t); + float y = b[1] * t + a[1] * (1 - t); + float z = b[2] * t + a[2] * (1 - t); + d[0] = x; + d[1] = y; + d[2] = z; +} + +static void par_shapes__scale3(float* result, float a) +{ + result[0] *= a; + result[1] *= a; + result[2] *= a; +} + +static void par_shapes__normalize3(float* v) +{ + float lsqr = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); + if (lsqr > 0) { + par_shapes__scale3(v, 1.0f / lsqr); + } +} + +static void par_shapes__subtract3(float* result, float const* a) +{ + result[0] -= a[0]; + result[1] -= a[1]; + result[2] -= a[2]; +} + +static void par_shapes__add3(float* result, float const* a) +{ + result[0] += a[0]; + result[1] += a[1]; + result[2] += a[2]; +} + +static float par_shapes__sqrdist3(float const* a, float const* b) +{ + float dx = a[0] - b[0]; + float dy = a[1] - b[1]; + float dz = a[2] - b[2]; + return dx * dx + dy * dy + dz * dz; +} + +void par_shapes__compute_welded_normals(par_shapes_mesh* m) +{ + const float epsilon = par_shapes__epsilon_welded_normals; + m->normals = PAR_MALLOC(float, m->npoints * 3); + PAR_SHAPES_T* weldmap = PAR_MALLOC(PAR_SHAPES_T, m->npoints); + par_shapes_mesh* welded = par_shapes_weld(m, epsilon, weldmap); + par_shapes_compute_normals(welded); + float* pdst = m->normals; + for (int i = 0; i < m->npoints; i++, pdst += 3) { + int d = weldmap[i]; + float const* pnormal = welded->normals + d * 3; + pdst[0] = pnormal[0]; + pdst[1] = pnormal[1]; + pdst[2] = pnormal[2]; + } + PAR_FREE(weldmap); + par_shapes_free_mesh(welded); +} + +par_shapes_mesh* par_shapes_create_cylinder(int slices, int stacks) +{ + if (slices < 3 || stacks < 1) { + return 0; + } + return par_shapes_create_parametric(par_shapes__cylinder, slices, + stacks, 0); +} + +par_shapes_mesh* par_shapes_create_cone(int slices, int stacks) +{ + if (slices < 3 || stacks < 1) { + return 0; + } + return par_shapes_create_parametric(par_shapes__cone, slices, + stacks, 0); +} + +par_shapes_mesh* par_shapes_create_parametric_disk(int slices, int stacks) +{ + par_shapes_mesh* m = par_shapes_create_cone(slices, stacks); + if (m) { + par_shapes_scale(m, 1.0f, 1.0f, 0.0f); + } + return m; +} + +par_shapes_mesh* par_shapes_create_parametric_sphere(int slices, int stacks) +{ + if (slices < 3 || stacks < 3) { + return 0; + } + par_shapes_mesh* m = par_shapes_create_parametric(par_shapes__sphere, + slices, stacks, 0); + par_shapes_remove_degenerate(m, par_shapes__epsilon_degenerate_sphere); + return m; +} + +par_shapes_mesh* par_shapes_create_hemisphere(int slices, int stacks) +{ + if (slices < 3 || stacks < 3) { + return 0; + } + par_shapes_mesh* m = par_shapes_create_parametric(par_shapes__hemisphere, + slices, stacks, 0); + par_shapes_remove_degenerate(m, par_shapes__epsilon_degenerate_sphere); + return m; +} + +par_shapes_mesh* par_shapes_create_torus(int slices, int stacks, float radius) +{ + if (slices < 3 || stacks < 3) { + return 0; + } + assert(radius <= 1.0 && "Use smaller radius to avoid self-intersection."); + assert(radius >= 0.1 && "Use larger radius to avoid self-intersection."); + void* userdata = (void*) &radius; + return par_shapes_create_parametric(par_shapes__torus, slices, + stacks, userdata); +} + +par_shapes_mesh* par_shapes_create_klein_bottle(int slices, int stacks) +{ + if (slices < 3 || stacks < 3) { + return 0; + } + par_shapes_mesh* mesh = par_shapes_create_parametric( + par_shapes__klein, slices, stacks, 0); + int face = 0; + for (int stack = 0; stack < stacks; stack++) { + for (int slice = 0; slice < slices; slice++, face += 2) { + if (stack < 27 * stacks / 32) { + par_shapes_invert(mesh, face, 2); + } + } + } + par_shapes__compute_welded_normals(mesh); + return mesh; +} + +par_shapes_mesh* par_shapes_create_trefoil_knot(int slices, int stacks, + float radius) +{ + if (slices < 3 || stacks < 3) { + return 0; + } + assert(radius <= 3.0 && "Use smaller radius to avoid self-intersection."); + assert(radius >= 0.5 && "Use larger radius to avoid self-intersection."); + void* userdata = (void*) &radius; + return par_shapes_create_parametric(par_shapes__trefoil, slices, + stacks, userdata); +} + +par_shapes_mesh* par_shapes_create_plane(int slices, int stacks) +{ + if (slices < 1 || stacks < 1) { + return 0; + } + return par_shapes_create_parametric(par_shapes__plane, slices, + stacks, 0); +} + +par_shapes_mesh* par_shapes_create_parametric(par_shapes_fn fn, + int slices, int stacks, void* userdata) +{ + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + + // Generate verts. + mesh->npoints = (slices + 1) * (stacks + 1); + mesh->points = PAR_CALLOC(float, 3 * mesh->npoints); + float uv[2]; + float xyz[3]; + float* points = mesh->points; + for (int stack = 0; stack < stacks + 1; stack++) { + uv[0] = (float) stack / stacks; + for (int slice = 0; slice < slices + 1; slice++) { + uv[1] = (float) slice / slices; + fn(uv, xyz, userdata); + *points++ = xyz[0]; + *points++ = xyz[1]; + *points++ = xyz[2]; + } + } + + // Generate texture coordinates. + mesh->tcoords = PAR_CALLOC(float, 2 * mesh->npoints); + float* uvs = mesh->tcoords; + for (int stack = 0; stack < stacks + 1; stack++) { + uv[0] = (float) stack / stacks; + for (int slice = 0; slice < slices + 1; slice++) { + uv[1] = (float) slice / slices; + *uvs++ = uv[0]; + *uvs++ = uv[1]; + } + } + + // Generate faces. + mesh->ntriangles = 2 * slices * stacks; + mesh->triangles = PAR_CALLOC(PAR_SHAPES_T, 3 * mesh->ntriangles); + int v = 0; + PAR_SHAPES_T* face = mesh->triangles; + for (int stack = 0; stack < stacks; stack++) { + for (int slice = 0; slice < slices; slice++) { + int next = slice + 1; + *face++ = v + slice + slices + 1; + *face++ = v + next; + *face++ = v + slice; + *face++ = v + slice + slices + 1; + *face++ = v + next + slices + 1; + *face++ = v + next; + } + v += slices + 1; + } + + par_shapes__compute_welded_normals(mesh); + return mesh; +} + +void par_shapes_free_mesh(par_shapes_mesh* mesh) +{ + PAR_FREE(mesh->points); + PAR_FREE(mesh->triangles); + PAR_FREE(mesh->normals); + PAR_FREE(mesh->tcoords); + PAR_FREE(mesh); +} + +void par_shapes_export(par_shapes_mesh const* mesh, char const* filename) +{ + FILE* objfile = fopen(filename, "wt"); + float const* points = mesh->points; + float const* tcoords = mesh->tcoords; + float const* norms = mesh->normals; + PAR_SHAPES_T const* indices = mesh->triangles; + if (tcoords && norms) { + for (int nvert = 0; nvert < mesh->npoints; nvert++) { + fprintf(objfile, "v %f %f %f\n", points[0], points[1], points[2]); + fprintf(objfile, "vt %f %f\n", tcoords[0], tcoords[1]); + fprintf(objfile, "vn %f %f %f\n", norms[0], norms[1], norms[2]); + points += 3; + norms += 3; + tcoords += 2; + } + for (int nface = 0; nface < mesh->ntriangles; nface++) { + int a = 1 + *indices++; + int b = 1 + *indices++; + int c = 1 + *indices++; + fprintf(objfile, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", + a, a, a, b, b, b, c, c, c); + } + } else if (norms) { + for (int nvert = 0; nvert < mesh->npoints; nvert++) { + fprintf(objfile, "v %f %f %f\n", points[0], points[1], points[2]); + fprintf(objfile, "vn %f %f %f\n", norms[0], norms[1], norms[2]); + points += 3; + norms += 3; + } + for (int nface = 0; nface < mesh->ntriangles; nface++) { + int a = 1 + *indices++; + int b = 1 + *indices++; + int c = 1 + *indices++; + fprintf(objfile, "f %d//%d %d//%d %d//%d\n", a, a, b, b, c, c); + } + } else if (tcoords) { + for (int nvert = 0; nvert < mesh->npoints; nvert++) { + fprintf(objfile, "v %f %f %f\n", points[0], points[1], points[2]); + fprintf(objfile, "vt %f %f\n", tcoords[0], tcoords[1]); + points += 3; + tcoords += 2; + } + for (int nface = 0; nface < mesh->ntriangles; nface++) { + int a = 1 + *indices++; + int b = 1 + *indices++; + int c = 1 + *indices++; + fprintf(objfile, "f %d/%d %d/%d %d/%d\n", a, a, b, b, c, c); + } + } else { + for (int nvert = 0; nvert < mesh->npoints; nvert++) { + fprintf(objfile, "v %f %f %f\n", points[0], points[1], points[2]); + points += 3; + } + for (int nface = 0; nface < mesh->ntriangles; nface++) { + int a = 1 + *indices++; + int b = 1 + *indices++; + int c = 1 + *indices++; + fprintf(objfile, "f %d %d %d\n", a, b, c); + } + } + fclose(objfile); +} + +static void par_shapes__sphere(float const* uv, float* xyz, void* userdata) +{ + float phi = uv[0] * PAR_PI; + float theta = uv[1] * 2 * PAR_PI; + xyz[0] = cosf(theta) * sinf(phi); + xyz[1] = sinf(theta) * sinf(phi); + xyz[2] = cosf(phi); +} + +static void par_shapes__hemisphere(float const* uv, float* xyz, void* userdata) +{ + float phi = uv[0] * PAR_PI; + float theta = uv[1] * PAR_PI; + xyz[0] = cosf(theta) * sinf(phi); + xyz[1] = sinf(theta) * sinf(phi); + xyz[2] = cosf(phi); +} + +static void par_shapes__plane(float const* uv, float* xyz, void* userdata) +{ + xyz[0] = uv[0]; + xyz[1] = uv[1]; + xyz[2] = 0; +} + +static void par_shapes__klein(float const* uv, float* xyz, void* userdata) +{ + float u = uv[0] * PAR_PI; + float v = uv[1] * 2 * PAR_PI; + u = u * 2; + if (u < PAR_PI) { + xyz[0] = 3 * cosf(u) * (1 + sinf(u)) + (2 * (1 - cosf(u) / 2)) * + cosf(u) * cosf(v); + xyz[2] = -8 * sinf(u) - 2 * (1 - cosf(u) / 2) * sinf(u) * cosf(v); + } else { + xyz[0] = 3 * cosf(u) * (1 + sinf(u)) + (2 * (1 - cosf(u) / 2)) * + cosf(v + PAR_PI); + xyz[2] = -8 * sinf(u); + } + xyz[1] = -2 * (1 - cosf(u) / 2) * sinf(v); +} + +static void par_shapes__cylinder(float const* uv, float* xyz, void* userdata) +{ + float theta = uv[1] * 2 * PAR_PI; + xyz[0] = sinf(theta); + xyz[1] = cosf(theta); + xyz[2] = uv[0]; +} + +static void par_shapes__cone(float const* uv, float* xyz, void* userdata) +{ + float r = 1.0f - uv[0]; + float theta = uv[1] * 2 * PAR_PI; + xyz[0] = r * sinf(theta); + xyz[1] = r * cosf(theta); + xyz[2] = uv[0]; +} + +static void par_shapes__torus(float const* uv, float* xyz, void* userdata) +{ + float major = 1; + float minor = *((float*) userdata); + float theta = uv[0] * 2 * PAR_PI; + float phi = uv[1] * 2 * PAR_PI; + float beta = major + minor * cosf(phi); + xyz[0] = cosf(theta) * beta; + xyz[1] = sinf(theta) * beta; + xyz[2] = sinf(phi) * minor; +} + +static void par_shapes__trefoil(float const* uv, float* xyz, void* userdata) +{ + float minor = *((float*) userdata); + const float a = 0.5f; + const float b = 0.3f; + const float c = 0.5f; + const float d = minor * 0.1f; + const float u = (1 - uv[0]) * 4 * PAR_PI; + const float v = uv[1] * 2 * PAR_PI; + const float r = a + b * cos(1.5f * u); + const float x = r * cos(u); + const float y = r * sin(u); + const float z = c * sin(1.5f * u); + float q[3]; + q[0] = + -1.5f * b * sin(1.5f * u) * cos(u) - (a + b * cos(1.5f * u)) * sin(u); + q[1] = + -1.5f * b * sin(1.5f * u) * sin(u) + (a + b * cos(1.5f * u)) * cos(u); + q[2] = 1.5f * c * cos(1.5f * u); + par_shapes__normalize3(q); + float qvn[3] = {q[1], -q[0], 0}; + par_shapes__normalize3(qvn); + float ww[3]; + par_shapes__cross3(ww, q, qvn); + xyz[0] = x + d * (qvn[0] * cos(v) + ww[0] * sin(v)); + xyz[1] = y + d * (qvn[1] * cos(v) + ww[1] * sin(v)); + xyz[2] = z + d * ww[2] * sin(v); +} + +void par_shapes_set_epsilon_welded_normals(float epsilon) { + par_shapes__epsilon_welded_normals = epsilon; +} + +void par_shapes_set_epsilon_degenerate_sphere(float epsilon) { + par_shapes__epsilon_degenerate_sphere = epsilon; +} + +void par_shapes_merge(par_shapes_mesh* dst, par_shapes_mesh const* src) +{ + PAR_SHAPES_T offset = dst->npoints; + int npoints = dst->npoints + src->npoints; + int vecsize = sizeof(float) * 3; + dst->points = PAR_REALLOC(float, dst->points, 3 * npoints); + memcpy(dst->points + 3 * dst->npoints, src->points, vecsize * src->npoints); + dst->npoints = npoints; + if (src->normals || dst->normals) { + dst->normals = PAR_REALLOC(float, dst->normals, 3 * npoints); + if (src->normals) { + memcpy(dst->normals + 3 * offset, src->normals, + vecsize * src->npoints); + } + } + if (src->tcoords || dst->tcoords) { + int uvsize = sizeof(float) * 2; + dst->tcoords = PAR_REALLOC(float, dst->tcoords, 2 * npoints); + if (src->tcoords) { + memcpy(dst->tcoords + 2 * offset, src->tcoords, + uvsize * src->npoints); + } + } + int ntriangles = dst->ntriangles + src->ntriangles; + dst->triangles = PAR_REALLOC(PAR_SHAPES_T, dst->triangles, 3 * ntriangles); + PAR_SHAPES_T* ptriangles = dst->triangles + 3 * dst->ntriangles; + PAR_SHAPES_T const* striangles = src->triangles; + for (int i = 0; i < src->ntriangles; i++) { + *ptriangles++ = offset + *striangles++; + *ptriangles++ = offset + *striangles++; + *ptriangles++ = offset + *striangles++; + } + dst->ntriangles = ntriangles; +} + +par_shapes_mesh* par_shapes_create_disk(float radius, int slices, + float const* center, float const* normal) +{ + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + mesh->npoints = slices + 1; + mesh->points = PAR_MALLOC(float, 3 * mesh->npoints); + float* points = mesh->points; + *points++ = 0; + *points++ = 0; + *points++ = 0; + for (int i = 0; i < slices; i++) { + float theta = i * PAR_PI * 2 / slices; + *points++ = radius * cos(theta); + *points++ = radius * sin(theta); + *points++ = 0; + } + float nnormal[3] = {normal[0], normal[1], normal[2]}; + par_shapes__normalize3(nnormal); + mesh->normals = PAR_MALLOC(float, 3 * mesh->npoints); + float* norms = mesh->normals; + for (int i = 0; i < mesh->npoints; i++) { + *norms++ = nnormal[0]; + *norms++ = nnormal[1]; + *norms++ = nnormal[2]; + } + mesh->ntriangles = slices; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, 3 * mesh->ntriangles); + PAR_SHAPES_T* triangles = mesh->triangles; + for (int i = 0; i < slices; i++) { + *triangles++ = 0; + *triangles++ = 1 + i; + *triangles++ = 1 + (i + 1) % slices; + } + float k[3] = {0, 0, -1}; + float axis[3]; + par_shapes__cross3(axis, nnormal, k); + par_shapes__normalize3(axis); + par_shapes_rotate(mesh, acos(nnormal[2]), axis); + par_shapes_translate(mesh, center[0], center[1], center[2]); + return mesh; +} + +par_shapes_mesh* par_shapes_create_empty() +{ + return PAR_CALLOC(par_shapes_mesh, 1); +} + +void par_shapes_translate(par_shapes_mesh* m, float x, float y, float z) +{ + float* points = m->points; + for (int i = 0; i < m->npoints; i++) { + *points++ += x; + *points++ += y; + *points++ += z; + } +} + +void par_shapes_rotate(par_shapes_mesh* mesh, float radians, float const* axis) +{ + float s = sinf(radians); + float c = cosf(radians); + float x = axis[0]; + float y = axis[1]; + float z = axis[2]; + float xy = x * y; + float yz = y * z; + float zx = z * x; + float oneMinusC = 1.0f - c; + float col0[3] = { + (((x * x) * oneMinusC) + c), + ((xy * oneMinusC) + (z * s)), ((zx * oneMinusC) - (y * s)) + }; + float col1[3] = { + ((xy * oneMinusC) - (z * s)), + (((y * y) * oneMinusC) + c), ((yz * oneMinusC) + (x * s)) + }; + float col2[3] = { + ((zx * oneMinusC) + (y * s)), + ((yz * oneMinusC) - (x * s)), (((z * z) * oneMinusC) + c) + }; + float* p = mesh->points; + for (int i = 0; i < mesh->npoints; i++, p += 3) { + float x = col0[0] * p[0] + col1[0] * p[1] + col2[0] * p[2]; + float y = col0[1] * p[0] + col1[1] * p[1] + col2[1] * p[2]; + float z = col0[2] * p[0] + col1[2] * p[1] + col2[2] * p[2]; + p[0] = x; + p[1] = y; + p[2] = z; + } + float* n = mesh->normals; + if (n) { + for (int i = 0; i < mesh->npoints; i++, n += 3) { + float x = col0[0] * n[0] + col1[0] * n[1] + col2[0] * n[2]; + float y = col0[1] * n[0] + col1[1] * n[1] + col2[1] * n[2]; + float z = col0[2] * n[0] + col1[2] * n[1] + col2[2] * n[2]; + n[0] = x; + n[1] = y; + n[2] = z; + } + } +} + +void par_shapes_scale(par_shapes_mesh* m, float x, float y, float z) +{ + float* points = m->points; + for (int i = 0; i < m->npoints; i++) { + *points++ *= x; + *points++ *= y; + *points++ *= z; + } + float* n = m->normals; + if (n && !(x == y && y == z)) { + bool x_zero = x == 0; + bool y_zero = y == 0; + bool z_zero = z == 0; + if (!x_zero && !y_zero && !z_zero) { + x = 1.0f / x; + y = 1.0f / y; + z = 1.0f / z; + } else { + x = x_zero && !y_zero && !z_zero; + y = y_zero && !x_zero && !z_zero; + z = z_zero && !x_zero && !y_zero; + } + for (int i = 0; i < m->npoints; i++, n += 3) { + n[0] *= x; + n[1] *= y; + n[2] *= z; + par_shapes__normalize3(n); + } + } +} + +void par_shapes_merge_and_free(par_shapes_mesh* dst, par_shapes_mesh* src) +{ + par_shapes_merge(dst, src); + par_shapes_free_mesh(src); +} + +void par_shapes_compute_aabb(par_shapes_mesh const* m, float* aabb) +{ + float* points = m->points; + aabb[0] = aabb[3] = points[0]; + aabb[1] = aabb[4] = points[1]; + aabb[2] = aabb[5] = points[2]; + points += 3; + for (int i = 1; i < m->npoints; i++, points += 3) { + aabb[0] = PAR_MIN(points[0], aabb[0]); + aabb[1] = PAR_MIN(points[1], aabb[1]); + aabb[2] = PAR_MIN(points[2], aabb[2]); + aabb[3] = PAR_MAX(points[0], aabb[3]); + aabb[4] = PAR_MAX(points[1], aabb[4]); + aabb[5] = PAR_MAX(points[2], aabb[5]); + } +} + +void par_shapes_invert(par_shapes_mesh* m, int face, int nfaces) +{ + nfaces = nfaces ? nfaces : m->ntriangles; + PAR_SHAPES_T* tri = m->triangles + face * 3; + for (int i = 0; i < nfaces; i++) { + PAR_SWAP(PAR_SHAPES_T, tri[0], tri[2]); + tri += 3; + } +} + +par_shapes_mesh* par_shapes_create_icosahedron() +{ + static float verts[] = { + 0.000, 0.000, 1.000, + 0.894, 0.000, 0.447, + 0.276, 0.851, 0.447, + -0.724, 0.526, 0.447, + -0.724, -0.526, 0.447, + 0.276, -0.851, 0.447, + 0.724, 0.526, -0.447, + -0.276, 0.851, -0.447, + -0.894, 0.000, -0.447, + -0.276, -0.851, -0.447, + 0.724, -0.526, -0.447, + 0.000, 0.000, -1.000 + }; + static PAR_SHAPES_T faces[] = { + 0,1,2, + 0,2,3, + 0,3,4, + 0,4,5, + 0,5,1, + 7,6,11, + 8,7,11, + 9,8,11, + 10,9,11, + 6,10,11, + 6,2,1, + 7,3,2, + 8,4,3, + 9,5,4, + 10,1,5, + 6,7,2, + 7,8,3, + 8,9,4, + 9,10,5, + 10,6,1 + }; + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + mesh->npoints = sizeof(verts) / sizeof(verts[0]) / 3; + mesh->points = PAR_MALLOC(float, sizeof(verts) / 4); + memcpy(mesh->points, verts, sizeof(verts)); + mesh->ntriangles = sizeof(faces) / sizeof(faces[0]) / 3; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, sizeof(faces) / 2); + memcpy(mesh->triangles, faces, sizeof(faces)); + return mesh; +} + +par_shapes_mesh* par_shapes_create_dodecahedron() +{ + static float verts[20 * 3] = { + 0.607, 0.000, 0.795, + 0.188, 0.577, 0.795, + -0.491, 0.357, 0.795, + -0.491, -0.357, 0.795, + 0.188, -0.577, 0.795, + 0.982, 0.000, 0.188, + 0.304, 0.934, 0.188, + -0.795, 0.577, 0.188, + -0.795, -0.577, 0.188, + 0.304, -0.934, 0.188, + 0.795, 0.577, -0.188, + -0.304, 0.934, -0.188, + -0.982, 0.000, -0.188, + -0.304, -0.934, -0.188, + 0.795, -0.577, -0.188, + 0.491, 0.357, -0.795, + -0.188, 0.577, -0.795, + -0.607, 0.000, -0.795, + -0.188, -0.577, -0.795, + 0.491, -0.357, -0.795, + }; + static PAR_SHAPES_T pentagons[12 * 5] = { + 0,1,2,3,4, + 5,10,6,1,0, + 6,11,7,2,1, + 7,12,8,3,2, + 8,13,9,4,3, + 9,14,5,0,4, + 15,16,11,6,10, + 16,17,12,7,11, + 17,18,13,8,12, + 18,19,14,9,13, + 19,15,10,5,14, + 19,18,17,16,15 + }; + int npentagons = sizeof(pentagons) / sizeof(pentagons[0]) / 5; + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + int ncorners = sizeof(verts) / sizeof(verts[0]) / 3; + mesh->npoints = ncorners; + mesh->points = PAR_MALLOC(float, mesh->npoints * 3); + memcpy(mesh->points, verts, sizeof(verts)); + PAR_SHAPES_T const* pentagon = pentagons; + mesh->ntriangles = npentagons * 3; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* tris = mesh->triangles; + for (int p = 0; p < npentagons; p++, pentagon += 5) { + *tris++ = pentagon[0]; + *tris++ = pentagon[1]; + *tris++ = pentagon[2]; + *tris++ = pentagon[0]; + *tris++ = pentagon[2]; + *tris++ = pentagon[3]; + *tris++ = pentagon[0]; + *tris++ = pentagon[3]; + *tris++ = pentagon[4]; + } + return mesh; +} + +par_shapes_mesh* par_shapes_create_octahedron() +{ + static float verts[6 * 3] = { + 0.000, 0.000, 1.000, + 1.000, 0.000, 0.000, + 0.000, 1.000, 0.000, + -1.000, 0.000, 0.000, + 0.000, -1.000, 0.000, + 0.000, 0.000, -1.000 + }; + static PAR_SHAPES_T triangles[8 * 3] = { + 0,1,2, + 0,2,3, + 0,3,4, + 0,4,1, + 2,1,5, + 3,2,5, + 4,3,5, + 1,4,5, + }; + int ntris = sizeof(triangles) / sizeof(triangles[0]) / 3; + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + int ncorners = sizeof(verts) / sizeof(verts[0]) / 3; + mesh->npoints = ncorners; + mesh->points = PAR_MALLOC(float, mesh->npoints * 3); + memcpy(mesh->points, verts, sizeof(verts)); + PAR_SHAPES_T const* triangle = triangles; + mesh->ntriangles = ntris; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* tris = mesh->triangles; + for (int p = 0; p < ntris; p++) { + *tris++ = *triangle++; + *tris++ = *triangle++; + *tris++ = *triangle++; + } + return mesh; +} + +par_shapes_mesh* par_shapes_create_tetrahedron() +{ + static float verts[4 * 3] = { + 0.000, 1.333, 0, + 0.943, 0, 0, + -0.471, 0, 0.816, + -0.471, 0, -0.816, + }; + static PAR_SHAPES_T triangles[4 * 3] = { + 2,1,0, + 3,2,0, + 1,3,0, + 1,2,3, + }; + int ntris = sizeof(triangles) / sizeof(triangles[0]) / 3; + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + int ncorners = sizeof(verts) / sizeof(verts[0]) / 3; + mesh->npoints = ncorners; + mesh->points = PAR_MALLOC(float, mesh->npoints * 3); + memcpy(mesh->points, verts, sizeof(verts)); + PAR_SHAPES_T const* triangle = triangles; + mesh->ntriangles = ntris; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* tris = mesh->triangles; + for (int p = 0; p < ntris; p++) { + *tris++ = *triangle++; + *tris++ = *triangle++; + *tris++ = *triangle++; + } + return mesh; +} + +par_shapes_mesh* par_shapes_create_cube() +{ + static float verts[8 * 3] = { + 0, 0, 0, // 0 + 0, 1, 0, // 1 + 1, 1, 0, // 2 + 1, 0, 0, // 3 + 0, 0, 1, // 4 + 0, 1, 1, // 5 + 1, 1, 1, // 6 + 1, 0, 1, // 7 + }; + static PAR_SHAPES_T quads[6 * 4] = { + 7,6,5,4, // front + 0,1,2,3, // back + 6,7,3,2, // right + 5,6,2,1, // top + 4,5,1,0, // left + 7,4,0,3, // bottom + }; + int nquads = sizeof(quads) / sizeof(quads[0]) / 4; + par_shapes_mesh* mesh = PAR_CALLOC(par_shapes_mesh, 1); + int ncorners = sizeof(verts) / sizeof(verts[0]) / 3; + mesh->npoints = ncorners; + mesh->points = PAR_MALLOC(float, mesh->npoints * 3); + memcpy(mesh->points, verts, sizeof(verts)); + PAR_SHAPES_T const* quad = quads; + mesh->ntriangles = nquads * 2; + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* tris = mesh->triangles; + for (int p = 0; p < nquads; p++, quad += 4) { + *tris++ = quad[0]; + *tris++ = quad[1]; + *tris++ = quad[2]; + *tris++ = quad[2]; + *tris++ = quad[3]; + *tris++ = quad[0]; + } + return mesh; +} + +typedef struct { + char* cmd; + char* arg; +} par_shapes__command; + +typedef struct { + char const* name; + int weight; + int ncommands; + par_shapes__command* commands; +} par_shapes__rule; + +typedef struct { + int pc; + float position[3]; + float scale[3]; + par_shapes_mesh* orientation; + par_shapes__rule* rule; +} par_shapes__stackframe; + +static par_shapes__rule* par_shapes__pick_rule(const char* name, + par_shapes__rule* rules, int nrules) +{ + par_shapes__rule* rule = 0; + int total = 0; + for (int i = 0; i < nrules; i++) { + rule = rules + i; + if (!strcmp(rule->name, name)) { + total += rule->weight; + } + } + float r = (float) rand() / RAND_MAX; + float t = 0; + for (int i = 0; i < nrules; i++) { + rule = rules + i; + if (!strcmp(rule->name, name)) { + t += (float) rule->weight / total; + if (t >= r) { + return rule; + } + } + } + return rule; +} + +static par_shapes_mesh* par_shapes__create_turtle() +{ + const float xaxis[] = {1, 0, 0}; + const float yaxis[] = {0, 1, 0}; + const float zaxis[] = {0, 0, 1}; + par_shapes_mesh* turtle = PAR_CALLOC(par_shapes_mesh, 1); + turtle->npoints = 3; + turtle->points = PAR_CALLOC(float, turtle->npoints * 3); + par_shapes__copy3(turtle->points + 0, xaxis); + par_shapes__copy3(turtle->points + 3, yaxis); + par_shapes__copy3(turtle->points + 6, zaxis); + return turtle; +} + +static par_shapes_mesh* par_shapes__apply_turtle(par_shapes_mesh* mesh, + par_shapes_mesh* turtle, float const* pos, float const* scale) +{ + par_shapes_mesh* m = par_shapes_clone(mesh, 0); + for (int p = 0; p < m->npoints; p++) { + float* pt = m->points + p * 3; + pt[0] *= scale[0]; + pt[1] *= scale[1]; + pt[2] *= scale[2]; + par_shapes__transform3(pt, + turtle->points + 0, turtle->points + 3, turtle->points + 6); + pt[0] += pos[0]; + pt[1] += pos[1]; + pt[2] += pos[2]; + } + return m; +} + +void par_shapes__connect(par_shapes_mesh* scene, par_shapes_mesh* cylinder, + int slices) +{ + int stacks = 1; + int npoints = (slices + 1) * (stacks + 1); + assert(scene->npoints >= npoints && "Cannot connect to empty scene."); + + // Create the new point list. + npoints = scene->npoints + (slices + 1); + float* points = PAR_MALLOC(float, npoints * 3); + memcpy(points, scene->points, sizeof(float) * scene->npoints * 3); + float* newpts = points + scene->npoints * 3; + memcpy(newpts, cylinder->points + (slices + 1) * 3, + sizeof(float) * (slices + 1) * 3); + PAR_FREE(scene->points); + scene->points = points; + + // Create the new triangle list. + int ntriangles = scene->ntriangles + 2 * slices * stacks; + PAR_SHAPES_T* triangles = PAR_MALLOC(PAR_SHAPES_T, ntriangles * 3); + memcpy(triangles, scene->triangles, + sizeof(PAR_SHAPES_T) * scene->ntriangles * 3); + int v = scene->npoints - (slices + 1); + PAR_SHAPES_T* face = triangles + scene->ntriangles * 3; + for (int stack = 0; stack < stacks; stack++) { + for (int slice = 0; slice < slices; slice++) { + int next = slice + 1; + *face++ = v + slice + slices + 1; + *face++ = v + next; + *face++ = v + slice; + *face++ = v + slice + slices + 1; + *face++ = v + next + slices + 1; + *face++ = v + next; + } + v += slices + 1; + } + PAR_FREE(scene->triangles); + scene->triangles = triangles; + + scene->npoints = npoints; + scene->ntriangles = ntriangles; +} + +par_shapes_mesh* par_shapes_create_lsystem(char const* text, int slices, + int maxdepth) +{ + char* program; + program = PAR_MALLOC(char, strlen(text) + 1); + + // The first pass counts the number of rules and commands. + strcpy(program, text); + char *cmd = strtok(program, " "); + int nrules = 1; + int ncommands = 0; + while (cmd) { + char *arg = strtok(0, " "); + if (!arg) { + puts("lsystem error: unexpected end of program."); + break; + } + if (!strcmp(cmd, "rule")) { + nrules++; + } else { + ncommands++; + } + cmd = strtok(0, " "); + } + + // Allocate space. + par_shapes__rule* rules = PAR_MALLOC(par_shapes__rule, nrules); + par_shapes__command* commands = PAR_MALLOC(par_shapes__command, ncommands); + + // Initialize the entry rule. + par_shapes__rule* current_rule = &rules[0]; + par_shapes__command* current_command = &commands[0]; + current_rule->name = "entry"; + current_rule->weight = 1; + current_rule->ncommands = 0; + current_rule->commands = current_command; + + // The second pass fills in the structures. + strcpy(program, text); + cmd = strtok(program, " "); + while (cmd) { + char *arg = strtok(0, " "); + if (!strcmp(cmd, "rule")) { + current_rule++; + + // Split the argument into a rule name and weight. + char* dot = strchr(arg, '.'); + if (dot) { + current_rule->weight = atoi(dot + 1); + *dot = 0; + } else { + current_rule->weight = 1; + } + + current_rule->name = arg; + current_rule->ncommands = 0; + current_rule->commands = current_command; + } else { + current_rule->ncommands++; + current_command->cmd = cmd; + current_command->arg = arg; + current_command++; + } + cmd = strtok(0, " "); + } + + // For testing purposes, dump out the parsed program. + #ifdef TEST_PARSE + for (int i = 0; i < nrules; i++) { + par_shapes__rule rule = rules[i]; + printf("rule %s.%d\n", rule.name, rule.weight); + for (int c = 0; c < rule.ncommands; c++) { + par_shapes__command cmd = rule.commands[c]; + printf("\t%s %s\n", cmd.cmd, cmd.arg); + } + } + #endif + + // Instantiate the aggregated shape and the template shapes. + par_shapes_mesh* scene = PAR_CALLOC(par_shapes_mesh, 1); + par_shapes_mesh* tube = par_shapes_create_cylinder(slices, 1); + par_shapes_mesh* turtle = par_shapes__create_turtle(); + + // We're not attempting to support texture coordinates and normals + // with L-systems, so remove them from the template shape. + PAR_FREE(tube->normals); + PAR_FREE(tube->tcoords); + tube->normals = 0; + tube->tcoords = 0; + + const float xaxis[] = {1, 0, 0}; + const float yaxis[] = {0, 1, 0}; + const float zaxis[] = {0, 0, 1}; + const float units[] = {1, 1, 1}; + + // Execute the L-system program until the stack size is 0. + par_shapes__stackframe* stack = + PAR_CALLOC(par_shapes__stackframe, maxdepth); + int stackptr = 0; + stack[0].orientation = turtle; + stack[0].rule = &rules[0]; + par_shapes__copy3(stack[0].scale, units); + while (stackptr >= 0) { + par_shapes__stackframe* frame = &stack[stackptr]; + par_shapes__rule* rule = frame->rule; + par_shapes_mesh* turtle = frame->orientation; + float* position = frame->position; + float* scale = frame->scale; + if (frame->pc >= rule->ncommands) { + par_shapes_free_mesh(turtle); + stackptr--; + continue; + } + + par_shapes__command* cmd = rule->commands + (frame->pc++); + #ifdef DUMP_TRACE + printf("%5s %5s %5s:%d %03d\n", cmd->cmd, cmd->arg, rule->name, + frame->pc - 1, stackptr); + #endif + + float value; + if (!strcmp(cmd->cmd, "shape")) { + par_shapes_mesh* m = par_shapes__apply_turtle(tube, turtle, + position, scale); + if (!strcmp(cmd->arg, "connect")) { + par_shapes__connect(scene, m, slices); + } else { + par_shapes_merge(scene, m); + } + par_shapes_free_mesh(m); + } else if (!strcmp(cmd->cmd, "call") && stackptr < maxdepth - 1) { + rule = par_shapes__pick_rule(cmd->arg, rules, nrules); + frame = &stack[++stackptr]; + frame->rule = rule; + frame->orientation = par_shapes_clone(turtle, 0); + frame->pc = 0; + par_shapes__copy3(frame->scale, scale); + par_shapes__copy3(frame->position, position); + continue; + } else { + value = atof(cmd->arg); + if (!strcmp(cmd->cmd, "rx")) { + par_shapes_rotate(turtle, value * PAR_PI / 180.0, xaxis); + } else if (!strcmp(cmd->cmd, "ry")) { + par_shapes_rotate(turtle, value * PAR_PI / 180.0, yaxis); + } else if (!strcmp(cmd->cmd, "rz")) { + par_shapes_rotate(turtle, value * PAR_PI / 180.0, zaxis); + } else if (!strcmp(cmd->cmd, "tx")) { + float vec[3] = {value, 0, 0}; + float t[3] = { + par_shapes__dot3(turtle->points + 0, vec), + par_shapes__dot3(turtle->points + 3, vec), + par_shapes__dot3(turtle->points + 6, vec) + }; + par_shapes__add3(position, t); + } else if (!strcmp(cmd->cmd, "ty")) { + float vec[3] = {0, value, 0}; + float t[3] = { + par_shapes__dot3(turtle->points + 0, vec), + par_shapes__dot3(turtle->points + 3, vec), + par_shapes__dot3(turtle->points + 6, vec) + }; + par_shapes__add3(position, t); + } else if (!strcmp(cmd->cmd, "tz")) { + float vec[3] = {0, 0, value}; + float t[3] = { + par_shapes__dot3(turtle->points + 0, vec), + par_shapes__dot3(turtle->points + 3, vec), + par_shapes__dot3(turtle->points + 6, vec) + }; + par_shapes__add3(position, t); + } else if (!strcmp(cmd->cmd, "sx")) { + scale[0] *= value; + } else if (!strcmp(cmd->cmd, "sy")) { + scale[1] *= value; + } else if (!strcmp(cmd->cmd, "sz")) { + scale[2] *= value; + } else if (!strcmp(cmd->cmd, "sa")) { + scale[0] *= value; + scale[1] *= value; + scale[2] *= value; + } + } + } + par_shapes_free_mesh(tube); + PAR_FREE(stack); + PAR_FREE(program); + PAR_FREE(rules); + PAR_FREE(commands); + return scene; +} + +void par_shapes_unweld(par_shapes_mesh* mesh, bool create_indices) +{ + int npoints = mesh->ntriangles * 3; + float* points = PAR_MALLOC(float, 3 * npoints); + float* dst = points; + PAR_SHAPES_T const* index = mesh->triangles; + for (int i = 0; i < npoints; i++) { + float const* src = mesh->points + 3 * (*index++); + *dst++ = src[0]; + *dst++ = src[1]; + *dst++ = src[2]; + } + PAR_FREE(mesh->points); + mesh->points = points; + mesh->npoints = npoints; + if (create_indices) { + PAR_SHAPES_T* tris = PAR_MALLOC(PAR_SHAPES_T, 3 * mesh->ntriangles); + PAR_SHAPES_T* index = tris; + for (int i = 0; i < mesh->ntriangles * 3; i++) { + *index++ = i; + } + PAR_FREE(mesh->triangles); + mesh->triangles = tris; + } +} + +void par_shapes_compute_normals(par_shapes_mesh* m) +{ + PAR_FREE(m->normals); + m->normals = PAR_CALLOC(float, m->npoints * 3); + PAR_SHAPES_T const* triangle = m->triangles; + float next[3], prev[3], cp[3]; + for (int f = 0; f < m->ntriangles; f++, triangle += 3) { + float const* pa = m->points + 3 * triangle[0]; + float const* pb = m->points + 3 * triangle[1]; + float const* pc = m->points + 3 * triangle[2]; + par_shapes__copy3(next, pb); + par_shapes__subtract3(next, pa); + par_shapes__copy3(prev, pc); + par_shapes__subtract3(prev, pa); + par_shapes__cross3(cp, next, prev); + par_shapes__add3(m->normals + 3 * triangle[0], cp); + par_shapes__copy3(next, pc); + par_shapes__subtract3(next, pb); + par_shapes__copy3(prev, pa); + par_shapes__subtract3(prev, pb); + par_shapes__cross3(cp, next, prev); + par_shapes__add3(m->normals + 3 * triangle[1], cp); + par_shapes__copy3(next, pa); + par_shapes__subtract3(next, pc); + par_shapes__copy3(prev, pb); + par_shapes__subtract3(prev, pc); + par_shapes__cross3(cp, next, prev); + par_shapes__add3(m->normals + 3 * triangle[2], cp); + } + float* normal = m->normals; + for (int p = 0; p < m->npoints; p++, normal += 3) { + par_shapes__normalize3(normal); + } +} + +static void par_shapes__subdivide(par_shapes_mesh* mesh) +{ + assert(mesh->npoints == mesh->ntriangles * 3 && "Must be unwelded."); + int ntriangles = mesh->ntriangles * 4; + int npoints = ntriangles * 3; + float* points = PAR_CALLOC(float, npoints * 3); + float* dpoint = points; + float const* spoint = mesh->points; + for (int t = 0; t < mesh->ntriangles; t++, spoint += 9, dpoint += 3) { + float const* a = spoint; + float const* b = spoint + 3; + float const* c = spoint + 6; + float const* p0 = dpoint; + float const* p1 = dpoint + 3; + float const* p2 = dpoint + 6; + par_shapes__mix3(dpoint, a, b, 0.5); + par_shapes__mix3(dpoint += 3, b, c, 0.5); + par_shapes__mix3(dpoint += 3, a, c, 0.5); + par_shapes__add3(dpoint += 3, a); + par_shapes__add3(dpoint += 3, p0); + par_shapes__add3(dpoint += 3, p2); + par_shapes__add3(dpoint += 3, p0); + par_shapes__add3(dpoint += 3, b); + par_shapes__add3(dpoint += 3, p1); + par_shapes__add3(dpoint += 3, p2); + par_shapes__add3(dpoint += 3, p1); + par_shapes__add3(dpoint += 3, c); + } + PAR_FREE(mesh->points); + mesh->points = points; + mesh->npoints = npoints; + mesh->ntriangles = ntriangles; +} + +par_shapes_mesh* par_shapes_create_subdivided_sphere(int nsubd) +{ + par_shapes_mesh* mesh = par_shapes_create_icosahedron(); + par_shapes_unweld(mesh, false); + PAR_FREE(mesh->triangles); + mesh->triangles = 0; + while (nsubd--) { + par_shapes__subdivide(mesh); + } + for (int i = 0; i < mesh->npoints; i++) { + par_shapes__normalize3(mesh->points + i * 3); + } + mesh->triangles = PAR_MALLOC(PAR_SHAPES_T, 3 * mesh->ntriangles); + for (int i = 0; i < mesh->ntriangles * 3; i++) { + mesh->triangles[i] = i; + } + par_shapes_mesh* tmp = mesh; + mesh = par_shapes_weld(mesh, 0.01, 0); + par_shapes_free_mesh(tmp); + par_shapes_compute_normals(mesh); + return mesh; +} + +par_shapes_mesh* par_shapes_create_rock(int seed, int subd) +{ + par_shapes_mesh* mesh = par_shapes_create_subdivided_sphere(subd); + struct osn_context* ctx; + par__simplex_noise(seed, &ctx); + for (int p = 0; p < mesh->npoints; p++) { + float* pt = mesh->points + p * 3; + float a = 0.25, f = 1.0; + double n = a * par__simplex_noise2(ctx, f * pt[0], f * pt[2]); + a *= 0.5; f *= 2; + n += a * par__simplex_noise2(ctx, f * pt[0], f * pt[2]); + pt[0] *= 1 + 2 * n; + pt[1] *= 1 + n; + pt[2] *= 1 + 2 * n; + if (pt[1] < 0) { + pt[1] = -pow(-pt[1], 0.5) / 2; + } + } + par__simplex_noise_free(ctx); + par_shapes_compute_normals(mesh); + return mesh; +} + +par_shapes_mesh* par_shapes_clone(par_shapes_mesh const* mesh, + par_shapes_mesh* clone) +{ + if (!clone) { + clone = PAR_CALLOC(par_shapes_mesh, 1); + } + clone->npoints = mesh->npoints; + clone->points = PAR_REALLOC(float, clone->points, 3 * clone->npoints); + memcpy(clone->points, mesh->points, sizeof(float) * 3 * clone->npoints); + clone->ntriangles = mesh->ntriangles; + clone->triangles = PAR_REALLOC(PAR_SHAPES_T, clone->triangles, 3 * + clone->ntriangles); + memcpy(clone->triangles, mesh->triangles, + sizeof(PAR_SHAPES_T) * 3 * clone->ntriangles); + if (mesh->normals) { + clone->normals = PAR_REALLOC(float, clone->normals, 3 * clone->npoints); + memcpy(clone->normals, mesh->normals, + sizeof(float) * 3 * clone->npoints); + } + if (mesh->tcoords) { + clone->tcoords = PAR_REALLOC(float, clone->tcoords, 2 * clone->npoints); + memcpy(clone->tcoords, mesh->tcoords, + sizeof(float) * 2 * clone->npoints); + } + return clone; +} + +static struct { + float const* points; + int gridsize; +} par_shapes__sort_context; + +static int par_shapes__cmp1(const void *arg0, const void *arg1) +{ + const int g = par_shapes__sort_context.gridsize; + + // Convert arg0 into a flattened grid index. + PAR_SHAPES_T d0 = *(const PAR_SHAPES_T*) arg0; + float const* p0 = par_shapes__sort_context.points + d0 * 3; + int i0 = (int) p0[0]; + int j0 = (int) p0[1]; + int k0 = (int) p0[2]; + int index0 = i0 + g * j0 + g * g * k0; + + // Convert arg1 into a flattened grid index. + PAR_SHAPES_T d1 = *(const PAR_SHAPES_T*) arg1; + float const* p1 = par_shapes__sort_context.points + d1 * 3; + int i1 = (int) p1[0]; + int j1 = (int) p1[1]; + int k1 = (int) p1[2]; + int index1 = i1 + g * j1 + g * g * k1; + + // Return the ordering. + if (index0 < index1) return -1; + if (index0 > index1) return 1; + return 0; +} + +static void par_shapes__sort_points(par_shapes_mesh* mesh, int gridsize, + PAR_SHAPES_T* sortmap) +{ + // Run qsort over a list of consecutive integers that get deferenced + // within the comparator function; this creates a reorder mapping. + for (int i = 0; i < mesh->npoints; i++) { + sortmap[i] = i; + } + par_shapes__sort_context.gridsize = gridsize; + par_shapes__sort_context.points = mesh->points; + qsort(sortmap, mesh->npoints, sizeof(PAR_SHAPES_T), par_shapes__cmp1); + + // Apply the reorder mapping to the XYZ coordinate data. + float* newpts = PAR_MALLOC(float, mesh->npoints * 3); + PAR_SHAPES_T* invmap = PAR_MALLOC(PAR_SHAPES_T, mesh->npoints); + float* dstpt = newpts; + for (int i = 0; i < mesh->npoints; i++) { + invmap[sortmap[i]] = i; + float const* srcpt = mesh->points + 3 * sortmap[i]; + *dstpt++ = *srcpt++; + *dstpt++ = *srcpt++; + *dstpt++ = *srcpt++; + } + PAR_FREE(mesh->points); + mesh->points = newpts; + + // Apply the inverse reorder mapping to the triangle indices. + PAR_SHAPES_T* newinds = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* dstind = newinds; + PAR_SHAPES_T const* srcind = mesh->triangles; + for (int i = 0; i < mesh->ntriangles * 3; i++) { + *dstind++ = invmap[*srcind++]; + } + PAR_FREE(mesh->triangles); + mesh->triangles = newinds; + + // Cleanup. + memcpy(sortmap, invmap, sizeof(PAR_SHAPES_T) * mesh->npoints); + PAR_FREE(invmap); +} + +static void par_shapes__weld_points(par_shapes_mesh* mesh, int gridsize, + float epsilon, PAR_SHAPES_T* weldmap) +{ + // Each bin contains a "pointer" (really an index) to its first point. + // We add 1 because 0 is reserved to mean that the bin is empty. + // Since the points are spatially sorted, there's no need to store + // a point count in each bin. + PAR_SHAPES_T* bins = PAR_CALLOC(PAR_SHAPES_T, + gridsize * gridsize * gridsize); + int prev_binindex = -1; + for (int p = 0; p < mesh->npoints; p++) { + float const* pt = mesh->points + p * 3; + int i = (int) pt[0]; + int j = (int) pt[1]; + int k = (int) pt[2]; + int this_binindex = i + gridsize * j + gridsize * gridsize * k; + if (this_binindex != prev_binindex) { + bins[this_binindex] = 1 + p; + } + prev_binindex = this_binindex; + } + + // Examine all bins that intersect the epsilon-sized cube centered at each + // point, and check for colocated points within those bins. + float const* pt = mesh->points; + int nremoved = 0; + for (int p = 0; p < mesh->npoints; p++, pt += 3) { + + // Skip if this point has already been welded. + if (weldmap[p] != p) { + continue; + } + + // Build a list of bins that intersect the epsilon-sized cube. + int nearby[8]; + int nbins = 0; + int minp[3], maxp[3]; + for (int c = 0; c < 3; c++) { + minp[c] = (int) (pt[c] - epsilon); + maxp[c] = (int) (pt[c] + epsilon); + } + for (int i = minp[0]; i <= maxp[0]; i++) { + for (int j = minp[1]; j <= maxp[1]; j++) { + for (int k = minp[2]; k <= maxp[2]; k++) { + int binindex = i + gridsize * j + gridsize * gridsize * k; + PAR_SHAPES_T binvalue = *(bins + binindex); + if (binvalue > 0) { + if (nbins == 8) { + printf("Epsilon value is too large.\n"); + break; + } + nearby[nbins++] = binindex; + } + } + } + } + + // Check for colocated points in each nearby bin. + for (int b = 0; b < nbins; b++) { + int binindex = nearby[b]; + PAR_SHAPES_T binvalue = bins[binindex]; + PAR_SHAPES_T nindex = binvalue - 1; + assert(nindex < mesh->npoints); + while (true) { + + // If this isn't "self" and it's colocated, then weld it! + if (nindex != p && weldmap[nindex] == nindex) { + float const* thatpt = mesh->points + nindex * 3; + float dist2 = par_shapes__sqrdist3(thatpt, pt); + if (dist2 < epsilon) { + weldmap[nindex] = p; + nremoved++; + } + } + + // Advance to the next point if possible. + if (++nindex >= mesh->npoints) { + break; + } + + // If the next point is outside the bin, then we're done. + float const* nextpt = mesh->points + nindex * 3; + int i = (int) nextpt[0]; + int j = (int) nextpt[1]; + int k = (int) nextpt[2]; + int nextbinindex = i + gridsize * j + gridsize * gridsize * k; + if (nextbinindex != binindex) { + break; + } + } + } + } + PAR_FREE(bins); + + // Apply the weldmap to the vertices. + int npoints = mesh->npoints - nremoved; + float* newpts = PAR_MALLOC(float, 3 * npoints); + float* dst = newpts; + PAR_SHAPES_T* condensed_map = PAR_MALLOC(PAR_SHAPES_T, mesh->npoints); + PAR_SHAPES_T* cmap = condensed_map; + float const* src = mesh->points; + int ci = 0; + for (int p = 0; p < mesh->npoints; p++, src += 3) { + if (weldmap[p] == p) { + *dst++ = src[0]; + *dst++ = src[1]; + *dst++ = src[2]; + *cmap++ = ci++; + } else { + *cmap++ = condensed_map[weldmap[p]]; + } + } + assert(ci == npoints); + PAR_FREE(mesh->points); + memcpy(weldmap, condensed_map, mesh->npoints * sizeof(PAR_SHAPES_T)); + PAR_FREE(condensed_map); + mesh->points = newpts; + mesh->npoints = npoints; + + // Apply the weldmap to the triangle indices and skip the degenerates. + PAR_SHAPES_T const* tsrc = mesh->triangles; + PAR_SHAPES_T* tdst = mesh->triangles; + int ntriangles = 0; + for (int i = 0; i < mesh->ntriangles; i++, tsrc += 3) { + PAR_SHAPES_T a = weldmap[tsrc[0]]; + PAR_SHAPES_T b = weldmap[tsrc[1]]; + PAR_SHAPES_T c = weldmap[tsrc[2]]; + if (a != b && a != c && b != c) { + assert(a < mesh->npoints); + assert(b < mesh->npoints); + assert(c < mesh->npoints); + *tdst++ = a; + *tdst++ = b; + *tdst++ = c; + ntriangles++; + } + } + mesh->ntriangles = ntriangles; +} + +par_shapes_mesh* par_shapes_weld(par_shapes_mesh const* mesh, float epsilon, + PAR_SHAPES_T* weldmap) +{ + par_shapes_mesh* clone = par_shapes_clone(mesh, 0); + float aabb[6]; + int gridsize = 20; + float maxcell = gridsize - 1; + par_shapes_compute_aabb(clone, aabb); + float scale[3] = { + aabb[3] == aabb[0] ? 1.0f : maxcell / (aabb[3] - aabb[0]), + aabb[4] == aabb[1] ? 1.0f : maxcell / (aabb[4] - aabb[1]), + aabb[5] == aabb[2] ? 1.0f : maxcell / (aabb[5] - aabb[2]), + }; + par_shapes_translate(clone, -aabb[0], -aabb[1], -aabb[2]); + par_shapes_scale(clone, scale[0], scale[1], scale[2]); + PAR_SHAPES_T* sortmap = PAR_MALLOC(PAR_SHAPES_T, mesh->npoints); + par_shapes__sort_points(clone, gridsize, sortmap); + bool owner = false; + if (!weldmap) { + owner = true; + weldmap = PAR_MALLOC(PAR_SHAPES_T, mesh->npoints); + } + for (int i = 0; i < mesh->npoints; i++) { + weldmap[i] = i; + } + par_shapes__weld_points(clone, gridsize, epsilon, weldmap); + if (owner) { + PAR_FREE(weldmap); + } else { + PAR_SHAPES_T* newmap = PAR_MALLOC(PAR_SHAPES_T, mesh->npoints); + for (int i = 0; i < mesh->npoints; i++) { + newmap[i] = weldmap[sortmap[i]]; + } + memcpy(weldmap, newmap, sizeof(PAR_SHAPES_T) * mesh->npoints); + PAR_FREE(newmap); + } + PAR_FREE(sortmap); + par_shapes_scale(clone, 1.0 / scale[0], 1.0 / scale[1], 1.0 / scale[2]); + par_shapes_translate(clone, aabb[0], aabb[1], aabb[2]); + return clone; +} + +// ----------------------------------------------------------------------------- +// BEGIN OPEN SIMPLEX NOISE +// ----------------------------------------------------------------------------- + +#define STRETCH_CONSTANT_2D (-0.211324865405187) // (1 / sqrt(2 + 1) - 1 ) / 2; +#define SQUISH_CONSTANT_2D (0.366025403784439) // (sqrt(2 + 1) -1) / 2; +#define STRETCH_CONSTANT_3D (-1.0 / 6.0) // (1 / sqrt(3 + 1) - 1) / 3; +#define SQUISH_CONSTANT_3D (1.0 / 3.0) // (sqrt(3+1)-1)/3; +#define STRETCH_CONSTANT_4D (-0.138196601125011) // (1 / sqrt(4 + 1) - 1) / 4; +#define SQUISH_CONSTANT_4D (0.309016994374947) // (sqrt(4 + 1) - 1) / 4; + +#define NORM_CONSTANT_2D (47.0) +#define NORM_CONSTANT_3D (103.0) +#define NORM_CONSTANT_4D (30.0) + +#define DEFAULT_SEED (0LL) + +struct osn_context { + int16_t* perm; + int16_t* permGradIndex3D; +}; + +#define ARRAYSIZE(x) (sizeof((x)) / sizeof((x)[0])) + +/* + * Gradients for 2D. They approximate the directions to the + * vertices of an octagon from the center. + */ +static const int8_t gradients2D[] = { + 5, 2, 2, 5, -5, 2, -2, 5, 5, -2, 2, -5, -5, -2, -2, -5, +}; + +/* + * Gradients for 3D. They approximate the directions to the + * vertices of a rhombicuboctahedron from the center, skewed so + * that the triangular and square facets can be inscribed inside + * circles of the same radius. + */ +static const signed char gradients3D[] = { + -11, 4, 4, -4, 11, 4, -4, 4, 11, 11, 4, 4, 4, 11, 4, 4, 4, 11, -11, -4, 4, + -4, -11, 4, -4, -4, 11, 11, -4, 4, 4, -11, 4, 4, -4, 11, -11, 4, -4, -4, 11, + -4, -4, 4, -11, 11, 4, -4, 4, 11, -4, 4, 4, -11, -11, -4, -4, -4, -11, -4, + -4, -4, -11, 11, -4, -4, 4, -11, -4, 4, -4, -11, +}; + +/* + * Gradients for 4D. They approximate the directions to the + * vertices of a disprismatotesseractihexadecachoron from the center, + * skewed so that the tetrahedral and cubic facets can be inscribed inside + * spheres of the same radius. + */ +static const signed char gradients4D[] = { + 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, -3, 1, 1, 1, -1, 3, 1, 1, + -1, 1, 3, 1, -1, 1, 1, 3, 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, 1, 1, -1, 1, + 3, -3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3, 1, -1, -1, 1, 3, 3, 1, -1, 1, 1, + 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, -3, 1, -1, 1, -1, 3, -1, 1, -1, 1, -3, + 1, -1, 1, -1, 3, 3, -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3, -3, + -1, -1, 1, -1, -3, -1, 1, -1, -1, -3, 1, -1, -1, -1, 3, 3, 1, 1, -1, 1, 3, + 1, -1, 1, 1, 3, -1, 1, 1, 1, -3, -3, 1, 1, -1, -1, 3, 1, -1, -1, 1, 3, -1, + -1, 1, 1, -3, 3, -1, 1, -1, 1, -3, 1, -1, 1, -1, 3, -1, 1, -1, 1, -3, -3, + -1, 1, -1, -1, -3, 1, -1, -1, -1, 3, -1, -1, -1, 1, -3, 3, 1, -1, -1, 1, 3, + -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, -3, 1, -1, -1, -1, 3, -1, -1, -1, 1, -3, + -1, -1, 1, -1, -3, 3, -1, -1, -1, 1, -3, -1, -1, 1, -1, -3, -1, 1, -1, -1, + -3, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3, +}; + +static double extrapolate2( + struct osn_context* ctx, int xsb, int ysb, double dx, double dy) +{ + int16_t* perm = ctx->perm; + int index = perm[(perm[xsb & 0xFF] + ysb) & 0xFF] & 0x0E; + return gradients2D[index] * dx + gradients2D[index + 1] * dy; +} + +static inline int fastFloor(double x) +{ + int xi = (int) x; + return x < xi ? xi - 1 : xi; +} + +static int allocate_perm(struct osn_context* ctx, int nperm, int ngrad) +{ + PAR_FREE(ctx->perm); + PAR_FREE(ctx->permGradIndex3D); + ctx->perm = PAR_MALLOC(int16_t, nperm); + if (!ctx->perm) { + return -ENOMEM; + } + ctx->permGradIndex3D = PAR_MALLOC(int16_t, ngrad); + if (!ctx->permGradIndex3D) { + PAR_FREE(ctx->perm); + return -ENOMEM; + } + return 0; +} + +static int par__simplex_noise(int64_t seed, struct osn_context** ctx) +{ + int rc; + int16_t source[256]; + int i; + int16_t* perm; + int16_t* permGradIndex3D; + *ctx = PAR_MALLOC(struct osn_context, 1); + if (!(*ctx)) { + return -ENOMEM; + } + (*ctx)->perm = NULL; + (*ctx)->permGradIndex3D = NULL; + rc = allocate_perm(*ctx, 256, 256); + if (rc) { + PAR_FREE(*ctx); + return rc; + } + perm = (*ctx)->perm; + permGradIndex3D = (*ctx)->permGradIndex3D; + for (i = 0; i < 256; i++) { + source[i] = (int16_t) i; + } + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + for (i = 255; i >= 0; i--) { + seed = seed * 6364136223846793005LL + 1442695040888963407LL; + int r = (int) ((seed + 31) % (i + 1)); + if (r < 0) + r += (i + 1); + perm[i] = source[r]; + permGradIndex3D[i] = + (short) ((perm[i] % (ARRAYSIZE(gradients3D) / 3)) * 3); + source[r] = source[i]; + } + return 0; +} + +static void par__simplex_noise_free(struct osn_context* ctx) +{ + if (!ctx) + return; + if (ctx->perm) { + PAR_FREE(ctx->perm); + ctx->perm = NULL; + } + if (ctx->permGradIndex3D) { + PAR_FREE(ctx->permGradIndex3D); + ctx->permGradIndex3D = NULL; + } + PAR_FREE(ctx); +} + +static double par__simplex_noise2(struct osn_context* ctx, double x, double y) +{ + // Place input coordinates onto grid. + double stretchOffset = (x + y) * STRETCH_CONSTANT_2D; + double xs = x + stretchOffset; + double ys = y + stretchOffset; + + // Floor to get grid coordinates of rhombus (stretched square) super-cell + // origin. + int xsb = fastFloor(xs); + int ysb = fastFloor(ys); + + // Skew out to get actual coordinates of rhombus origin. We'll need these + // later. + double squishOffset = (xsb + ysb) * SQUISH_CONSTANT_2D; + double xb = xsb + squishOffset; + double yb = ysb + squishOffset; + + // Compute grid coordinates relative to rhombus origin. + double xins = xs - xsb; + double yins = ys - ysb; + + // Sum those together to get a value that determines which region we're in. + double inSum = xins + yins; + + // Positions relative to origin point. + double dx0 = x - xb; + double dy0 = y - yb; + + // We'll be defining these inside the next block and using them afterwards. + double dx_ext, dy_ext; + int xsv_ext, ysv_ext; + + double value = 0; + + // Contribution (1,0) + double dx1 = dx0 - 1 - SQUISH_CONSTANT_2D; + double dy1 = dy0 - 0 - SQUISH_CONSTANT_2D; + double attn1 = 2 - dx1 * dx1 - dy1 * dy1; + if (attn1 > 0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate2(ctx, xsb + 1, ysb + 0, dx1, dy1); + } + + // Contribution (0,1) + double dx2 = dx0 - 0 - SQUISH_CONSTANT_2D; + double dy2 = dy0 - 1 - SQUISH_CONSTANT_2D; + double attn2 = 2 - dx2 * dx2 - dy2 * dy2; + if (attn2 > 0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate2(ctx, xsb + 0, ysb + 1, dx2, dy2); + } + + if (inSum <= 1) { // We're inside the triangle (2-Simplex) at (0,0) + double zins = 1 - inSum; + if (zins > xins || zins > yins) { + if (xins > yins) { + xsv_ext = xsb + 1; + ysv_ext = ysb - 1; + dx_ext = dx0 - 1; + dy_ext = dy0 + 1; + } else { + xsv_ext = xsb - 1; + ysv_ext = ysb + 1; + dx_ext = dx0 + 1; + dy_ext = dy0 - 1; + } + } else { //(1,0) and (0,1) are the closest two vertices. + xsv_ext = xsb + 1; + ysv_ext = ysb + 1; + dx_ext = dx0 - 1 - 2 * SQUISH_CONSTANT_2D; + dy_ext = dy0 - 1 - 2 * SQUISH_CONSTANT_2D; + } + } else { // We're inside the triangle (2-Simplex) at (1,1) + double zins = 2 - inSum; + if (zins < xins || zins < yins) { + if (xins > yins) { + xsv_ext = xsb + 2; + ysv_ext = ysb + 0; + dx_ext = dx0 - 2 - 2 * SQUISH_CONSTANT_2D; + dy_ext = dy0 + 0 - 2 * SQUISH_CONSTANT_2D; + } else { + xsv_ext = xsb + 0; + ysv_ext = ysb + 2; + dx_ext = dx0 + 0 - 2 * SQUISH_CONSTANT_2D; + dy_ext = dy0 - 2 - 2 * SQUISH_CONSTANT_2D; + } + } else { //(1,0) and (0,1) are the closest two vertices. + dx_ext = dx0; + dy_ext = dy0; + xsv_ext = xsb; + ysv_ext = ysb; + } + xsb += 1; + ysb += 1; + dx0 = dx0 - 1 - 2 * SQUISH_CONSTANT_2D; + dy0 = dy0 - 1 - 2 * SQUISH_CONSTANT_2D; + } + + // Contribution (0,0) or (1,1) + double attn0 = 2 - dx0 * dx0 - dy0 * dy0; + if (attn0 > 0) { + attn0 *= attn0; + value += attn0 * attn0 * extrapolate2(ctx, xsb, ysb, dx0, dy0); + } + + // Extra Vertex + double attn_ext = 2 - dx_ext * dx_ext - dy_ext * dy_ext; + if (attn_ext > 0) { + attn_ext *= attn_ext; + value += attn_ext * attn_ext * + extrapolate2(ctx, xsv_ext, ysv_ext, dx_ext, dy_ext); + } + + return value / NORM_CONSTANT_2D; +} + +void par_shapes_remove_degenerate(par_shapes_mesh* mesh, float mintriarea) +{ + int ntriangles = 0; + PAR_SHAPES_T* triangles = PAR_MALLOC(PAR_SHAPES_T, mesh->ntriangles * 3); + PAR_SHAPES_T* dst = triangles; + PAR_SHAPES_T const* src = mesh->triangles; + float next[3], prev[3], cp[3]; + float mincplen2 = (mintriarea * 2) * (mintriarea * 2); + for (int f = 0; f < mesh->ntriangles; f++, src += 3) { + float const* pa = mesh->points + 3 * src[0]; + float const* pb = mesh->points + 3 * src[1]; + float const* pc = mesh->points + 3 * src[2]; + par_shapes__copy3(next, pb); + par_shapes__subtract3(next, pa); + par_shapes__copy3(prev, pc); + par_shapes__subtract3(prev, pa); + par_shapes__cross3(cp, next, prev); + float cplen2 = par_shapes__dot3(cp, cp); + if (cplen2 >= mincplen2) { + *dst++ = src[0]; + *dst++ = src[1]; + *dst++ = src[2]; + ntriangles++; + } + } + mesh->ntriangles = ntriangles; + PAR_FREE(mesh->triangles); + mesh->triangles = triangles; +} + +#endif // PAR_SHAPES_IMPLEMENTATION +#endif // PAR_SHAPES_H + +// par_shapes is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_sprune.h b/source/engine/thirdparty/par/par_sprune.h new file mode 100644 index 0000000..b274600 --- /dev/null +++ b/source/engine/thirdparty/par/par_sprune.h @@ -0,0 +1,476 @@ +// SPRUNE :: https://github.com/prideout/par +// Sweep and Prune library for detecting axis-aligned box collisions in 2D. +// +// For an emscripten demo of this library, take a look at the following link. +// +// https://prideout.net/d3cpp +// +// The axis-aligned bounding boxes are specified by (minx, miny, maxx, maxy). +// Simple usage example: +// +// float boxes[] = { +// 0.10, 0.10, 0.30, 0.30, // box 0 +// 0.20, 0.20, 0.40, 0.40, // box 1 +// 0.60, 0.15, 0.70, 0.25, // box 2 +// }; +// int nboxes = 3; +// par_sprune_context* ctx = par_sprune_overlap(boxes, nboxes, 0); +// int const* pairs = ctx->collision_pairs; +// for (int i = 0; i < ctx->ncollision_pairs * 2; i += 2) { +// printf("box %d overlaps box %d\n", pairs[i], pairs[i + 1]); +// } +// par_sprune_free_context(ctx); +// +// Distributed under the MIT License, see bottom of file. + +#ifndef PAR_SPRUNE_H +#define PAR_SPRUNE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifndef PAR_SPRUNE_INT +#define PAR_SPRUNE_INT int32_t +#endif + +#ifndef PAR_SPRUNE_FLT +#define PAR_SPRUNE_FLT float +#endif + +// ----------------------------------------------------------------------------- +// BEGIN PUBLIC API +// ----------------------------------------------------------------------------- + +typedef struct { + PAR_SPRUNE_INT const* const collision_pairs; // list of two-tuples + PAR_SPRUNE_INT const ncollision_pairs; // number of two-tuples + PAR_SPRUNE_INT const* const culled; // filled by par_sprune_cull + PAR_SPRUNE_INT const nculled; // set by par_sprune_cull +} par_sprune_context; + +void par_sprune_free_context(par_sprune_context* context); + +// Takes an array of 4-tuples (minx miny maxx maxy) and performs SaP. Populates +// "collision_pairs" and "ncollision_pairs". Optionally takes an existing +// context to avoid memory churn; pass NULL for initial construction. +par_sprune_context* par_sprune_overlap(PAR_SPRUNE_FLT const* aabbs, + PAR_SPRUNE_INT naabbs, par_sprune_context* previous); + +// Reads new aabb data from the same pointer that was passed to the overlap +// function and refreshes the two relevant fields. This function should +// only be used when the number of aabbs remains constant. If this returns +// false, no changes to the collision set were detected. +bool par_sprune_update(par_sprune_context* ctx); + +// Examines all collision groups and creates a culling set such that no boxes +// would overlap if the culled boxes are removed. When two boxes collide, the +// box that occurs earlier in the list is more likely to be culled. Populates +// the "culled" and "nculled" fields in par_sprune_context. This is useful for +// hiding labels in GIS applications. +void par_sprune_cull(par_sprune_context* context); + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- + +#ifdef __cplusplus +} +#endif + +#ifdef PAR_SPRUNE_IMPLEMENTATION +#define PARINT PAR_SPRUNE_INT +#define PARFLT PAR_SPRUNE_FLT + +#include +#include + +#ifndef PAR_PI +#define PAR_PI (3.14159265359) +#define PAR_MIN(a, b) (a > b ? b : a) +#define PAR_MAX(a, b) (a > b ? a : b) +#define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) +#define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#define PAR_SQR(a) ((a) * (a)) +#endif + +#ifndef PAR_MALLOC +#define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) +#define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) +#define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N))) +#define PAR_FREE(BUF) free(BUF) +#endif + +#ifndef PAR_ARRAY +#define PAR_ARRAY +#define pa_free(a) ((a) ? PAR_FREE(pa___raw(a)), 0 : 0) +#define pa_push(a, v) (pa___maybegrow(a, (int) 1), (a)[pa___n(a)++] = (v)) +#define pa_count(a) ((a) ? pa___n(a) : 0) +#define pa_add(a, n) (pa___maybegrow(a, (int) n), pa___n(a) += (n)) +#define pa_last(a) ((a)[pa___n(a) - 1]) +#define pa_end(a) (a + pa_count(a)) +#define pa_clear(arr) if (arr) pa___n(arr) = 0 +#define pa___raw(a) ((int*) (a) -2) +#define pa___m(a) pa___raw(a)[0] +#define pa___n(a) pa___raw(a)[1] +#define pa___needgrow(a, n) ((a) == 0 || pa___n(a) + ((int) n) >= pa___m(a)) +#define pa___maybegrow(a, n) (pa___needgrow(a, (n)) ? pa___grow(a, n) : 0) +#define pa___grow(a, n) (*((void**)& (a)) = pa___growf((void*) (a), (n), \ + sizeof(*(a)))) + +// ptr[-2] is capacity, ptr[-1] is size. +static void* pa___growf(void* arr, int increment, int itemsize) +{ + int dbl_cur = arr ? 2 * pa___m(arr) : 0; + int min_needed = pa_count(arr) + increment; + int m = dbl_cur > min_needed ? dbl_cur : min_needed; + int* p = (int *) PAR_REALLOC(uint8_t, arr ? pa___raw(arr) : 0, + itemsize * m + sizeof(int) * 2); + if (p) { + if (!arr) { + p[1] = 0; + } + p[0] = m; + return p + 2; + } + return (void*) (2 * sizeof(int)); +} + +#endif + +typedef struct { + + // Public: + PARINT* collision_pairs; + PARINT ncollision_pairs; + PARINT* culled; + PARINT nculled; + + // Private: + PARFLT const* aabbs; + PARINT naabbs; + PARINT* sorted_indices[2]; + PARINT* pairs[2]; + +} par_sprune__context; + +static inline int par_qsort_cmpswap(char *__restrict a, char *__restrict b, + size_t w, + int (*compar)(const void *_a, const void *_b, + void *_arg), + void *arg) +{ + char tmp, *end = a+w; + if (compar(a, b, arg) > 0) { + for(; a < end; a++, b++) { tmp = *a; *a = *b; *b = tmp; } + return 1; + } + return 0; +} + +// qsort doesn't take a context, so we have our own portable implementation. +// Parameters: +// base is the array to be sorted +// nel is the number of elements in the array +// w is the size in bytes of each element of the array +// compar is the comparison function +// arg is a pointer to be passed to the comparison function +// +static inline void par_qsort( + void *base, + size_t nel, + size_t w, + int (*compar)(const void *_a, const void *_b, void *_arg), + void *arg) +{ + char *b = (char*) base, *end = (char*) (b + nel * w); + if (nel < 7) { + char *pi, *pj; + for (pi = b+w; pi < end; pi += w) { + for (pj = pi; pj > b && par_qsort_cmpswap(pj-w, pj, w, compar, arg); + pj -= w) {} + } + return; + } + char *x, *y, *xend, ch; + char *pl, *pr; + char *last = b+w*(nel-1), *tmp; + char *l[3]; + l[0] = b; + l[1] = b+w*(nel/2); + l[2] = last; + if (compar(l[0],l[1],arg) > 0) { + tmp=l[0]; l[0]=l[1]; l[1]=tmp; + } + if (compar(l[1],l[2],arg) > 0) { + tmp=l[1]; l[1]=l[2]; l[2]=tmp; + if (compar(l[0],l[1],arg) > 0) { + tmp=l[0]; l[0]=l[1]; l[1]=tmp; + } + } + for(x = l[1], y = last, xend = x+w; xsorted_indices[0]); + pa_free(ctx->sorted_indices[1]); + pa_free(ctx->pairs[0]); + pa_free(ctx->pairs[1]); + pa_free(ctx->collision_pairs); + PAR_FREE(ctx); +} + +static void par_sprune__remove(PARINT* arr, PARINT val) +{ + int i = pa_count(arr) - 1; + for (; i >= 0; i--) { + if (arr[i] == val) { + break; + } + } + assert(i >= 0); + for (++i; i < pa_count(arr); i++) { + PAR_SWAP(PARINT, arr[i - 1], arr[i]); + } + pa___n(arr)--; +} + +typedef struct { + PARFLT const* aabbs; +} par__sprune_sorter; + +static int par__cmpinds(const void* pa, const void* pb, void* psorter) +{ + PARINT a = *((const PARINT*) pa); + PARINT b = *((const PARINT*) pb); + par__sprune_sorter* sorter = (par__sprune_sorter*) psorter; + PARFLT const* aabbs = sorter->aabbs; + PARFLT vala = aabbs[a]; + PARFLT valb = aabbs[b]; + if (vala > valb) return 1; + if (vala < valb) return -1; + if (a > b) return 1; + if (a < b) return -1; + return 0; +} + +static int par__cmppairs(const void* pa, const void* pb, void* unused) +{ + PARINT a = *((const PARINT*) pa); + PARINT b = *((const PARINT*) pb); + if (a > b) return 1; + if (a < b) return -1; + a = *(1 + (const PARINT*) pa); + b = *(1 + (const PARINT*) pb); + if (a > b) return 1; + if (a < b) return -1; + return 0; +} + +static int par__cmpfind(const void* pa, const void* pb) +{ + PARINT a = *((const PARINT*) pa); + PARINT b = *((const PARINT*) pb); + if (a > b) return 1; + if (a < b) return -1; + a = *(1 + (const PARINT*) pa); + b = *(1 + (const PARINT*) pb); + if (a > b) return 1; + if (a < b) return -1; + return 0; +} + +par_sprune_context* par_sprune_overlap(PARFLT const* aabbs, PARINT naabbs, + par_sprune_context* previous) +{ + par_sprune__context* ctx = (par_sprune__context*) previous; + if (!ctx) { + ctx = PAR_CALLOC(par_sprune__context, 1); + } + ctx->aabbs = aabbs; + ctx->naabbs = naabbs; + for (int axis = 0; axis < 2; axis++) { + pa_clear(ctx->sorted_indices[axis]); + pa_add(ctx->sorted_indices[axis], naabbs * 2); + pa_clear(ctx->pairs[axis]); + } + for (PARINT i = 0; i < naabbs; i++) { + ctx->sorted_indices[0][i * 2 + 0] = i * 4 + 0; + ctx->sorted_indices[1][i * 2 + 0] = i * 4 + 1; + ctx->sorted_indices[0][i * 2 + 1] = i * 4 + 2; + ctx->sorted_indices[1][i * 2 + 1] = i * 4 + 3; + } + par__sprune_sorter sorter; + sorter.aabbs = ctx->aabbs; + PARINT* active = 0; + + // Sweep a plane first across the X-axis, then down through the Y-axis. + + for (int axis = 0; axis < 2; axis++) { + PARINT** pairs = &ctx->pairs[axis]; + PARINT* indices = ctx->sorted_indices[axis]; + par_qsort(indices, naabbs * 2, sizeof(PARINT), par__cmpinds, &sorter); + pa_clear(active); + for (PARINT i = 0; i < naabbs * 2; i++) { + PARINT fltindex = indices[i]; + PARINT boxindex = fltindex / 4; + bool ismin = ((fltindex - axis) % 4) == 0; + if (ismin) { + for (int j = 0; j < pa_count(active); j++) { + pa_push(*pairs, active[j]); + pa_push(*pairs, boxindex); + pa_push(*pairs, boxindex); + pa_push(*pairs, active[j]); + } + pa_push(active, boxindex); + } else { + par_sprune__remove(active, boxindex); + } + } + } + + // Sort the Y-axis collision pairs to make it easier to intersect it + // with the set of X-axis collision pairs. We also sort the X-axis + // pairs because it's required for subsequent calls to par_sprune_update. + + PARINT* xpairs = ctx->pairs[0]; + PARINT* ypairs = ctx->pairs[1]; + int nxpairs = pa_count(xpairs) / 2; + int nypairs = pa_count(ypairs) / 2; + int pairsize = 2 * sizeof(PARINT); + pa_free(active); + par_qsort(xpairs, nxpairs, pairsize, par__cmppairs, 0); + par_qsort(ypairs, nypairs, pairsize, par__cmppairs, 0); + pa_clear(ctx->collision_pairs); + + // Find the intersection of X-axis overlaps and Y-axis overlaps. + + for (int i = 0; i < pa_count(xpairs); i += 2) { + PARINT* key = xpairs + i; + if (key[1] < key[0]) { + continue; + } + void* found = bsearch(key, ypairs, nypairs, pairsize, par__cmpfind); + if (found) { + pa_push(ctx->collision_pairs, key[0]); + pa_push(ctx->collision_pairs, key[1]); + } + } + ctx->ncollision_pairs = pa_count(ctx->collision_pairs) / 2; + return (par_sprune_context*) ctx; +} + +bool par_sprune_update(par_sprune_context* context) +{ + par_sprune__context* ctx = (par_sprune__context*) context; + PARINT* collision_pairs = ctx->collision_pairs; + PARINT ncollision_pairs = ctx->ncollision_pairs; + ctx->collision_pairs = 0; + par_sprune_overlap(ctx->aabbs, ctx->naabbs, context); + bool dirty = ncollision_pairs != ctx->ncollision_pairs; + if (!dirty) { + int pairsize = 2 * sizeof(PARINT); + for (int i = 0; i < ctx->ncollision_pairs; i += 2) { + PARINT* key = ctx->collision_pairs + i; + if (!bsearch(key, collision_pairs, ncollision_pairs, + pairsize, par__cmpfind)) { + dirty = true; + break; + } + } + } + pa_free(collision_pairs); + return dirty; +} + +bool par_sprune__is_culled(par_sprune__context* ctx, PARINT key) +{ + for (int i = 0; i < pa_count(ctx->culled); i++) { + if (key == ctx->culled[i]) { + return true; + } + } + return false; +} + +static int par__cmpfindsingle(const void* pa, const void* pb) +{ + PARINT a = *((const PARINT*) pa); + PARINT b = *((const PARINT*) pb); + if (a > b) return 1; + if (a < b) return -1; + return 0; +} + +void par_sprune_cull(par_sprune_context* context) +{ + par_sprune__context* ctx = (par_sprune__context*) context; + pa_clear(ctx->culled); + PARINT* collision_pairs = ctx->collision_pairs; + PARINT ncollision_pairs = ctx->ncollision_pairs; + int pairsize = 2 * sizeof(PARINT); + for (int i = 0; i < ctx->naabbs; i++) { + PARINT* found = (PARINT*) bsearch(&i, collision_pairs, ncollision_pairs, + pairsize, par__cmpfindsingle); + if (!found) { + continue; + } + if (!par_sprune__is_culled(ctx, found[0]) && + !par_sprune__is_culled(ctx, found[1])) { + pa_push(ctx->culled, found[0]); + } + } + ctx->nculled = pa_count(ctx->culled); +} + +#undef PARINT +#undef PARFLT +#endif // PAR_SPRUNE_IMPLEMENTATION +#endif // PAR_SPRUNE_H + +// par_sprune is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_streamlines.h b/source/engine/thirdparty/par/par_streamlines.h new file mode 100644 index 0000000..1f1c694 --- /dev/null +++ b/source/engine/thirdparty/par/par_streamlines.h @@ -0,0 +1,1259 @@ +// STREAMLINES :: https://prideout.net/blog/par_streamlines/ +// Simple C library for triangulating wide lines, curves, and streamlines. +// +// Usage example: +// +// #define PAR_STREAMLINES_IMPLEMENTATION +// #include "par_streamlines.h" +// +// parsl_context* ctx = parsl_create_context({ .thickness = 3 }); +// parsl_position vertices[] = { {0, 0}, {2, 1}, {4, 0} }; +// uint16_t spine_lengths[] = { 3 }; +// parsl_mesh* mesh = parsl_mesh_from_lines(ctx, { +// .num_vertices = sizeof(vertices) / sizeof(parsl_position), +// .num_spines = sizeof(spine_lengths) / sizeof(uint16_t), +// .vertices = vertices, +// .spine_lengths = spine_lengths +// }); +// ... +// parsl_destroy_context(ctx); +// +// Distributed under the MIT License, see bottom of file. + +#ifndef PAR_STREAMLINES_H +#define PAR_STREAMLINES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// Configures how the library assigns UV coordinates. +typedef enum { + PAR_U_MODE_NORMALIZED_DISTANCE, // this is the default + PAR_U_MODE_DISTANCE, // non-normalized distance along the curve + PAR_U_MODE_SEGMENT_INDEX, // starts at zero for each curve, counts up + PAR_U_MODE_SEGMENT_FRACTION, // 0.0, 1.0 / COUNT, 2.0 / COUNT, etc... +} parsl_u_mode; + +// Layout for generated vertex attributes. +typedef struct { + float u_along_curve; // longitudinal coordinate (see parsl_u_mode) + float v_across_curve; // either + or - depending on the side + float spine_to_edge_x; // normalized vector from spine to edge + float spine_to_edge_y; // normalized vector from spine to edge +} parsl_annotation; + +// Simple two-tuple math type used for mesh and spine vertices. +typedef struct { + float x; + float y; +} parsl_position; + +// Triangle mesh generated by the library. The vertex data is owned by +// streamlines context and becomes invalid on any subsequent call to the API. +// The annotations, spine_lengths, and random_offsets fields are null unless +// their corresponding flags have been set in parsl_config. +typedef struct { + uint32_t num_vertices; + uint32_t num_triangles; + uint32_t* triangle_indices; + parsl_position* positions; + parsl_annotation* annotations; + float* spine_lengths; + float* random_offsets; +} parsl_mesh; + +// Viewport for streamline seed placement. +typedef struct { + float left; + float top; + float right; + float bottom; +} parsl_viewport; + +#define PARSL_FLAG_WIREFRAME (1 << 0) // enables 4 indices per triangle +#define PARSL_FLAG_ANNOTATIONS (1 << 1) // populates mesh.annotations +#define PARSL_FLAG_SPINE_LENGTHS (1 << 2) // populates mesh.lengths +#define PARSL_FLAG_RANDOM_OFFSETS (1 << 3) // populates mesh.random_offsets +#define PARSL_FLAG_CURVE_GUIDES (1 << 4) // draws control points + +// Immutable configuration for a streamlines context. +typedef struct { + float thickness; + uint32_t flags; + parsl_u_mode u_mode; + float curves_max_flatness; + float streamlines_seed_spacing; + parsl_viewport streamlines_seed_viewport; + float miter_limit; +} parsl_config; + +// Client-owned list of line strips that will be tessellated. +typedef struct { + uint32_t num_vertices; + uint16_t num_spines; + parsl_position* vertices; + uint16_t* spine_lengths; + bool closed; +} parsl_spine_list; + +// Opaque handle to a streamlines context and its memory arena. +typedef struct parsl_context_s parsl_context; + +// Client function that moves a streamline particle by a single time step. +typedef void (*parsl_advection_callback)(parsl_position* point, void* userdata); + +parsl_context* parsl_create_context(parsl_config config); + +void parsl_destroy_context(parsl_context* ctx); + +// Low-level function that simply generates two triangles for each line segment. +parsl_mesh* parsl_mesh_from_lines(parsl_context* ctx, parsl_spine_list spines); + +// High-level function that can be used to visualize a vector field. +parsl_mesh* parsl_mesh_from_streamlines(parsl_context* context, + parsl_advection_callback advect, uint32_t first_tick, uint32_t num_ticks, + void* userdata); + +// High-level function that tessellates a series of curves into triangles, +// where each spine is a series of chained cubic Bézier curves. +// +// The first curve of each spine is defined by an endpoint, followed by two +// control points, followed by an endpoint. Every subsequent curve in the spine +// is defined by a single control point followed by an endpoint. Only one +// control point is required because the first control point is computed via +// reflection over the endpoint. +// +// The number of vertices in each spine should be 4+(n-1)*2 where n is the +// number of piecewise curves. +// +// Each spine is equivalent to an SVG path that looks like M C S S S. +parsl_mesh* parsl_mesh_from_curves_cubic(parsl_context* context, + parsl_spine_list spines); + +// High-level function that tessellates a series of curves into triangles, +// where each spine is a series of chained quadratic Bézier curves. +// +// The first curve of each spine is defined by an endpoint, followed by one +// control point, followed by an endpoint. Every subsequent curve in the spine +// is defined by a single control point followed by an endpoint. +// +// The number of vertices in each spine should be 3+(n-1)*2 where n is the +// number of piecewise curves. +// +// Each spine is equivalent to an SVG path that looks like M Q M Q M Q. +parsl_mesh* parsl_mesh_from_curves_quadratic(parsl_context* context, + parsl_spine_list spines); + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- +#ifdef PAR_STREAMLINES_IMPLEMENTATION + +#include +#include +#include +#include +#include + +static float parsl__dot(parsl_position a, parsl_position b) { + return a.x * b.x + a.y * b.y; +} + +static parsl_position parsl__sub(parsl_position a, parsl_position b) { + return (parsl_position) { a.x - b.x, a.y - b.y }; +} + +static parsl_position parsl__add(parsl_position a, parsl_position b) { + return (parsl_position) { a.x + b.x, a.y + b.y }; +} + +static parsl_position parsl_mul(parsl_position v, float s) { + return (parsl_position) { v.x * s, v.y * s }; +} + +#define PARSL_MAX_RECURSION 16 + +#ifndef PAR_PI +#define PAR_PI (3.14159265359) +#define PAR_MIN(a, b) (a > b ? b : a) +#define PAR_MAX(a, b) (a > b ? a : b) +#define PAR_CLAMP(v, lo, hi) PAR_MAX(lo, PAR_MIN(hi, v)) +#define PAR_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } +#define PAR_SQR(a) ((a) * (a)) +#endif + +#ifndef PAR_MALLOC +#define PAR_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) +#define PAR_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) +#define PAR_REALLOC(T, BUF, N) ((T*) realloc(BUF, sizeof(T) * (N))) +#define PAR_FREE(BUF) free(BUF) +#endif + +#ifndef PAR_ARRAY +#define PAR_ARRAY +#define pa_free(a) ((a) ? PAR_FREE(pa___raw(a)), 0 : 0) +#define pa_push(a, v) (pa___maybegrow(a, (int) 1), (a)[pa___n(a)++] = (v)) +#define pa_count(a) ((a) ? pa___n(a) : 0) +#define pa_add(a, n) (pa___maybegrow(a, (int) n), pa___n(a) += (n)) +#define pa_last(a) ((a)[pa___n(a) - 1]) +#define pa_end(a) (a + pa_count(a)) +#define pa_clear(arr) if (arr) pa___n(arr) = 0 + +#define pa___raw(a) ((int*) (a) -2) +#define pa___m(a) pa___raw(a)[0] +#define pa___n(a) pa___raw(a)[1] +#define pa___needgrow(a, n) ((a) == 0 || pa___n(a) + ((int) n) >= pa___m(a)) +#define pa___maybegrow(a, n) (pa___needgrow(a, (n)) ? pa___grow(a, n) : 0) +#define pa___grow(a, n) (*((void**)& (a)) = pa___growf((void*) (a), (n), \ + sizeof(*(a)))) + +// ptr[-2] is capacity, ptr[-1] is size. +static void* pa___growf(void* arr, int increment, int itemsize) +{ + int dbl_cur = arr ? 2 * pa___m(arr) : 0; + int min_needed = pa_count(arr) + increment; + int m = dbl_cur > min_needed ? dbl_cur : min_needed; + int* p = (int *) PAR_REALLOC(uint8_t, arr ? pa___raw(arr) : 0, + itemsize * m + sizeof(int) * 2); + if (p) { + if (!arr) { + p[1] = 0; + } + p[0] = m; + return p + 2; + } + return (void*) (2 * sizeof(int)); +} + +#endif + +struct parsl_context_s { + parsl_config config; + parsl_mesh result; + parsl_position* streamline_seeds; + parsl_position* streamline_points; + parsl_spine_list streamline_spines; + parsl_spine_list curve_spines; + uint16_t guideline_start; +}; + +parsl_context* parsl_create_context(parsl_config config) +{ + parsl_context* context = PAR_CALLOC(parsl_context, 1); + context->config = config; + return context; +} + +void parsl_destroy_context(parsl_context* context) +{ + pa_free(context->result.triangle_indices); + pa_free(context->result.spine_lengths); + pa_free(context->result.annotations); + pa_free(context->result.positions); + pa_free(context->result.random_offsets); + pa_free(context->streamline_seeds); + pa_free(context->streamline_points); + pa_free(context->streamline_spines.spine_lengths); + pa_free(context->streamline_spines.vertices); + pa_free(context->curve_spines.spine_lengths); + pa_free(context->curve_spines.vertices); + PAR_FREE(context); +} + +parsl_mesh* parsl_mesh_from_lines(parsl_context* context, + parsl_spine_list spines) +{ + typedef parsl_position Position; + typedef parsl_annotation Annotation; + + parsl_mesh* mesh = &context->result; + const bool closed = spines.closed; + const bool wireframe = context->config.flags & PARSL_FLAG_WIREFRAME; + const bool has_annotations = context->config.flags & PARSL_FLAG_ANNOTATIONS; + const bool has_lengths = context->config.flags & PARSL_FLAG_SPINE_LENGTHS; + const float miter_limit = context->config.miter_limit ? + context->config.miter_limit : (context->config.thickness * 2); + const float miter_acos_max = +1.0; + const float miter_acos_min = -1.0; + const uint32_t ind_per_tri = wireframe ? 4 : 3; + + mesh->num_vertices = 0; + mesh->num_triangles = 0; + + for (uint32_t spine = 0; spine < spines.num_spines; spine++) { + assert(spines.spine_lengths[spine] > 1); + mesh->num_vertices += 2 * spines.spine_lengths[spine]; + mesh->num_triangles += 2 * (spines.spine_lengths[spine] - 1); + if (closed) { + mesh->num_vertices += 2; + mesh->num_triangles += 2; + } + } + + pa_clear(mesh->spine_lengths); + pa_clear(mesh->annotations); + pa_clear(mesh->positions); + pa_clear(mesh->triangle_indices); + + if (has_lengths) { + pa_add(mesh->spine_lengths, mesh->num_vertices); + } + if (has_annotations) { + pa_add(mesh->annotations, mesh->num_vertices); + } + + pa_add(mesh->positions, mesh->num_vertices); + pa_add(mesh->triangle_indices, ind_per_tri * mesh->num_triangles); + + float* dst_lengths = mesh->spine_lengths; + Annotation* dst_annotations = mesh->annotations; + Position* dst_positions = mesh->positions; + uint32_t* dst_indices = mesh->triangle_indices; + + const Position* src_position = spines.vertices; + uint32_t base_index = 0; + + for (uint16_t spine = 0; spine < spines.num_spines; spine++) { + const bool thin = context->guideline_start > 0 && + spine >= context->guideline_start; + const float thickness = thin ? 1.0f : context->config.thickness; + const uint16_t spine_length = spines.spine_lengths[spine]; + float dx = src_position[1].x - src_position[0].x; + float dy = src_position[1].y - src_position[0].y; + float segment_length = sqrtf(dx * dx + dy * dy); + float invlen = segment_length ? 1.0f / segment_length : 0.0f; + const float nx = -dy * invlen; + const float ny = dx * invlen; + + const Position first_src_position = src_position[0]; + const Position last_src_position = src_position[spine_length - 1]; + + float ex = nx * thickness / 2; + float ey = ny * thickness / 2; + + if (closed) { + const float dx = src_position[0].x - last_src_position.x; + const float dy = src_position[0].y - last_src_position.y; + const float segment_length = sqrtf(dx * dx + dy * dy); + float invlen = segment_length ? 1.0f / segment_length : 0.0f; + const float pnx = -dy * invlen; + const float pny = dx * invlen; + + // NOTE: sin(pi / 2 - acos(X) / 2) == sqrt(1 + X) / sqrt(2) + float extent = 0.5 * thickness; + const float dotp = (pnx * nx + pny * ny); + if (dotp < miter_acos_max && dotp > miter_acos_min) { + const float phi = acos(dotp) / 2; + const float theta = PAR_PI / 2 - phi; + extent = PAR_CLAMP(extent / sin(theta), -miter_limit, + miter_limit); + } + + ex = pnx + nx; + ey = pny + ny; + const float len = sqrtf(ex * ex + ey * ey); + invlen = len == 0.0 ? 0.0 : (1.0f / len); + ex *= invlen * extent; + ey *= invlen * extent; + } + + dst_positions[0].x = src_position[0].x + ex; + dst_positions[0].y = src_position[0].y + ey; + dst_positions[1].x = src_position[0].x - ex; + dst_positions[1].y = src_position[0].y - ey; + + float pnx = nx; + float pny = ny; + + const Position first_dst_positions[2] = { + dst_positions[0], + dst_positions[1] + }; + + src_position++; + dst_positions += 2; + + if (has_annotations) { + dst_annotations[0].u_along_curve = 0; + dst_annotations[1].u_along_curve = 0; + dst_annotations[0].v_across_curve = 1; + dst_annotations[1].v_across_curve = -1; + dst_annotations[0].spine_to_edge_x = ex; + dst_annotations[1].spine_to_edge_x = -ex; + dst_annotations[0].spine_to_edge_y = ey; + dst_annotations[1].spine_to_edge_y = -ey; + dst_annotations += 2; + } + + float distance_along_spine = segment_length; + + uint16_t segment_index = 1; + for (; segment_index < spine_length - 1; segment_index++) { + + const float dx = src_position[1].x - src_position[0].x; + const float dy = src_position[1].y - src_position[0].y; + const float segment_length = sqrtf(dx * dx + dy * dy); + float invlen = segment_length ? 1.0f / segment_length : 0.0f; + const float nx = -dy * invlen; + const float ny = dx * invlen; + + // NOTE: sin(pi / 2 - acos(X) / 2) == sqrt(1 + X) / sqrt(2) + float extent = 0.5 * thickness; + const float dotp = (pnx * nx + pny * ny); + if (dotp < miter_acos_max && dotp > miter_acos_min) { + const float phi = acos(dotp) / 2; + const float theta = PAR_PI / 2 - phi; + extent = PAR_CLAMP(extent / sin(theta), -miter_limit, + miter_limit); + } + + float ex = pnx + nx; + float ey = pny + ny; + const float len = sqrtf(ex * ex + ey * ey); + invlen = len == 0.0 ? 0.0 : (1.0f / len); + ex *= invlen * extent; + ey *= invlen * extent; + + dst_positions[0].x = src_position[0].x + ex; + dst_positions[0].y = src_position[0].y + ey; + dst_positions[1].x = src_position[0].x - ex; + dst_positions[1].y = src_position[0].y - ey; + src_position++; + dst_positions += 2; + + pnx = nx; + pny = ny; + + if (has_annotations) { + dst_annotations[0].u_along_curve = distance_along_spine; + dst_annotations[1].u_along_curve = distance_along_spine; + dst_annotations[0].v_across_curve = 1; + dst_annotations[1].v_across_curve = -1; + dst_annotations[0].spine_to_edge_x = ex; + dst_annotations[1].spine_to_edge_x = -ex; + dst_annotations[0].spine_to_edge_y = ey; + dst_annotations[1].spine_to_edge_y = -ey; + dst_annotations += 2; + } + distance_along_spine += segment_length; + + if (wireframe) { + dst_indices[0] = base_index + (segment_index - 1) * 2; + dst_indices[1] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[2] = base_index + (segment_index - 0) * 2; + dst_indices[3] = base_index + (segment_index - 1) * 2; + + dst_indices[4] = base_index + (segment_index - 0) * 2; + dst_indices[5] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[6] = base_index + (segment_index - 0) * 2 + 1; + dst_indices[7] = base_index + (segment_index - 0) * 2; + dst_indices += 8; + } else { + dst_indices[0] = base_index + (segment_index - 1) * 2; + dst_indices[1] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[2] = base_index + (segment_index - 0) * 2; + + dst_indices[3] = base_index + (segment_index - 0) * 2; + dst_indices[4] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[5] = base_index + (segment_index - 0) * 2 + 1; + dst_indices += 6; + } + } + + ex = pnx * thickness / 2; + ey = pny * thickness / 2; + + if (closed) { + const float dx = first_src_position.x - src_position[0].x; + const float dy = first_src_position.y - src_position[0].y; + segment_length = sqrtf(dx * dx + dy * dy); + float invlen = segment_length ? 1.0f / segment_length : 0.0f; + const float nx = -dy * invlen; + const float ny = dx * invlen; + + // NOTE: sin(pi / 2 - acos(X) / 2) == sqrt(1 + X) / sqrt(2) + float extent = 0.5 * thickness; + const float dotp = (pnx * nx + pny * ny); + if (dotp < miter_acos_max && dotp > miter_acos_min) { + const float phi = acos(dotp) / 2; + const float theta = PAR_PI / 2 - phi; + extent = PAR_CLAMP(extent / sin(theta), -miter_limit, + miter_limit); + } + + ex = pnx + nx; + ey = pny + ny; + const float len = sqrtf(ex * ex + ey * ey); + invlen = len == 0.0 ? 0.0 : (1.0f / len); + ex *= invlen * extent; + ey *= invlen * extent; + } + + dst_positions[0].x = src_position[0].x + ex; + dst_positions[0].y = src_position[0].y + ey; + dst_positions[1].x = src_position[0].x - ex; + dst_positions[1].y = src_position[0].y - ey; + src_position++; + dst_positions += 2; + + pnx = nx; + pny = ny; + + if (has_annotations) { + dst_annotations[0].u_along_curve = distance_along_spine; + dst_annotations[1].u_along_curve = distance_along_spine; + dst_annotations[0].v_across_curve = 1; + dst_annotations[1].v_across_curve = -1; + dst_annotations[0].spine_to_edge_x = ex; + dst_annotations[1].spine_to_edge_x = -ex; + dst_annotations[0].spine_to_edge_y = ey; + dst_annotations[1].spine_to_edge_y = -ey; + dst_annotations += 2; + } + + if (wireframe) { + dst_indices[0] = base_index + (segment_index - 1) * 2; + dst_indices[1] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[2] = base_index + (segment_index - 0) * 2; + dst_indices[3] = base_index + (segment_index - 1) * 2; + + dst_indices[4] = base_index + (segment_index - 0) * 2; + dst_indices[5] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[6] = base_index + (segment_index - 0) * 2 + 1; + dst_indices[7] = base_index + (segment_index - 0) * 2; + dst_indices += 8; + } else { + dst_indices[0] = base_index + (segment_index - 1) * 2; + dst_indices[1] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[2] = base_index + (segment_index - 0) * 2; + + dst_indices[3] = base_index + (segment_index - 0) * 2; + dst_indices[4] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[5] = base_index + (segment_index - 0) * 2 + 1; + dst_indices += 6; + } + + if (closed) { + segment_index++; + distance_along_spine += segment_length; + + dst_positions[0] = first_dst_positions[0]; + dst_positions[1] = first_dst_positions[1]; + dst_positions += 2; + + if (has_annotations) { + dst_annotations[0].u_along_curve = distance_along_spine; + dst_annotations[1].u_along_curve = distance_along_spine; + dst_annotations[0].v_across_curve = 1; + dst_annotations[1].v_across_curve = -1; + dst_annotations[0].spine_to_edge_x = ex; + dst_annotations[1].spine_to_edge_x = -ex; + dst_annotations[0].spine_to_edge_y = ey; + dst_annotations[1].spine_to_edge_y = -ey; + dst_annotations += 2; + } + + if (wireframe) { + dst_indices[0] = base_index + (segment_index - 1) * 2; + dst_indices[1] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[2] = base_index + (segment_index - 0) * 2; + dst_indices[3] = base_index + (segment_index - 1) * 2; + + dst_indices[4] = base_index + (segment_index - 0) * 2; + dst_indices[5] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[6] = base_index + (segment_index - 0) * 2 + 1; + dst_indices[7] = base_index + (segment_index - 0) * 2; + dst_indices += 8; + } else { + dst_indices[0] = base_index + (segment_index - 1) * 2; + dst_indices[1] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[2] = base_index + (segment_index - 0) * 2; + + dst_indices[3] = base_index + (segment_index - 0) * 2; + dst_indices[4] = base_index + (segment_index - 1) * 2 + 1; + dst_indices[5] = base_index + (segment_index - 0) * 2 + 1; + dst_indices += 6; + } + } + + base_index += spine_length * 2 + (closed ? 2 : 0); + + const uint16_t nverts = spine_length + (closed ? 1 : 0); + + if (has_lengths) { + for (uint16_t i = 0; i < nverts; i++) { + dst_lengths[0] = distance_along_spine; + dst_lengths[1] = distance_along_spine; + dst_lengths += 2; + } + } + + // Go back through the curve and fix up the U coordinates. + if (has_annotations) { + const float invlength = 1.0f / distance_along_spine; + const float invcount = 1.0f / spine_length; + switch (context->config.u_mode) { + case PAR_U_MODE_DISTANCE: + break; + case PAR_U_MODE_NORMALIZED_DISTANCE: + dst_annotations -= nverts * 2; + for (uint16_t i = 0; i < nverts; i++) { + dst_annotations[0].u_along_curve *= invlength; + dst_annotations[1].u_along_curve *= invlength; + dst_annotations += 2; + } + break; + case PAR_U_MODE_SEGMENT_INDEX: + dst_annotations -= nverts * 2; + for (uint16_t i = 0; i < nverts; i++) { + dst_annotations[0].u_along_curve = i; + dst_annotations[1].u_along_curve = i; + dst_annotations += 2; + } + break; + case PAR_U_MODE_SEGMENT_FRACTION: + dst_annotations -= nverts * 2; + for (uint16_t i = 0; i < nverts; i++) { + dst_annotations[0].u_along_curve = invcount * i; + dst_annotations[1].u_along_curve = invcount * i; + dst_annotations += 2; + } + break; + } + } + } + + assert(src_position - spines.vertices == spines.num_vertices); + assert(dst_positions - mesh->positions == mesh->num_vertices); + assert(dst_indices - mesh->triangle_indices == + mesh->num_triangles * ind_per_tri); + + if (context->config.flags & PARSL_FLAG_RANDOM_OFFSETS) { + pa_clear(mesh->random_offsets); + pa_add(mesh->random_offsets, mesh->num_vertices); + float* pvertex = mesh->random_offsets; + for (uint16_t spine = 0; spine < spines.num_spines; spine++) { + const uint16_t num_segments = spines.spine_lengths[spine]; + const float r = (float) rand() / RAND_MAX; + for (uint16_t segment = 0; segment < num_segments; segment++) { + *pvertex++ = r; + } + } + } + + return mesh; +} + +// This function is designed to be called in two passes. In the first pass, the +// points pointer is null, so this simply determines the number of required +// points to fulfill the flatness criterion. On the second pass, points is +// non-null it actually writes out the point positions. +static void parsl__tesselate_cubic( + parsl_position* points, uint32_t* num_points, + float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3, + float max_flatness_squared, int recursion_depth) +{ + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (sqrt(dx0*dx0 + dy0*dy0) + sqrt(dx1*dx1 + dy1*dy1) + + sqrt(dx2*dx2 + dy2*dy2)); + float shortlen = (float) sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen - shortlen*shortlen; + + if (recursion_depth > PARSL_MAX_RECURSION) { + return; + } + + if (flatness_squared > max_flatness_squared) { + const float x01 = (x0+x1) / 2; + const float y01 = (y0+y1) / 2; + const float x12 = (x1+x2) / 2; + const float y12 = (y1+y2) / 2; + const float x23 = (x2+x3) / 2; + const float y23 = (y2+y3) / 2; + const float xa = (x01+x12) / 2; + const float ya = (y01+y12) / 2; + const float xb = (x12+x23) / 2; + const float yb = (y12+y23) / 2; + const float mx = (xa+xb) / 2; + const float my = (ya+yb) / 2; + parsl__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, + max_flatness_squared, recursion_depth + 1); + parsl__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, + max_flatness_squared, recursion_depth + 1); + return; + } + + int n = *num_points; + if (points) { + points[n].x = x3; + points[n].y = y3; + } + *num_points = n + 1; +} + +// This function is designed to be called in two passes. In the first pass, the +// points pointer is null, so this simply determines the number of required +// points to fulfill the flatness criterion. On the second pass, points is +// non-null it actually writes out the point positions. +static void parsl__tesselate_quadratic( + parsl_position* points, uint32_t* num_points, + float x0, float y0, float x1, float y1, float x2, float y2, + float max_flatness_squared, int recursion_depth) +{ + const float mx = (x0 + 2 * x1 + x2) / 4; + const float my = (y0 + 2 * y1 + y2) / 4; + const float dx = (x0 + x2) / 2 - mx; + const float dy = (y0 + y2) / 2 - my; + const float flatness_squared = dx * dx + dy * dy; + + if (recursion_depth++ > PARSL_MAX_RECURSION) { + return; + } + + if (flatness_squared > max_flatness_squared) { + parsl__tesselate_quadratic(points, num_points, x0,y0, + (x0 + x1) / 2.0f, (y0 + y1) / 2.0f, + mx, my, + max_flatness_squared, recursion_depth); + parsl__tesselate_quadratic(points, num_points, mx,my, + (x1 + x2) / 2.0f, (y1 + y2) / 2.0f, + x2, y2, + max_flatness_squared, recursion_depth); + return; + } + + int n = *num_points; + if (points) { + points[n].x = x2; + points[n].y = y2; + } + *num_points = n + 1; +} + +parsl_mesh* parsl_mesh_from_curves_cubic(parsl_context* context, + parsl_spine_list source_spines) +{ + float max_flatness = context->config.curves_max_flatness; + if (max_flatness == 0) { + max_flatness = 1.0f; + } + const float max_flatness_squared = max_flatness * max_flatness; + parsl_spine_list* target_spines = &context->curve_spines; + const bool has_guides = context->config.flags & PARSL_FLAG_CURVE_GUIDES; + + // Determine the number of spines in the target list. + target_spines->num_spines = source_spines.num_spines; + if (has_guides) { + for (uint32_t spine = 0; spine < source_spines.num_spines; spine++) { + uint32_t spine_length = source_spines.spine_lengths[spine]; + uint32_t num_piecewise = 1 + (spine_length - 4) / 2; + target_spines->num_spines += num_piecewise * 2; + } + } + pa_clear(target_spines->spine_lengths); + pa_add(target_spines->spine_lengths, target_spines->num_spines); + + // First pass: determine the number of required vertices. + uint32_t total_required_spine_points = 0; + const parsl_position* psource = source_spines.vertices; + for (uint32_t spine = 0; spine < source_spines.num_spines; spine++) { + + // Source vertices look like: P1 C1 C2 P2 [C2 P2]* + uint32_t spine_length = source_spines.spine_lengths[spine]; + assert(spine_length >= 4); + assert((spine_length % 2) == 0); + uint32_t num_piecewise = 1 + (spine_length - 4) / 2; + + // First piecewise curve. + uint32_t num_required_spine_points = 1; + parsl__tesselate_cubic(NULL, &num_required_spine_points, + psource[0].x, psource[0].y, psource[1].x, psource[1].y, + psource[2].x, psource[2].y, psource[3].x, psource[3].y, + max_flatness_squared, 0); + psource += 4; + + // Subsequent piecewise curves. + for (uint32_t piecewise = 1; piecewise < num_piecewise; piecewise++) { + parsl_position p1 = psource[-1]; + parsl_position previous_c2 = psource[-2]; + parsl_position c1 = parsl__sub(p1, parsl__sub(previous_c2, p1)); + parsl_position c2 = psource[0]; + parsl_position p2 = psource[1]; + parsl__tesselate_cubic(NULL, &num_required_spine_points, + p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p2.x, p2.y, + max_flatness_squared, 0); + psource += 2; + } + + target_spines->spine_lengths[spine] = num_required_spine_points; + total_required_spine_points += num_required_spine_points; + } + + if (has_guides) { + uint32_t nsrcspines = source_spines.num_spines; + uint16_t* guide_lengths = &target_spines->spine_lengths[nsrcspines]; + for (uint32_t spine = 0; spine < nsrcspines; spine++) { + uint32_t spine_length = source_spines.spine_lengths[spine]; + uint32_t num_piecewise = 1 + (spine_length - 4) / 2; + for (uint32_t pw = 0; pw < num_piecewise; pw++) { + guide_lengths[0] = 2; + guide_lengths[1] = 2; + guide_lengths += 2; + total_required_spine_points += 4; + } + } + } + + // Allocate memory. + target_spines->num_vertices = total_required_spine_points; + pa_clear(target_spines->vertices); + pa_add(target_spines->vertices, total_required_spine_points); + + // Second pass: write out the data. + psource = source_spines.vertices; + parsl_position* ptarget = target_spines->vertices; + for (uint32_t spine = 0; spine < source_spines.num_spines; spine++) { + + // Source vertices look like: P1 C1 C2 P2 [C2 P2]* + uint32_t spine_length = source_spines.spine_lengths[spine]; + uint32_t num_piecewise = 1 + (spine_length - 4) / 2; + + __attribute__((unused)) + parsl_position* target_spine_start = ptarget; + + // First piecewise curve. + ptarget[0].x = psource[0].x; + ptarget[0].y = psource[0].y; + ptarget++; + uint32_t num_written_points = 0; + parsl__tesselate_cubic(ptarget, &num_written_points, + psource[0].x, psource[0].y, psource[1].x, psource[1].y, + psource[2].x, psource[2].y, psource[3].x, psource[3].y, + max_flatness_squared, 0); + psource += 4; + ptarget += num_written_points; + + // Subsequent piecewise curves. + for (uint32_t piecewise = 1; piecewise < num_piecewise; piecewise++) { + parsl_position p1 = psource[-1]; + parsl_position previous_c2 = psource[-2]; + parsl_position c1 = parsl__sub(p1, parsl__sub(previous_c2, p1)); + parsl_position c2 = psource[0]; + parsl_position p2 = psource[1]; + num_written_points = 0; + parsl__tesselate_cubic(ptarget, &num_written_points, + p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p2.x, p2.y, + max_flatness_squared, 0); + psource += 2; + ptarget += num_written_points; + } + + __attribute__((unused)) + uint32_t num_written = ptarget - target_spine_start; + assert(num_written == (uint32_t) target_spines->spine_lengths[spine]); + } + + // Source vertices look like: P1 C1 C2 P2 [C2 P2]* + if (has_guides) { + uint32_t nsrcspines = source_spines.num_spines; + context->guideline_start = nsrcspines; + psource = source_spines.vertices; + for (uint32_t spine = 0; spine < nsrcspines; spine++) { + uint32_t spine_length = source_spines.spine_lengths[spine]; + uint32_t num_piecewise = 1 + (spine_length - 4) / 2; + *ptarget++ = psource[0]; + *ptarget++ = psource[1]; + *ptarget++ = psource[2]; + *ptarget++ = psource[3]; + psource += 4; + for (uint32_t pw = 1; pw < num_piecewise; pw++) { + parsl_position p1 = psource[-1]; + parsl_position previous_c2 = psource[-2]; + parsl_position c1 = parsl__sub(p1, parsl__sub(previous_c2, p1)); + parsl_position c2 = psource[0]; + parsl_position p2 = psource[1]; + *ptarget++ = p1; + *ptarget++ = c1; + *ptarget++ = p2; + *ptarget++ = c2; + psource += 2; + } + } + } + + assert(ptarget - target_spines->vertices == total_required_spine_points); + parsl_mesh_from_lines(context, context->curve_spines); + context->guideline_start = 0; + return &context->result; +} + +parsl_mesh* parsl_mesh_from_curves_quadratic(parsl_context* context, + parsl_spine_list source_spines) +{ + float max_flatness = context->config.curves_max_flatness; + if (max_flatness == 0) { + max_flatness = 1.0f; + } + const float max_flatness_squared = max_flatness * max_flatness; + parsl_spine_list* target_spines = &context->curve_spines; + const bool has_guides = context->config.flags & PARSL_FLAG_CURVE_GUIDES; + + // Determine the number of spines in the target list. + target_spines->num_spines = source_spines.num_spines; + if (has_guides) { + target_spines->num_spines += source_spines.num_spines; + } + pa_clear(target_spines->spine_lengths); + pa_add(target_spines->spine_lengths, target_spines->num_spines); + + // First pass: determine the number of required vertices. + uint32_t total_required_spine_points = 0; + const parsl_position* psource = source_spines.vertices; + for (uint32_t spine = 0; spine < source_spines.num_spines; spine++) { + + // Source vertices look like: PT C PT [C PT]* + uint32_t spine_length = source_spines.spine_lengths[spine]; + assert(spine_length >= 3); + assert((spine_length % 2) == 1); + uint32_t num_piecewise = 1 + (spine_length - 3) / 2; + + // First piecewise curve. + uint32_t num_required_spine_points = 1; + parsl__tesselate_quadratic(NULL, &num_required_spine_points, + psource[0].x, psource[0].y, psource[1].x, psource[1].y, + psource[2].x, psource[2].y, max_flatness_squared, 0); + psource += 3; + + // Subsequent piecewise curves. + for (uint32_t piecewise = 1; piecewise < num_piecewise; piecewise++) { + parsl_position p1 = psource[-1]; + parsl_position c1 = psource[0]; + parsl_position p2 = psource[1]; + parsl__tesselate_quadratic(NULL, &num_required_spine_points, + p1.x, p1.y, c1.x, c1.y, p2.x, p2.y, max_flatness_squared, 0); + psource += 2; + } + + target_spines->spine_lengths[spine] = num_required_spine_points; + total_required_spine_points += num_required_spine_points; + } + + if (has_guides) { + uint32_t nsrcspines = source_spines.num_spines; + uint16_t* guide_lengths = &target_spines->spine_lengths[nsrcspines]; + for (uint32_t spine = 0; spine < nsrcspines; spine++) { + uint32_t spine_length = source_spines.spine_lengths[spine]; + uint32_t num_piecewise = 1 + (spine_length - 3) / 2; + guide_lengths[0] = 3 + (num_piecewise - 1) * 2; + total_required_spine_points += guide_lengths[0]; + guide_lengths++; + } + } + + // Allocate memory. + target_spines->num_vertices = total_required_spine_points; + pa_clear(target_spines->vertices); + pa_add(target_spines->vertices, total_required_spine_points); + + // Second pass: write out the data. + psource = source_spines.vertices; + parsl_position* ptarget = target_spines->vertices; + for (uint32_t spine = 0; spine < source_spines.num_spines; spine++) { + + // Source vertices look like: PT C PT [C PT]* + uint32_t spine_length = source_spines.spine_lengths[spine]; + uint32_t num_piecewise = 1 + (spine_length - 3) / 2; + + __attribute__((unused)) + parsl_position* target_spine_start = ptarget; + + // First piecewise curve. + ptarget[0].x = psource[0].x; + ptarget[0].y = psource[0].y; + ptarget++; + uint32_t num_written_points = 0; + parsl__tesselate_quadratic(ptarget, &num_written_points, + psource[0].x, psource[0].y, psource[1].x, psource[1].y, + psource[2].x, psource[2].y, max_flatness_squared, 0); + psource += 3; + ptarget += num_written_points; + + // Subsequent piecewise curves. + for (uint32_t piecewise = 1; piecewise < num_piecewise; piecewise++) { + parsl_position p1 = psource[-1]; + parsl_position c1 = psource[0]; + parsl_position p2 = psource[1]; + num_written_points = 0; + parsl__tesselate_quadratic(ptarget, &num_written_points, + p1.x, p1.y, c1.x, c1.y, p2.x, p2.y, max_flatness_squared, 0); + psource += 2; + ptarget += num_written_points; + } + + __attribute__((unused)) + uint32_t num_written = ptarget - target_spine_start; + assert(num_written == (uint32_t) target_spines->spine_lengths[spine]); + } + + // Source vertices look like: PT C PT [C PT]* + if (has_guides) { + uint32_t nsrcspines = source_spines.num_spines; + context->guideline_start = nsrcspines; + psource = source_spines.vertices; + for (uint32_t spine = 0; spine < nsrcspines; spine++) { + uint32_t spine_length = source_spines.spine_lengths[spine]; + uint32_t num_piecewise = 1 + (spine_length - 3) / 2; + *ptarget++ = psource[0]; + *ptarget++ = psource[1]; + *ptarget++ = psource[2]; + psource += 3; + for (uint32_t pw = 1; pw < num_piecewise; pw++) { + *ptarget++ = psource[0]; + *ptarget++ = psource[1]; + psource += 2; + } + } + } + + assert(ptarget - target_spines->vertices == total_required_spine_points); + parsl_mesh_from_lines(context, context->curve_spines); + context->guideline_start = 0; + return &context->result; +} + +static unsigned int par__randhash(unsigned int seed) { + unsigned int i = (seed ^ 12345391u) * 2654435769u; + i ^= (i << 6) ^ (i >> 26); + i *= 2654435769u; + i += (i << 5) ^ (i >> 12); + return i; +} + +static float par__randhashf(unsigned int seed, float a, float b) { + return (b - a) * par__randhash(seed) / (float) UINT_MAX + a; +} + +static parsl_position par__sample_annulus(float radius, parsl_position center, + int* seedptr) { + unsigned int seed = *seedptr; + parsl_position r; + float rscale = 1.0f / UINT_MAX; + while (1) { + r.x = 4 * rscale * par__randhash(seed++) - 2; + r.y = 4 * rscale * par__randhash(seed++) - 2; + float r2 = parsl__dot(r, r); + if (r2 > 1 && r2 <= 4) { + break; + } + } + *seedptr = seed; + return (parsl_position) { + r.x * radius + center.x, + r.y * radius + center.y + }; +} + +#define GRIDF(vec) \ + grid [(int) (vec.x * invcell) + ncols * (int) (vec.y * invcell)] + +#define GRIDI(vec) grid [(int) vec.y * ncols + (int) vec.x] + +static parsl_position* par__generate_pts(float width, float height, + float radius, int seed, parsl_position* result) { + + int maxattempts = 30; + float rscale = 1.0f / UINT_MAX; + parsl_position rvec; + rvec.x = rvec.y = radius; + float r2 = radius * radius; + + // Acceleration grid. + float cellsize = radius / sqrtf(2); + float invcell = 1.0f / cellsize; + int ncols = ceil(width * invcell); + int nrows = ceil(height * invcell); + int maxcol = ncols - 1; + int maxrow = nrows - 1; + int ncells = ncols * nrows; + int* grid = (int*) PAR_MALLOC(int, ncells); + for (int i = 0; i < ncells; i++) { + grid[i] = -1; + } + + // Active list and resulting sample list. + int* actives = (int*) PAR_MALLOC(int, ncells); + int nactives = 0; + + pa_clear(result); + pa_add(result, ncells); + + parsl_position* samples = result; + int nsamples = 0; + + // First sample. + parsl_position pt; + pt.x = width * par__randhash(seed++) * rscale; + pt.y = height * par__randhash(seed++) * rscale; + GRIDF(pt) = actives[nactives++] = nsamples; + samples[nsamples++] = pt; + + while (nsamples < ncells) { + int aindex = PAR_MIN(par__randhashf(seed++, 0, nactives), + nactives - 1.0f); + int sindex = actives[aindex]; + int found = 0; + parsl_position j, minj, maxj, delta; + int attempt; + for (attempt = 0; attempt < maxattempts && !found; attempt++) { + pt = par__sample_annulus(radius, samples[sindex], &seed); + + // Check that this sample is within bounds. + if (pt.x < 0 || pt.x >= width || pt.y < 0 || pt.y >= height) { + continue; + } + + // Test proximity to nearby samples. + maxj = parsl_mul(parsl__add(pt, rvec), invcell); + minj = parsl_mul(parsl__sub(pt, rvec), invcell); + + minj.x = PAR_CLAMP((int) minj.x, 0, maxcol); + minj.y = PAR_CLAMP((int) minj.y, 0, maxrow); + maxj.x = PAR_CLAMP((int) maxj.x, 0, maxcol); + maxj.y = PAR_CLAMP((int) maxj.y, 0, maxrow); + int reject = 0; + for (j.y = minj.y; j.y <= maxj.y && !reject; j.y++) { + for (j.x = minj.x; j.x <= maxj.x && !reject; j.x++) { + int entry = GRIDI(j); + if (entry > -1 && entry != sindex) { + delta = parsl__sub(samples[entry], pt); + if (parsl__dot(delta, delta) < r2) { + reject = 1; + } + } + } + } + if (reject) { + continue; + } + found = 1; + } + if (found) { + GRIDF(pt) = actives[nactives++] = nsamples; + samples[nsamples++] = pt; + } else { + if (--nactives <= 0) { + break; + } + actives[aindex] = actives[nactives]; + } + } + + pa___n(result) = nsamples * 2; + + PAR_FREE(grid); + PAR_FREE(actives); + return result; +} + +#undef GRIDF +#undef GRIDI + +parsl_mesh* parsl_mesh_from_streamlines(parsl_context* context, + parsl_advection_callback advect, uint32_t first_tick, uint32_t num_ticks, + void* userdata) +{ + const int seed = 42; + const parsl_viewport vp = context->config.streamlines_seed_viewport; + const float radius = context->config.streamlines_seed_spacing; + float width = vp.right - vp.left; + float height = vp.bottom - vp.top; + + if (context->streamline_seeds == NULL) { + context->streamline_seeds = par__generate_pts(width, height, radius, + seed, context->streamline_seeds); + uint32_t num_points = pa_count(context->streamline_seeds); + for (uint32_t p = 0; p < num_points; ++p) { + context->streamline_seeds[p].x += vp.left; + context->streamline_seeds[p].y += vp.top; + } + } + + uint32_t num_points = pa_count(context->streamline_seeds); + pa_clear(context->streamline_points); + pa_add(context->streamline_points, num_points); + + parsl_position* points = context->streamline_points; + memcpy(points, context->streamline_seeds, + num_points * sizeof(parsl_position)); + + context->streamline_spines.num_spines = num_points; + pa_clear(context->streamline_spines.spine_lengths); + pa_add(context->streamline_spines.spine_lengths, num_points); + uint16_t* lengths = context->streamline_spines.spine_lengths; + for (uint32_t i = 0; i < num_points; i++) { + lengths[i] = num_ticks; + } + + context->streamline_spines.num_vertices = num_points * num_ticks; + pa_clear(context->streamline_spines.vertices); + pa_add(context->streamline_spines.vertices, num_points * num_ticks); + parsl_position* vertices = context->streamline_spines.vertices; + + for (uint32_t tick = 0; tick < first_tick; tick++) { + for (uint32_t i = 0; i < num_points; i++) { + advect(&points[i], userdata); + } + } + + parsl_position* pvertices = vertices; + for (uint32_t i = 0; i < num_points; i++) { + for (uint32_t tick = 0; tick < num_ticks; ++tick) { + advect(&points[i], userdata); + *pvertices++ = points[i]; + } + } + + parsl_mesh_from_lines(context, context->streamline_spines); + return &context->result; +} + +#endif // PAR_STREAMLINES_IMPLEMENTATION +#endif // PAR_STREAMLINES_H + +// par_streamlines is distributed under the MIT license: +// +// Copyright (c) 2019 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/source/engine/thirdparty/par/par_string_blocks.h b/source/engine/thirdparty/par/par_string_blocks.h new file mode 100644 index 0000000..52a4b6d --- /dev/null +++ b/source/engine/thirdparty/par/par_string_blocks.h @@ -0,0 +1,468 @@ +// STRING_BLOCKS :: https://github.com/prideout/par +// String extraction and concatenation, especially useful for snippets of GLSL or Lua. +// +// This little library extracts blocks of text from a memory blob or file, then lets you retrieve +// them by name. It also makes it easy to glue together a sequence of blocks. +// +// Each block of text is assigned a name using a prefix line that starts with three dash characters, +// such as "--- the_name" or "--- my.block". +// +// For example, suppose you have a file called "shaders.glsl" that looks like this: +// +// --- my_shader +// void main() { ... } +// --- common +// uniform vec4 resolution; +// uniform vec4 color; +// +// You can use this library to read in the file and extract one of the blocks: +// +// parsb_context* blocks = parsb_create_context((parsb_options){}); +// parsb_add_blocks_from_file(blocks, "shaders.glsl"); +// const char* single = parsb_get_blocks(blocks, "my_shader"); +// +// You can also concatenate blocks using a space-delimited list of names: +// +// const char* concatenated = parsb_get_blocks(blocks, "common my_shader"); +// +// You can also add or replace blocks on the fly: +// +// parsb_add_block(blocks, "prefix", "#version 330\n"); +// const char* concatenated = parsb_get_blocks(blocks, "prefix common my_shader"); +// +// The "blocks" context in the above examples holds a cache of generated strings, so be sure to +// destroy it when you're done: +// +// parsb_destroy_context(blocks); +// +// Distributed under the MIT License, see bottom of file. + +#ifndef PAR_STRING_BLOCKS_H +#define PAR_STRING_BLOCKS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// OPTIONS +// ------- +// line_directives ... adds #line annotations into concatenated strings for better error messages. +typedef struct parsb_options { + bool line_directives; +} parsb_options; + +// CONTEXT CREATION AND DESTRUCTION +// -------------------------------- +// A context is an opaque handle to a memory arena. All generated strings are owned by the context +// and freed when the context is destroyed. +typedef struct parsb_context_s parsb_context; +parsb_context* parsb_create_context(parsb_options); +void parsb_destroy_context(parsb_context*); + +// ADDING AND REPLACING BLOCKS +// --------------------------- +// When using the plural form (add_blocks), the submitted buffer may contain multiple blocks, each +// with a name defined by its closest preceding triple-dash line. If a block with the specified name +// already exists, it gets replaced. +// +// The singular form (add_block) adds a single block whose name is explicitly specified as an +// argument. Again, if a block with the given name already exists, it gets replaced. +// +// These functions do not retain the passed-in strings so clients can free them after pushing them. +void parsb_add_blocks(parsb_context*, const char* buffer, int buffer_size); +void parsb_add_block(parsb_context*, const char* name, const char* body); +#ifndef PARSB_NO_STDIO +void parsb_add_blocks_from_file(parsb_context* context, const char* filename); +#endif + +// EXTRACTING AND CONCATENATING BLOCKS +// ----------------------------------- +// The block_names string is a space-separated list of block names that are being requested. The +// returned string is owned by the context, so please make a copy if you need it to outlive the +// context. If the returned string is null, then one or more of the block names could not be found. +const char* parsb_get_blocks(parsb_context*, const char* block_names); + +// GETTING BLOCKS BY INDEX +// ----------------------- +int parsb_get_num_blocks(const parsb_context*); +void parsb_get_block(const parsb_context*, int index, const char** name, const char** body); + +// SAVING THE BLOCK LIST +// --------------------- +// These functions export the entire "database" of atomic blocks. +typedef void (*parsb_write_line)(const char* line, void* userdata); +void parsb_write_blocks(parsb_context*, parsb_write_line writefn, void* user); +#ifndef PARSB_NO_STDIO +void parsb_write_blocks_to_file(parsb_context*, const char* filename); +#endif + +#ifndef PARSB_MAX_NUM_BLOCKS +#define PARSB_MAX_NUM_BLOCKS 128 +#endif + +#ifndef PARSB_MAX_NAME_LENGTH +#define PARSB_MAX_NAME_LENGTH 256 +#endif + +#ifndef PARSB_MAX_LINE_LENGTH +#define PARSB_MAX_LINE_LENGTH 256 +#endif + +#ifdef __cplusplus +} +#endif + +// ----------------------------------------------------------------------------- +// END PUBLIC API +// ----------------------------------------------------------------------------- + +#ifdef PAR_STRING_BLOCKS_IMPLEMENTATION + +#include +#include +#include +#include + +#ifndef PARSB_NO_STDIO +#include +#endif + +#define PARSB_MIN(a, b) (a > b ? b : a) + +typedef struct { + int count; + char* values[PARSB_MAX_NUM_BLOCKS]; + char* names[PARSB_MAX_NUM_BLOCKS]; +} parsb__list; + +struct parsb_context_s { + parsb_options options; + parsb__list blocks; + parsb__list results; +}; + +static char* parsb__add_or_replace(parsb_context*, const char* id, const char* value, + int value_size, int line_number); +static char* parsb__list_add(parsb__list*, const char* id, const char* value, int value_size, + int line_number); +static char* parsb__list_get(parsb__list*, const char* id, int idlen); +static void parsb__list_free(parsb__list* ); + +parsb_context* parsb_create_context(parsb_options options) { + parsb_context* context = (parsb_context*) calloc(1, sizeof(parsb_context)); + context->options = options; + return context; +} + +void parsb_destroy_context(parsb_context* context) { + parsb__list_free(&context->blocks); + parsb__list_free(&context->results); + free(context); +} + +void parsb_add_blocks(parsb_context* context, const char* blob, int buffer_size) { + const char* previous_block = 0; + char previous_name[PARSB_MAX_NAME_LENGTH]; + int line_number = 0; + int block_line_number = 0; + for (int i = 0; i < buffer_size - 4; i++) { + if (blob[i] != '-' || blob[i + 1] != '-' || blob[i + 2] != '-' || blob[i + 3] != ' ') { + if (blob[i] == '\n') { + line_number++; + } + continue; + } + if (previous_block) { + parsb__add_or_replace(context, previous_name, previous_block, + i - (previous_block - blob), block_line_number); + } + i += 4; + const char* name = blob + i; + const char* block_start = 0; + for (; i < buffer_size; i++) { + if (blob[i] == '\n') { + line_number++; + int name_length = i - (name - blob); + memcpy(previous_name, name, name_length); + block_line_number = line_number + 2; + previous_name[name_length] = 0; + block_start = blob + i + 1; + break; + } + if (isspace(blob[i])) { + int name_length = i - (name - blob); + memcpy(previous_name, name, name_length); + block_line_number = line_number + 2; + previous_name[name_length] = 0; + for (i++; i < buffer_size; i++) { + if (blob[i] == '\n') { + line_number++; + block_start = blob + i + 1; + break; + } + } + break; + } + } + if (block_start == 0) { + return; + } + previous_block = block_start; + } + if (previous_block) { + parsb__add_or_replace(context, previous_name, previous_block, + buffer_size - (previous_block - blob), block_line_number); + } +} + +void parsb_add_block(parsb_context* context, const char* name, const char* body) { + char* dup = strdup(body); + parsb__add_or_replace(context, name, dup, 1 + strlen(body), 0); +} + +const char* parsb_get_blocks(parsb_context* context, const char* block_names) { + int len = strlen(block_names); + const char* name = block_names; + int name_length = 0; + int result_length = 0; + + // First pass determines the amount of required memory. + int num_names = 0; + for (int i = 0; i < len; i++) { + char c = block_names[i]; + if (isspace(c) || !c) { + const char* block = parsb__list_get(&context->blocks, name, name_length); + if (block) { + result_length += strlen(block); + num_names++; + } else { + return NULL; + } + name_length = 0; + name = block_names + i + 1; + } else { + name_length++; + } + } + const char* block = parsb__list_get(&context->blocks, name, name_length); + if (block) { + result_length += strlen(block); + num_names++; + } + + // If no concatenation is required, return early. + if (num_names == 1) { + return parsb__list_get(&context->blocks, name, name_length); + } + + // Allocate storage for the result. + char* result = parsb__list_add(&context->results, 0, 0, result_length, 0); + char* cursor = result; + + // Second pass populates the result. + name = block_names; + name_length = 0; + for (int i = 0; i < len; i++) { + char c = block_names[i]; + if (isspace(c) || !c) { + const char* block = parsb__list_get(&context->blocks, name, name_length); + if (block) { + memcpy(cursor, block, strlen(block)); + cursor += strlen(block); + } + name_length = 0; + name = block_names + i + 1; + } else { + name_length++; + } + } + block = parsb__list_get(&context->blocks, name, name_length); + if (block) { + memcpy(cursor, block, strlen(block)); + cursor += strlen(block); + } + return result; +} + +int parsb_get_num_blocks(const parsb_context* context) { + return context->blocks.count; +} + +void parsb_get_block(const parsb_context* context, int index, const char** name, + const char** body) { + if (index < 0 || index >= context->blocks.count) { + return; + } + *name = context->blocks.names[index]; + *body = context->blocks.values[index]; +} + +void parsb_write_blocks(parsb_context* context, parsb_write_line writefn, void* userdata) { + char line[PARSB_MAX_LINE_LENGTH + 1] = {0}; + for (int i = 0; i < context->blocks.count; i++) { + + sprintf(line, "--- %s", context->blocks.names[i]); + writefn(line, userdata); + + const char* cursor = context->blocks.values[i]; + const int blocklen = strlen(cursor); + int previous = 0; + for (int i = 0; i < blocklen; i++) { + if (cursor[i] == '\n') { + int line_length = PARSB_MIN(i - previous, PARSB_MAX_LINE_LENGTH); + memcpy(line, cursor + previous, line_length); + line[line_length] = 0; + writefn(line, userdata); + previous = i + 1; + } else if (i == blocklen - 1) { + int line_length = PARSB_MIN(1 + i - previous, PARSB_MAX_LINE_LENGTH); + memcpy(line, cursor + previous, line_length); + line[line_length] = 0; + writefn(line, userdata); + previous = i + 1; + } + } + } +} + +static char* parsb__add_or_replace(parsb_context* context, const char* id, const char* value, + int value_size, int line_number) { + line_number = context->options.line_directives ? line_number : 0; + const size_t idlen = strlen(id); + for (int i = 0; i < context->blocks.count; i++) { + if (strncmp(id, context->blocks.names[i], idlen) == 0) { + free(context->blocks.values[i]); + context->blocks.values[i] = strndup(value, value_size); + return context->blocks.values[i]; + } + } + return parsb__list_add(&context->blocks, id, value, value_size, line_number); +} + +static char* parsb__list_add(parsb__list* list, const char* name, + const char* value, int value_size, int line_number) { + if (value_size == 0) { + return NULL; + } + + if (list->count == PARSB_MAX_NUM_BLOCKS) { + assert(false && "Please increase PARSB_MAX_NUM_BLOCKS."); + return NULL; + } + + char* storage; + char* cursor; + + if (line_number > 0) { + char line_directive[16] = {0}; + int prefix_length = snprintf(line_directive, 16, "\n#line %d\n", line_number); + storage = (char*) calloc(1, prefix_length + value_size + 1); + memcpy(storage, line_directive, prefix_length); + cursor = storage + prefix_length; + } else { + storage = cursor = (char*) calloc(1, value_size + 1); + } + + if (value) { + memcpy(cursor, value, value_size); + } + + #if PARSB_ENABLE_TRIM + value_size--; + while (isspace(cursor[value_size])) { + cursor[value_size] = 0; + value_size--; + if (value_size == 0) { + break; + } + } + #endif + + if (name) { + list->names[list->count] = strdup(name); + } else { + list->names[list->count] = 0; + } + + list->values[list->count] = storage; + list->count++; + return storage; +} + +static char* parsb__list_get(parsb__list* list, const char* name, int idlen) { + for (int i = 0; i < list->count; i++) { + if (strncmp(name, list->names[i], idlen) == 0) { + return list->values[i]; + } + } + return NULL; +} + +static void parsb__list_free(parsb__list* list) { + for (int i = 0; i < list->count; i++) { + free(list->names[i]); + free(list->values[i]); + } + list->count = 0; +} + +#ifndef PARSB_NO_STDIO + +void parsb_add_blocks_from_file(parsb_context* context, const char* filename) { + FILE* f = fopen(filename, "r"); + if (!f) { + fprintf(stderr, "Unable to open %s\n", filename); + return; + } + fseek(f, 0, SEEK_END); + int length = ftell(f); + fseek(f, 0, SEEK_SET); + char* buffer = (char*) malloc(length); + fread(buffer, 1, length, f); + fclose(f); + parsb_add_blocks(context, buffer, length); + free(buffer); +} + +static void writefn(const char* line, void* userdata) { + fprintf((FILE*) userdata, "%s\n", line); +} + +void parsb_write_blocks_to_file(parsb_context* context, const char* filename) { + FILE* f = fopen(filename, "w"); + if (!f) { + fprintf(stderr, "Unable to open %s\n", filename); + return; + } + parsb_write_blocks(context, writefn, f); + fclose(f); +} + +#endif + +#endif // PAR_STRING_BLOCKS_IMPLEMENTATION +#endif // PAR_STRING_BLOCKS_H + +// par_string_blocks is distributed under the MIT license: +// +// Copyright (c) 2020 Philip Rideout +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE.