// This file is part of the 64k demo project. // It implements basic procedural texture generators. #include "procedural/generator.h" #include #include namespace procedural { // Smoothstep constexpr float smooth(float x) { return x * x * (3.f - 2.f * x); } constexpr float mix(float a, float b, float t) { return (a * (1.0f - t) + b * t); } // Fractional part static inline float fract(float x) { return x - floorf(x); } // Hash function: vec2 -> float (deterministic, no rand()) // Matches WGSL hash_2f() behavior static float hash_2f(float x, float y) { const float h = x * 127.1f + y * 311.7f; return fract(sinf(h) * 43758.5453123f); } // Value noise: 2D (matches WGSL noise_2d) static float noise_2d(float x, float y) { const float ix = floorf(x); const float iy = floorf(y); const float fx = fract(x); const float fy = fract(y); const float u = smooth(fx); const float v = smooth(fy); const float n00 = hash_2f(ix + 0.0f, iy + 0.0f); const float n10 = hash_2f(ix + 1.0f, iy + 0.0f); const float n01 = hash_2f(ix + 0.0f, iy + 1.0f); const float n11 = hash_2f(ix + 1.0f, iy + 1.0f); const float ix0 = mix(n00, n10, u); const float ix1 = mix(n01, n11, u); return mix(ix0, ix1, v); } // Perlin noise generator (Fractional Brownian Motion using value noise) // Params[0]: Seed (offset for hash function) // Params[1]: Frequency (Scale) // Params[2]: Amplitude // Params[3]: Amplitude decay // Params[4]: Number of octaves bool gen_perlin(uint8_t* buffer, int w, int h, const float* params, int num_params) { const float seed = (num_params > 0) ? params[0] : 0.0f; const float base_freq = (num_params > 1) ? params[1] : 4.0f; const float base_amp = (num_params > 2) ? params[2] : 1.0f; const float amp_decay = (num_params > 3) ? params[3] : 0.5f; const int octaves = (num_params > 4) ? (int)params[4] : 4; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { float value = 0.0f; float amplitude = base_amp; float frequency = base_freq; float total_amp = 0.0f; for (int o = 0; o < octaves; ++o) { const float nx = (float)x / (float)w * frequency + seed; const float ny = (float)y / (float)h * frequency + seed; value += noise_2d(nx, ny) * amplitude; total_amp += amplitude; frequency *= 2.0f; amplitude *= amp_decay; } value /= total_amp; const uint8_t uval = (uint8_t)(fminf(fmaxf(value, 0.0f), 1.0f) * 255.0f); const int idx = (y * w + x) * 4; buffer[idx + 0] = uval; buffer[idx + 1] = uval; buffer[idx + 2] = uval; buffer[idx + 3] = 255; } } return true; } // Simple smooth noise generator (Value Noise) // Params[0]: Seed (offset for hash function) // Params[1]: Frequency (Scale) bool gen_noise(uint8_t* buffer, int w, int h, const float* params, int num_params) { if (num_params > 0 && params[0] == -1337.0f) return false; const float seed = (num_params > 0) ? params[0] : 0.0f; const float freq = (num_params > 1) ? params[1] : 4.0f; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const float nx = (float)x / (float)w * freq + seed; const float ny = (float)y / (float)h * freq + seed; const float noise = noise_2d(nx, ny); const uint8_t val = (uint8_t)(noise * 255.0f); const int idx = (y * w + x) * 4; buffer[idx + 0] = val; buffer[idx + 1] = val; buffer[idx + 2] = val; buffer[idx + 3] = 255; } } return true; } // Simple grid generator // Params[0]: Grid Size (pixels) // Params[1]: Line Thickness (pixels) bool gen_grid(uint8_t* buffer, int w, int h, const float* params, int num_params) { int grid_size = (num_params > 0) ? (int)params[0] : 32; int thickness = (num_params > 1) ? (int)params[1] : 2; if (grid_size < 1) grid_size = 32; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { bool on_line = ((x % grid_size) < thickness) || ((y % grid_size) < thickness); int idx = (y * w + x) * 4; uint8_t val = on_line ? 255 : 0; buffer[idx + 0] = val; buffer[idx + 1] = val; buffer[idx + 2] = val; buffer[idx + 3] = 255; } } return true; } bool make_periodic(uint8_t* buffer, int w, int h, const float* params, int num_params) { float ratio = (num_params > 0) ? params[0] : 0.1f; if (ratio <= 0.0f) return true; if (ratio > 0.5f) ratio = 0.5f; const int bx = (int)(w * ratio); const int by = (int)(h * ratio); const float scale_x = 1. / bx; const float scale_y = 1. / by; // X pass: blend right edge into left edge for (int y = 0; y < h; ++y) { for (int x = 0; x < bx; ++x) { const float t = smooth(scale_x * x); const int idx_dst = (y * w + x) * 4; const int idx_src = (y * w + (w - bx + x)) * 4; for (int c = 0; c < 3; ++c) { const float v_dst = buffer[idx_dst + c]; const float v_src = buffer[idx_src + c]; buffer[idx_dst + c] = (uint8_t)mix(v_src, v_dst, t); } } } // Y pass for (int x = 0; x < w; ++x) { for (int y = 0; y < by; ++y) { const float t = smooth(scale_y * y); const int idx_dst = (y * w + x) * 4; const int idx_src = ((h - by + y) * w + x) * 4; for (int c = 0; c < 3; ++c) { float v_dst = buffer[idx_dst + c]; float v_src = buffer[idx_src + c]; buffer[idx_dst + c] = (uint8_t)mix(v_src, v_dst, t); } } } return true; } } // namespace procedural