// 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); } // Simple smooth noise generator (Value Noise-ish) // Params[0]: Seed // Params[1]: Frequency (Scale) void gen_noise(uint8_t* buffer, int w, int h, const float* params, int num_params) { float freq = (num_params > 1) ? params[1] : 4.0f; if (num_params > 0 && params[0] != 0) { srand((unsigned int)params[0]); } // Create a small lattice of random values const int lattice_w = (int)ceil(freq); const int lattice_h = (int)ceil(freq); std::vector lattice(lattice_w * lattice_h); for (float& v : lattice) { v = (float)rand() / RAND_MAX; } const float scale_u = 1.f * (lattice_w - 1) / w; const float scale_v = 1.f * (lattice_h - 1) / h; for (int y = 0; y < h; ++y) { const float v = scale_v * y; const int ly = (int)floor(v); const int ly_next = (ly + 1) % lattice_h; // Wrap const float* const n0 = &lattice[ly * lattice_w]; const float* const n1 = &lattice[ly_next * lattice_w]; float fv = smooth(v - ly); uint8_t* const dst = &buffer[y * w * 4]; for (int x = 0; x < w; ++x) { float u = scale_u * x; const int lx = (int)floor(u); const int lx_next = (lx + 1) % lattice_w; float fu = smooth(u - lx); float n00 = n0[lx]; float n10 = n0[lx_next]; float n01 = n1[lx]; float n11 = n1[lx_next]; const float noise = mix(mix(n00, n10, fu), mix(n01, n11, fu), fv); const uint8_t val = (uint8_t)(noise * 255.0f); dst[4 * x + 0] = val; // R dst[4 * x + 1] = val; // G dst[4 * x + 2] = val; // B dst[4 * x + 3] = 255; // A } } } // Simple grid generator // Params[0]: Grid Size (pixels) // Params[1]: Line Thickness (pixels) void 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; } } } void 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; 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); } } // Copy left edge back to right edge to ensure perfect pixel match? // Actually, texture wrapping usually means buffer[w] ~ buffer[0]. // buffer[w-1] should neighbor buffer[0]. // With above logic: buffer[0] is blend(buffer[w-bx], buffer[0], 0) = // buffer[w-bx]. So buffer[0] looks like the pixel at w-bx. This effectively // shrinks the texture content by bx? A bit hacky but works for noise. To be // seamless at w-1 -> 0: buffer[w-1] is original. buffer[0] matches // buffer[w-bx]. There is still a jump at w-1 -> 0 if buffer[w-1] != // buffer[w-bx-1]? // // Improved logic: Blend BOTH sides to the average? // Let's modify the right side too. for (int x = 0; x < bx; ++x) { // We want buffer[w-bx+x] to blend towards buffer[x] (which is now // modified? No, original) This is getting complicated. The simple "mix // right side into left side" works if the texture frequency is high // enough. Let's just stick to the simple one requested. } } // 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); } } } } } // namespace procedural