summaryrefslogtreecommitdiff
path: root/src/procedural
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-03 19:06:41 +0100
committerskal <pascal.massimino@gmail.com>2026-02-03 19:06:41 +0100
commit3108fb0065a51dfc3548836ea16b287e92cd8881 (patch)
treeb20d3ffd904b65596ce9dd2df15a527b91a6539f /src/procedural
parentc3714939897af2541c655c03bcdd61108fff46ea (diff)
feat: side-quest - Perlin noise sky and ProcGenFunc error handling
- Updated ProcGenFunc signature to return bool for error reporting. - Implemented gen_perlin (Fractional Brownian Motion) in procedural/generator.cc. - Added support for sky texture in Renderer3D and its shader. - Integrated Perlin noise sky texture in test_3d_render.cc. - Caught and handled memory/generation errors in AssetManager and TextureManager. - Assigned reference numbers to all remaining tasks in documentation. handoff(Gemini): Side-quest complete. ProcGenFunc now returns bool. Perlin noise added and used for sky in 3D test. Windows build remains stable. All tasks numbered.
Diffstat (limited to 'src/procedural')
-rw-r--r--src/procedural/generator.cc175
-rw-r--r--src/procedural/generator.h20
2 files changed, 164 insertions, 31 deletions
diff --git a/src/procedural/generator.cc b/src/procedural/generator.cc
index 5ae83e4..0eb8b03 100644
--- a/src/procedural/generator.cc
+++ b/src/procedural/generator.cc
@@ -15,65 +15,204 @@ constexpr float mix(float a, float b, float t) {
return (a * (1.0f - t) + b * t);
}
+// Perlin noise generator (Fractional Brownian Motion using value noise)
+// Params[0]: Seed
+// 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;
+
+ 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);
+
+ const float noise = mix(mix(n00, n10, fu), mix(n01, n11, fu), fv);
+ accum[y * w + x] += noise * current_amp;
+ }
+ }
+
+ 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;
+ }
+
+ free(accum);
+ return true;
+}
+
// 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,
+
+bool 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);
- float* lattice =
- (float*)malloc((size_t)lattice_w * lattice_h * sizeof(float));
- if (!lattice)
- return;
+
+ 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;
+
+
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
+
}
+
}
+
free(lattice);
+
+ return true;
+
}
+
+
// 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,
+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;
@@ -95,13 +234,14 @@ void gen_grid(uint8_t* buffer, int w, int h, const float* params,
buffer[idx + 3] = 255;
}
}
+ return true;
}
-void make_periodic(uint8_t* buffer, int w, int h, const float* params,
+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;
+ return true;
if (ratio > 0.5f)
ratio = 0.5f;
@@ -124,24 +264,6 @@ void make_periodic(uint8_t* buffer, int w, int h, const float* params,
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
@@ -158,6 +280,7 @@ void make_periodic(uint8_t* buffer, int w, int h, const float* params,
}
}
}
+ return true;
}
} // namespace procedural
diff --git a/src/procedural/generator.h b/src/procedural/generator.h
index 72682b0..e57ef61 100644
--- a/src/procedural/generator.h
+++ b/src/procedural/generator.h
@@ -10,22 +10,32 @@
// buffer: Pointer to RGBA8 buffer (size w * h * 4)
// w, h: Dimensions
// params: Arbitrary float parameters for the generator
-typedef void (*ProcGenFunc)(uint8_t* buffer, int w, int h, const float* params,
+// Returns true on success, false on failure.
+typedef bool (*ProcGenFunc)(uint8_t* buffer, int w, int h, const float* params,
int num_params);
namespace procedural {
-// Example: Simple noise generator
-void gen_noise(uint8_t* buffer, int w, int h, const float* params,
+// Simple noise generator
+bool gen_noise(uint8_t* buffer, int w, int h, const float* params,
int num_params);
+// Perlin noise generator
+// Params[0]: Seed
+// Params[1]: Frequency (Scale)
+// Params[2]: Amplitude
+// Params[3]: Amplitude decay (e.g. 0.5)
+// Params[4]: Number of octaves (int)
+bool gen_perlin(uint8_t* buffer, int w, int h, const float* params,
+ int num_params);
+
// Example: Grid pattern
-void gen_grid(uint8_t* buffer, int w, int h, const float* params,
+bool gen_grid(uint8_t* buffer, int w, int h, const float* params,
int num_params);
// Post-process: Make texture periodic by blending edges
// Params[0]: Border size ratio (0.0 - 0.5), default 0.1
-void make_periodic(uint8_t* buffer, int w, int h, const float* params,
+bool make_periodic(uint8_t* buffer, int w, int h, const float* params,
int num_params);
} // namespace procedural