summaryrefslogtreecommitdiff
path: root/src/procedural/generator.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/procedural/generator.cc')
-rw-r--r--src/procedural/generator.cc175
1 files changed, 149 insertions, 26 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