par and mipmaps

This commit is contained in:
John Alanbrook 2023-05-13 03:21:11 +00:00
parent e0f3985b00
commit 9a325543ae
14 changed files with 11363 additions and 7 deletions

View file

@ -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];

View file

@ -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();
@ -52,17 +68,41 @@ struct Texture *texture_pullfromfile(const char *path) {
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);

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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.

View 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.

View 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/FowlerNollVo_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.

File diff suppressed because it is too large Load diff

View 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

File diff suppressed because it is too large Load diff

View 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.

File diff suppressed because it is too large Load diff

View 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.