diff options
| author | skal <pascal.massimino@gmail.com> | 2026-03-29 01:42:28 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-03-29 01:42:28 +0100 |
| commit | 70b77307a9a9ee4fdff23f783e041fe49e60e100 (patch) | |
| tree | 828948256e04a1aee5762643da773b23d29f8e11 /src/procedural | |
| parent | f05f76e07c8a91911aa61af16b7db4f30247a3d8 (diff) | |
feat(procedural): add plasma, voronoi, normalmap generators
Add three new procedural texture generators:
- gen_plasma: classic sine-sum color texture (RGB output)
- gen_voronoi: Worley cellular noise (F1/F2/F2-F1 modes)
- gen_normalmap: post-process grayscale→RGB normal map
Remove gen_noise_256 (was an alias for gen_noise). Register new
generators in asset_manager and asset_packer. Add unit tests for
all three, and use them in test_3d_render (plasma sky, voronoi noise,
fBm normal map).
handoff(Gemini): plasma/voronoi/normalmap procedural generators added;
gen_noise_256 removed; tests + 3d_render usage wired up.
Diffstat (limited to 'src/procedural')
| -rw-r--r-- | src/procedural/generator.cc | 119 | ||||
| -rw-r--r-- | src/procedural/generator.h | 20 |
2 files changed, 129 insertions, 10 deletions
diff --git a/src/procedural/generator.cc b/src/procedural/generator.cc index 7c165db..a2a383b 100644 --- a/src/procedural/generator.cc +++ b/src/procedural/generator.cc @@ -4,6 +4,7 @@ #include "procedural/generator.h" #include <cmath> #include <cstdlib> +#include <vector> namespace procedural { @@ -94,9 +95,6 @@ bool gen_perlin(uint8_t* buffer, int w, int h, const float* params, // 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; @@ -191,16 +189,123 @@ bool make_periodic(uint8_t* buffer, int w, int h, const float* params, return true; } -#if !defined(DEMO_STRIP_ALL) -// Test-only: 256x256 noise generator -bool gen_noise_256(uint8_t* buffer, int w, int h, const float* params, +// Plasma: classic sine-sum color texture +// Params[0]: time/seed, Params[1]: frequency multiplier +bool gen_plasma(uint8_t* buffer, int w, int h, const float* params, + int num_params) { + const float time = (num_params > 0) ? params[0] : 0.0f; + const float freq = (num_params > 1) ? params[1] : 2.0f; + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const float nx = (float)x / (float)w * freq; + const float ny = (float)y / (float)h * freq; + + float v = sinf(nx * 6.2832f + time); + v += sinf(ny * 6.2832f + time * 1.3f); + v += sinf((nx + ny) * 3.1416f + time * 0.7f); + v += sinf(sqrtf(nx * nx + ny * ny) * 6.2832f + time); + v = v * 0.125f + 0.5f; + + // Map value to RGB via 120-degree phase-shifted sines + const float kTau = 6.2832f; + const float kPhase = 2.0944f; // 2*pi/3 + const int idx = (y * w + x) * 4; + buffer[idx + 0] = (uint8_t)(fminf(fmaxf(sinf(v * kTau) * 0.5f + 0.5f, 0.0f), 1.0f) * 255.0f); + buffer[idx + 1] = (uint8_t)(fminf(fmaxf(sinf(v * kTau + kPhase) * 0.5f + 0.5f, 0.0f), 1.0f) * 255.0f); + buffer[idx + 2] = (uint8_t)(fminf(fmaxf(sinf(v * kTau + 2.0f * kPhase) * 0.5f + 0.5f, 0.0f), 1.0f) * 255.0f); + buffer[idx + 3] = 255; + } + } + return true; +} + +// Voronoi (Worley) cellular noise +// Params[0]: cell scale, Params[1]: mode (0=F1, 1=F2, 2=F2-F1), Params[2]: seed +bool gen_voronoi(uint8_t* buffer, int w, int h, const float* params, + int num_params) { + const float scale = (num_params > 0) ? params[0] : 4.0f; + const int mode = (num_params > 1) ? (int)params[1] : 2; + const float seed = (num_params > 2) ? params[2] : 0.0f; + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const float px = (float)x / (float)w * scale; + const float py = (float)y / (float)h * scale; + const float cell_x = floorf(px); + const float cell_y = floorf(py); + + float f1 = 1e10f, f2 = 1e10f; + + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + const float cx = cell_x + dx; + const float cy = cell_y + dy; + const float fx = cx + hash_2f(cx + seed, cy + seed * 1.37f); + const float fy = cy + hash_2f(cx + seed * 1.13f, cy + seed * 0.97f); + const float ddx = px - fx; + const float ddy = py - fy; + const float dist2 = ddx * ddx + ddy * ddy; + + if (dist2 < f1) { f2 = f1; f1 = dist2; } + else if (dist2 < f2) { f2 = dist2; } + } + } + + float value; + if (mode == 0) value = sqrtf(f1) * 1.5f; + else if (mode == 1) value = sqrtf(f2) * 1.2f; + else value = (sqrtf(f2) - sqrtf(f1)) * 3.0f; + + value = fminf(fmaxf(value, 0.0f), 1.0f); + const uint8_t uval = (uint8_t)(value * 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; +} + +// Normal map: post-process converting grayscale R channel to RGB normals +// Params[0]: strength +bool gen_normalmap(uint8_t* buffer, int w, int h, const float* params, int num_params) { - return gen_noise(buffer, w, h, params, num_params); + const float strength = (num_params > 0) ? params[0] : 4.0f; + + std::vector<float> height(w * h); + for (int i = 0; i < w * h; ++i) { + height[i] = buffer[i * 4] / 255.0f; + } + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const float hL = height[y * w + (x - 1 + w) % w]; + const float hR = height[y * w + (x + 1) % w]; + const float hD = height[((y - 1 + h) % h) * w + x]; + const float hU = height[((y + 1) % h) * w + x]; + + const float dx = (hR - hL) * strength; + const float dy = (hU - hD) * strength; + const float len = sqrtf(dx * dx + dy * dy + 1.0f); + + const int idx = (y * w + x) * 4; + buffer[idx + 0] = (uint8_t)((-dx / len * 0.5f + 0.5f) * 255.0f); + buffer[idx + 1] = (uint8_t)((-dy / len * 0.5f + 0.5f) * 255.0f); + buffer[idx + 2] = (uint8_t)((1.0f / len * 0.5f + 0.5f) * 255.0f); + buffer[idx + 3] = 255; + } + } + return true; } +#if !defined(DEMO_STRIP_ALL) // Test-only: Failing generator bool gen_fail(uint8_t* buffer, int w, int h, const float* params, int num_params) { + (void)buffer; (void)w; (void)h; (void)params; (void)num_params; return false; } #endif diff --git a/src/procedural/generator.h b/src/procedural/generator.h index 96d7651..9224d00 100644 --- a/src/procedural/generator.h +++ b/src/procedural/generator.h @@ -17,6 +17,7 @@ typedef bool (*ProcGenFunc)(uint8_t* buffer, int w, int h, const float* params, namespace procedural { // Simple noise generator +// Params[0]: Seed, Params[1]: Frequency bool gen_noise(uint8_t* buffer, int w, int h, const float* params, int num_params); @@ -38,11 +39,24 @@ bool gen_grid(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); -#if !defined(DEMO_STRIP_ALL) -// Test-only: 256x256 noise generator -bool gen_noise_256(uint8_t* buffer, int w, int h, const float* params, +// Plasma: classic sine-sum color texture +// Params[0]: time/seed offset, Params[1]: frequency multiplier (default 2.0) +bool gen_plasma(uint8_t* buffer, int w, int h, const float* params, + int num_params); + +// Voronoi (Worley) cellular noise +// Params[0]: cell scale (default 4.0) +// Params[1]: mode: 0=F1, 1=F2, 2=F2-F1 borders (default 2) +// Params[2]: seed +bool gen_voronoi(uint8_t* buffer, int w, int h, const float* params, + int num_params); + +// Post-process: convert grayscale height (R channel) to RGB normal map +// Params[0]: strength (default 4.0) +bool gen_normalmap(uint8_t* buffer, int w, int h, const float* params, int num_params); +#if !defined(DEMO_STRIP_ALL) // Test-only: Failing generator bool gen_fail(uint8_t* buffer, int w, int h, const float* params, int num_params); |
