diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-08 19:14:55 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-08 19:14:55 +0100 |
| commit | b7d282249394a8c0a48d5dfe5af410b541c724cc (patch) | |
| tree | 19020b59ae03b5029db33cee8932589c92bde11e /src/procedural/generator.cc | |
| parent | 6071c9f6bea2d3890024cb47083b92d9ddbf0447 (diff) | |
refactor(procedural): Use hash-based noise instead of lattice approach
Replaces lattice-based noise generation with deterministic hash functions
matching the WGSL implementation. Eliminates heap allocations and rand()
dependency.
Changes:
- Added hash_2f() and noise_2d() C++ functions (matches WGSL)
- Refactored gen_perlin() to use hash-based FBM (no malloc/free)
- Refactored gen_noise() to use hash_based value noise
- Removed all rand()/srand() calls (now deterministic via seed offset)
- Eliminated lattice allocation/deallocation per octave
Benefits:
- Zero heap allocations (was allocating lattice per octave)
- Deterministic output (seed-based, not rand-based)
- 28% smaller code (270 → 194 lines, -75 lines)
- Matches WGSL noise implementation behavior
- Faster (no malloc overhead, better cache locality)
Testing:
- All 33 tests pass (100%)
- test_procedural validates noise/perlin/grid generation
- No visual regressions
Size Impact: ~200-300 bytes smaller (malloc/free overhead eliminated)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/procedural/generator.cc')
| -rw-r--r-- | src/procedural/generator.cc | 215 |
1 files changed, 70 insertions, 145 deletions
diff --git a/src/procedural/generator.cc b/src/procedural/generator.cc index 41ee661..18ad133 100644 --- a/src/procedural/generator.cc +++ b/src/procedural/generator.cc @@ -15,180 +15,105 @@ 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 +// 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) { - float base_freq = (num_params > 1) ? params[1] : 4.0f; - float base_amp = (num_params > 2) ? params[2] : 1.0f; - float amp_decay = (num_params > 3) ? params[3] : 0.5f; - int octaves = (num_params > 4) ? (int)params[4] : 4; - if (num_params > 0 && params[0] != 0) { - srand((unsigned int)params[0]); - } - - // Pre-allocate temporary float buffer for accumulating noise - float* accum = (float*)calloc((size_t)w * h, sizeof(float)); - if (!accum) - return false; - - float current_freq = base_freq; - float current_amp = base_amp; - float total_amp = 0.0f; - - for (int o = 0; o < octaves; ++o) { - const int lattice_w = (int)ceil(current_freq) + 1; - const int lattice_h = (int)ceil(current_freq) + 1; - float* lattice = - (float*)malloc((size_t)lattice_w * lattice_h * sizeof(float)); - if (!lattice) { - free(accum); - return false; - } - - for (int i = 0; i < lattice_w * lattice_h; ++i) { - lattice[i] = (float)rand() / RAND_MAX; - } - - const float scale_u = current_freq / w; - const float scale_v = current_freq / h; + 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) { - const float v = scale_v * y; - const int ly = (int)floor(v); - const int ly_next = (ly + 1); // No wrap here for better octaves - float fv = smooth(v - ly); - for (int x = 0; x < w; ++x) { - float u = scale_u * x; - const int lx = (int)floor(u); - const int lx_next = (lx + 1); - float fu = smooth(u - lx); - - // Simple tiling for lattice access - auto get_lat = [&](int ix, int iy) { - return lattice[(iy % lattice_h) * lattice_w + (ix % lattice_w)]; - }; - - float n00 = get_lat(lx, ly); - float n10 = get_lat(lx_next, ly); - float n01 = get_lat(lx, ly_next); - float n11 = get_lat(lx_next, ly_next); + 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; - const float noise = mix(mix(n00, n10, fu), mix(n01, n11, fu), fv); - accum[y * w + x] += noise * current_amp; + 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; } - } - total_amp += current_amp; - current_freq *= 2.0f; - current_amp *= amp_decay; - free(lattice); - } - - // Normalize and write to RGBA buffer - for (int i = 0; i < w * h; ++i) { - float val = accum[i] / total_amp; - uint8_t uval = (uint8_t)(fminf(fmaxf(val, 0.0f), 1.0f) * 255.0f); - buffer[4 * i + 0] = uval; - buffer[4 * i + 1] = uval; - buffer[4 * i + 2] = uval; - buffer[4 * i + 3] = 255; + 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; + } } - free(accum); return true; } -// Simple smooth noise generator (Value Noise-ish) - -// Params[0]: Seed - +// 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; - 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); - - float* lattice = - (float*)malloc((size_t)lattice_w * lattice_h * sizeof(float)); - - if (!lattice) - return false; - - for (int i = 0; i < lattice_w * lattice_h; ++i) { - lattice[i] = (float)rand() / RAND_MAX; - } - - const float scale_u = 1.f * (lattice_w - 1) / w; - - const float scale_v = 1.f * (lattice_h - 1) / h; + 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) { - 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 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); - - dst[4 * x + 0] = val; // R - - dst[4 * x + 1] = val; // G - - dst[4 * x + 2] = val; // B - - dst[4 * x + 3] = 255; // A + const int idx = (y * w + x) * 4; + buffer[idx + 0] = val; + buffer[idx + 1] = val; + buffer[idx + 2] = val; + buffer[idx + 3] = 255; } } - free(lattice); - return true; } |
