pxl8/src/procgen/pxl8_graph.c

333 lines
10 KiB
C
Raw Normal View History

#include "pxl8_graph.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "pxl8_math.h"
#include "pxl8_mem.h"
static inline u32 hash2d(i32 x, i32 y, u32 seed) {
return pxl8_hash32((u32)x ^ ((u32)y * 2654435769u) ^ seed);
}
static inline f32 hash2d_f(i32 x, i32 y, u32 seed) {
return (f32)hash2d(x, y, seed) / (f32)0xFFFFFFFF;
}
static f32 gradient2d(i32 ix, i32 iy, f32 fx, f32 fy, u32 seed) {
u32 h = hash2d(ix, iy, seed);
f32 angle = (f32)h / (f32)0xFFFFFFFF * 6.28318530718f;
f32 gx = cosf(angle);
f32 gy = sinf(angle);
return gx * fx + gy * fy;
}
static inline f32 smoothstep(f32 t) {
return t * t * (3.0f - 2.0f * t);
}
static inline f32 lerp(f32 a, f32 b, f32 t) {
return a + t * (b - a);
}
static f32 noise_value(f32 x, f32 y, f32 scale, u32 seed) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 ix = (i32)floorf(sx);
i32 iy = (i32)floorf(sy);
f32 fx = sx - (f32)ix;
f32 fy = sy - (f32)iy;
f32 u = smoothstep(fx);
f32 v = smoothstep(fy);
f32 n00 = hash2d_f(ix, iy, seed);
f32 n10 = hash2d_f(ix + 1, iy, seed);
f32 n01 = hash2d_f(ix, iy + 1, seed);
f32 n11 = hash2d_f(ix + 1, iy + 1, seed);
return lerp(lerp(n00, n10, u), lerp(n01, n11, u), v);
}
static f32 noise_perlin(f32 x, f32 y, f32 scale, u32 seed) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 ix = (i32)floorf(sx);
i32 iy = (i32)floorf(sy);
f32 fx = sx - (f32)ix;
f32 fy = sy - (f32)iy;
f32 u = smoothstep(fx);
f32 v = smoothstep(fy);
f32 n00 = gradient2d(ix, iy, fx, fy, seed);
f32 n10 = gradient2d(ix + 1, iy, fx - 1.0f, fy, seed);
f32 n01 = gradient2d(ix, iy + 1, fx, fy - 1.0f, seed);
f32 n11 = gradient2d(ix + 1, iy + 1, fx - 1.0f, fy - 1.0f, seed);
f32 result = lerp(lerp(n00, n10, u), lerp(n01, n11, u), v);
return result * 0.5f + 0.5f;
}
static f32 noise_fbm(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
value += amplitude * noise_perlin(x, y, frequency, seed + (u32)i * 1337);
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static f32 noise_ridged(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
f32 n = noise_perlin(x, y, frequency, seed + (u32)i * 1337);
n = 1.0f - fabsf(n * 2.0f - 1.0f);
value += amplitude * n;
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static f32 noise_turbulence(f32 x, f32 y, i32 octaves, f32 scale, f32 persistence, u32 seed) {
f32 value = 0.0f;
f32 amplitude = 1.0f;
f32 frequency = scale;
f32 max_value = 0.0f;
for (i32 i = 0; i < octaves; i++) {
f32 n = noise_perlin(x, y, frequency, seed + (u32)i * 1337);
value += amplitude * fabsf(n * 2.0f - 1.0f);
max_value += amplitude;
amplitude *= persistence;
frequency *= 2.0f;
}
return value / max_value;
}
static void voronoi(f32 x, f32 y, f32 scale, u32 seed, f32* cell_dist, f32* edge_dist, i32* cell_id) {
f32 sx = x * scale;
f32 sy = y * scale;
i32 cx = (i32)floorf(sx);
i32 cy = (i32)floorf(sy);
f32 fx = sx - (f32)cx;
f32 fy = sy - (f32)cy;
f32 min_dist = 1e30f;
f32 second_dist = 1e30f;
i32 closest_id = 0;
for (i32 dy = -1; dy <= 1; dy++) {
for (i32 dx = -1; dx <= 1; dx++) {
i32 nx = cx + dx;
i32 ny = cy + dy;
u32 h = hash2d(nx, ny, seed);
f32 px = (f32)dx + (f32)(h & 0xFF) / 255.0f - 0.5f - fx;
f32 py = (f32)dy + (f32)((h >> 8) & 0xFF) / 255.0f - 0.5f - fy;
f32 dist = px * px + py * py;
if (dist < min_dist) {
second_dist = min_dist;
min_dist = dist;
closest_id = (i32)h;
} else if (dist < second_dist) {
second_dist = dist;
}
}
}
*cell_dist = sqrtf(min_dist);
*edge_dist = sqrtf(second_dist) - sqrtf(min_dist);
*cell_id = closest_id;
}
static f32 gradient_linear(f32 x, f32 y, f32 angle) {
f32 dx = cosf(angle);
f32 dy = sinf(angle);
f32 result = x * dx + y * dy;
return fmaxf(0.0f, fminf(1.0f, result));
}
static f32 gradient_radial(f32 x, f32 y, f32 cx, f32 cy) {
f32 dx = x - cx;
f32 dy = y - cy;
return fmaxf(0.0f, fminf(1.0f, sqrtf(dx * dx + dy * dy)));
}
pxl8_graph* pxl8_graph_create(u32 capacity) {
pxl8_graph* graph = pxl8_calloc(1, sizeof(pxl8_graph));
if (!graph) return NULL;
graph->nodes = pxl8_calloc(capacity, sizeof(pxl8_node));
if (!graph->nodes) {
pxl8_free(graph);
return NULL;
}
graph->capacity = capacity;
graph->count = 0;
graph->seed = 0;
graph->output_reg = 0;
return graph;
}
void pxl8_graph_destroy(pxl8_graph* graph) {
if (!graph) return;
pxl8_free(graph->nodes);
pxl8_free(graph);
}
void pxl8_graph_clear(pxl8_graph* graph) {
if (!graph) return;
graph->count = 0;
graph->output_reg = 0;
}
u8 pxl8_graph_add_node(pxl8_graph* graph, pxl8_graph_op op, u8 in0, u8 in1, u8 in2, u8 in3, f32 param) {
if (!graph || graph->count >= graph->capacity) return 0;
u8 out = (u8)(graph->count + 4);
pxl8_node* node = &graph->nodes[graph->count++];
node->op = (u8)op;
node->out = out;
node->in[0] = in0;
node->in[1] = in1;
node->in[2] = in2;
node->in[3] = in3;
node->param = param;
return out;
}
void pxl8_graph_set_output(pxl8_graph* graph, u8 reg) {
if (graph) graph->output_reg = reg;
}
void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed) {
if (graph) graph->seed = seed;
}
f32 pxl8_graph_eval(const pxl8_graph* graph, pxl8_graph_context* ctx) {
if (!graph || !ctx) return 0.0f;
for (u32 i = 0; i < graph->count; i++) {
const pxl8_node* n = &graph->nodes[i];
f32 a = ctx->regs[n->in[0]];
f32 b = ctx->regs[n->in[1]];
f32 c = ctx->regs[n->in[2]];
f32 d = ctx->regs[n->in[3]];
f32 result = 0.0f;
switch (n->op) {
case PXL8_OP_CONST: result = n->param; break;
case PXL8_OP_INPUT_AGE: result = ctx->regs[3]; break;
case PXL8_OP_INPUT_SEED: result = (f32)ctx->seed; break;
case PXL8_OP_INPUT_TIME: result = ctx->regs[2]; break;
case PXL8_OP_INPUT_X: result = ctx->regs[0]; break;
case PXL8_OP_INPUT_Y: result = ctx->regs[1]; break;
case PXL8_OP_ABS: result = fabsf(a); break;
case PXL8_OP_CEIL: result = ceilf(a); break;
case PXL8_OP_COS: result = cosf(a); break;
case PXL8_OP_FLOOR: result = floorf(a); break;
case PXL8_OP_FRACT: result = a - floorf(a); break;
case PXL8_OP_NEGATE: result = -a; break;
case PXL8_OP_SIN: result = sinf(a); break;
case PXL8_OP_SQRT: result = sqrtf(a); break;
case PXL8_OP_ADD: result = a + b; break;
case PXL8_OP_DIV: result = b != 0.0f ? a / b : 0.0f; break;
case PXL8_OP_MAX: result = fmaxf(a, b); break;
case PXL8_OP_MIN: result = fminf(a, b); break;
case PXL8_OP_MOD: result = b != 0.0f ? fmodf(a, b) : 0.0f; break;
case PXL8_OP_MUL: result = a * b; break;
case PXL8_OP_POW: result = powf(a, b); break;
case PXL8_OP_SUB: result = a - b; break;
case PXL8_OP_CLAMP: result = fmaxf(b, fminf(c, a)); break;
case PXL8_OP_LERP: result = a + c * (b - a); break;
case PXL8_OP_SELECT: result = c > 0.0f ? a : b; break;
case PXL8_OP_SMOOTHSTEP: {
f32 t = fmaxf(0.0f, fminf(1.0f, (c - a) / (b - a)));
result = t * t * (3.0f - 2.0f * t);
break;
}
case PXL8_OP_NOISE_FBM: result = noise_fbm(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_PERLIN: result = noise_perlin(a, b, c, ctx->seed); break;
case PXL8_OP_NOISE_RIDGED: result = noise_ridged(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_TURBULENCE:result = noise_turbulence(a, b, (i32)n->param, c, d, ctx->seed); break;
case PXL8_OP_NOISE_VALUE: result = noise_value(a, b, c, ctx->seed); break;
case PXL8_OP_VORONOI_CELL: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = cell;
break;
}
case PXL8_OP_VORONOI_EDGE: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = edge;
break;
}
case PXL8_OP_VORONOI_ID: {
f32 cell, edge; i32 id;
voronoi(a, b, c, ctx->seed, &cell, &edge, &id);
result = (f32)(id & 0xFF) / 255.0f;
break;
}
case PXL8_OP_GRADIENT_LINEAR: result = gradient_linear(a, b, c); break;
case PXL8_OP_GRADIENT_RADIAL: result = gradient_radial(a, b, c, d); break;
case PXL8_OP_QUANTIZE: {
u8 base = (u8)n->param;
f32 range = b;
f32 clamped = fmaxf(0.0f, fminf(1.0f, a));
result = (f32)(base + (u8)fminf(clamped * range, range - 1.0f));
break;
}
default: break;
}
ctx->regs[n->out] = result;
}
return ctx->regs[graph->output_reg];
}
void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height) {
if (!graph || !buffer) return;
pxl8_graph_context ctx = {0};
ctx.seed = graph->seed;
for (i32 y = 0; y < height; y++) {
for (i32 x = 0; x < width; x++) {
ctx.regs[0] = (f32)x / (f32)width;
ctx.regs[1] = (f32)y / (f32)height;
ctx.regs[2] = 0.0f;
ctx.regs[3] = 0.0f;
f32 result = pxl8_graph_eval(graph, &ctx);
buffer[y * width + x] = (u8)fmaxf(0.0f, fminf(255.0f, result));
}
}
}