par and mipmaps
This commit is contained in:
parent
e0f3985b00
commit
9a325543ae
|
@ -11,6 +11,9 @@
|
||||||
#include "stb_ds.h"
|
#include "stb_ds.h"
|
||||||
#include "sokol/sokol_gfx.h"
|
#include "sokol/sokol_gfx.h"
|
||||||
|
|
||||||
|
#define PAR_STREAMLINES_IMPLEMENTATION
|
||||||
|
#include "par/par_streamlines.h"
|
||||||
|
|
||||||
#include "font.h"
|
#include "font.h"
|
||||||
|
|
||||||
static sg_pipeline grid_pipe;
|
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)
|
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[0] = color[0];
|
||||||
cv[1] = color[1];
|
cv[1] = color[1];
|
||||||
cv[2] = color[2];
|
cv[2] = color[2];
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stb_ds.h>
|
#include <stb_ds.h>
|
||||||
#include <stb_image.h>
|
#include <stb_image.h>
|
||||||
|
|
||||||
|
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||||
|
#include "stb_image_resize.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
struct glrect ST_UNIT = {0.f, 1.f, 0.f, 1.f};
|
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");
|
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 */
|
/* If an empty string or null is put for path, loads default texture */
|
||||||
struct Texture *texture_pullfromfile(const char *path) {
|
struct Texture *texture_pullfromfile(const char *path) {
|
||||||
if (!path) return texture_notex();
|
if (!path) return texture_notex();
|
||||||
|
@ -51,18 +67,42 @@ struct Texture *texture_pullfromfile(const char *path) {
|
||||||
} else {
|
} else {
|
||||||
filter = SG_FILTER_LINEAR;
|
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){
|
tex->id = sg_make_image(&(sg_image_desc){
|
||||||
.type = SG_IMAGETYPE_2D,
|
.type = SG_IMAGETYPE_2D,
|
||||||
.width = tex->width,
|
.width = tex->width,
|
||||||
.height = tex->height,
|
.height = tex->height,
|
||||||
.usage = SG_USAGE_IMMUTABLE,
|
.usage = SG_USAGE_IMMUTABLE,
|
||||||
.min_filter = filter,
|
.min_filter = SG_FILTER_NEAREST_MIPMAP_NEAREST,
|
||||||
.mag_filter = filter,
|
.mag_filter = SG_FILTER_NEAREST,
|
||||||
.max_anisotropy = 16,
|
.num_mipmaps = mips,
|
||||||
.data.subimage[0][0] = {
|
.data = sg_img_data
|
||||||
.ptr = data,
|
});
|
||||||
.size = tex->width * tex->height * 4}});
|
|
||||||
|
|
||||||
if (shlen(texhash) == 0)
|
if (shlen(texhash) == 0)
|
||||||
sh_new_arena(texhash);
|
sh_new_arena(texhash);
|
||||||
|
|
563
source/engine/thirdparty/par/par_bluenoise.h
vendored
Normal file
563
source/engine/thirdparty/par/par_bluenoise.h
vendored
Normal file
|
@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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.
|
1451
source/engine/thirdparty/par/par_bubbles.h
vendored
Normal file
1451
source/engine/thirdparty/par/par_bubbles.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
1095
source/engine/thirdparty/par/par_camera_control.h
vendored
Normal file
1095
source/engine/thirdparty/par/par_camera_control.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
429
source/engine/thirdparty/par/par_easings.h
vendored
Normal file
429
source/engine/thirdparty/par/par_easings.h
vendored
Normal file
|
@ -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.
|
228
source/engine/thirdparty/par/par_easycurl.h
vendored
Normal file
228
source/engine/thirdparty/par/par_easycurl.h
vendored
Normal file
|
@ -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 <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define strncasecmp _strnicmp
|
||||||
|
#define strcasecmp _stricmp
|
||||||
|
#else
|
||||||
|
#include <strings.h>
|
||||||
|
#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.
|
458
source/engine/thirdparty/par/par_filecache.h
vendored
Normal file
458
source/engine/thirdparty/par/par_filecache.h
vendored
Normal file
|
@ -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 <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// 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 <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#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.
|
2156
source/engine/thirdparty/par/par_msquares.h
vendored
Normal file
2156
source/engine/thirdparty/par/par_msquares.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
577
source/engine/thirdparty/par/par_octasphere.h
vendored
Normal file
577
source/engine/thirdparty/par/par_octasphere.h
vendored
Normal file
|
@ -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 <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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 <assert.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <memory.h> // 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.
|
2153
source/engine/thirdparty/par/par_shapes.h
vendored
Normal file
2153
source/engine/thirdparty/par/par_shapes.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
476
source/engine/thirdparty/par/par_sprune.h
vendored
Normal file
476
source/engine/thirdparty/par/par_sprune.h
vendored
Normal file
|
@ -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 <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#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 <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#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; x<xend; x++, y++) {
|
||||||
|
ch = *x; *x = *y; *y = ch;
|
||||||
|
}
|
||||||
|
pl = b;
|
||||||
|
pr = last;
|
||||||
|
while (pl < pr) {
|
||||||
|
for (; pl < pr; pl += w) {
|
||||||
|
if (par_qsort_cmpswap(pl, pr, w, compar, arg)) {
|
||||||
|
pr -= w;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; pl < pr; pr -= w) {
|
||||||
|
if (par_qsort_cmpswap(pl, pr, w, compar, arg)) {
|
||||||
|
pl += w;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
par_qsort(b, (pl-b) / w, w, compar, arg);
|
||||||
|
par_qsort(pl+w, (end - (pl+w)) / w, w, compar, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void par_sprune_free_context(par_sprune_context* context)
|
||||||
|
{
|
||||||
|
par_sprune__context* ctx = (par_sprune__context*) context;
|
||||||
|
pa_free(ctx->sorted_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.
|
1259
source/engine/thirdparty/par/par_streamlines.h
vendored
Normal file
1259
source/engine/thirdparty/par/par_streamlines.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
468
source/engine/thirdparty/par/par_string_blocks.h
vendored
Normal file
468
source/engine/thirdparty/par/par_string_blocks.h
vendored
Normal file
|
@ -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 <stdbool.h>
|
||||||
|
|
||||||
|
// 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 <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef PARSB_NO_STDIO
|
||||||
|
#include <stdio.h>
|
||||||
|
#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.
|
Loading…
Reference in a new issue