summaryrefslogtreecommitdiff
path: root/src/procedural/generator.cc
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-08 19:14:55 +0100
committerskal <pascal.massimino@gmail.com>2026-02-08 19:14:55 +0100
commitb7d282249394a8c0a48d5dfe5af410b541c724cc (patch)
tree19020b59ae03b5029db33cee8932589c92bde11e /src/procedural/generator.cc
parent6071c9f6bea2d3890024cb47083b92d9ddbf0447 (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.cc215
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;
}