#include "pxl8_graph.h" #include #include #include #include "pxl8_math.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 = calloc(1, sizeof(pxl8_graph)); if (!graph) return NULL; graph->nodes = calloc(capacity, sizeof(pxl8_node)); if (!graph->nodes) { 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; free(graph->nodes); 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)); } } }