From 18b03fbdac1b0b4114f369e7a611a1c3246777ad Mon Sep 17 00:00:00 2001 From: Yggdrasil75 Date: Fri, 7 Nov 2025 14:27:40 -0500 Subject: [PATCH] need more noise features --- main.cpp | 72 ++++++++++++++- simtools/sim2.hpp | 11 +-- util/noise/pnoise.hpp | 204 ++++++++++++++++++++++++++++++++++++++++++ util/noise2.hpp | 74 +++++++-------- web/index.html | 47 ++++++++++ web/script.js | 107 +++++++++++++++++++++- web/style.css | 56 ++++++++++++ 7 files changed, 521 insertions(+), 50 deletions(-) create mode 100644 util/noise/pnoise.hpp diff --git a/main.cpp b/main.cpp index fb142eb..d6b3880 100644 --- a/main.cpp +++ b/main.cpp @@ -72,13 +72,13 @@ bool generateGradientImage(const std::string& filename, int width = 512, int hei } // Generate terrain simulation image -bool generateTerrainImage(const std::string& filename, int width = 512, int height = 512) { +bool generateTerrainImage(const std::string& filename, int width = 512, int height = 512, uint32_t seed = 42, float scale = 4.0f, int octaves = 4, + float persistence = 0.5f, float lacunarity = 2.0f, float waterlevel = 0.3f, float elevation = 1.0f) { TIME_FUNCTION; - static Sim2 sim(width, height); - + Sim2 sim(width, height, seed, scale, octaves, persistence, lacunarity, waterlevel, elevation); + sim.generateTerrain(); // Randomize seed for variety - sim.randomizeSeed(); // Render to RGB image std::vector imageData = sim.renderToRGB(width, height); @@ -171,6 +171,70 @@ int main(int argc, char* argv[]) { SimpleHTTPServer server(port, webRoot); + // Add parameter setting endpoint + server.addRoute("/api/set-parameters", [webRoot](const std::string& method, const std::string& body) { + if (method == "POST") { + try { + // Parse JSON parameters + // Simple JSON parsing - in a real application you'd use a proper JSON library + float scale = 4.0f; + int octaves = 4; + float persistence = 0.5f; + float lacunarity = 2.0f; + float elevation = 1.0f; + float waterLevel = 0.3f; + uint32_t seed = 42; + + // Extract parameters from JSON (simplified parsing) + if (body.find("\"scale\"") != std::string::npos) { + size_t pos = body.find("\"scale\":") + 8; + scale = std::stof(body.substr(pos)); + } + if (body.find("\"octaves\"") != std::string::npos) { + size_t pos = body.find("\"octaves\":") + 10; + octaves = std::stoi(body.substr(pos)); + } + if (body.find("\"persistence\"") != std::string::npos) { + size_t pos = body.find("\"persistence\":") + 14; + persistence = std::stof(body.substr(pos)); + } + if (body.find("\"lacunarity\"") != std::string::npos) { + size_t pos = body.find("\"lacunarity\":") + 13; + lacunarity = std::stof(body.substr(pos)); + } + if (body.find("\"elevation\"") != std::string::npos) { + size_t pos = body.find("\"elevation\":") + 12; + elevation = std::stof(body.substr(pos)); + } + if (body.find("\"waterLevel\"") != std::string::npos) { + size_t pos = body.find("\"waterLevel\":") + 13; + waterLevel = std::stof(body.substr(pos)); + } + if (body.find("\"seed\"") != std::string::npos) { + size_t pos = body.find("\"seed\":") + 7; + seed = std::stoul(body.substr(pos)); + } + + // Create NEW instance each time (remove 'static') + Sim2 sim(512, 512, seed, scale, octaves, persistence, lacunarity, waterLevel, elevation); + + // Regenerate and save + std::vector imageData = sim.renderToRGB(512, 512); + bool success = generateTerrainImage(webRoot + "/output/display.jxl", 512, 512, seed, scale, octaves, persistence, lacunarity, waterLevel, elevation); + //bool success = JXLWriter::saveJXL(webRoot + "/output/display.jxl", imageData, 512, 512); + + if (success) { + return std::make_pair(200, std::basic_string("{\"status\":\"success\"}")); + } else { + return std::make_pair(500, std::basic_string("{\"error\":\"Failed to generate terrain\"}")); + } + } catch (const std::exception& e) { + return std::make_pair(400, std::basic_string("{\"error\":\"Invalid parameters\"}")); + } + } + return std::make_pair(405, std::basic_string("{\"error\":\"Method Not Allowed\"}")); + }); + // Add timing stats endpoint server.addRoute("/api/timing-stats", [](const std::string& method, const std::string& body) { if (method == "GET") { diff --git a/simtools/sim2.hpp b/simtools/sim2.hpp index dd53b06..88ade70 100644 --- a/simtools/sim2.hpp +++ b/simtools/sim2.hpp @@ -32,14 +32,15 @@ private: Vec4 waterColor; public: - Sim2(int width = 512, int height = 512, uint32_t seed = 42) - : gridWidth(width), gridHeight(height), scale(4.0f), octaves(4), - persistence(0.5f), lacunarity(2.0f), seed(seed), offset(0, 0), - elevationMultiplier(1.0f), waterLevel(0.3f), + Sim2(int width = 512, int height = 512, uint32_t seed = 42, float scale = 4.0f, int octaves = 4, + float persistence = 0.5f, float lacunarity = 2.0f, float waterlevel = 0.3f, float elevation = 1.0f) + : gridWidth(width), gridHeight(height), scale(scale), octaves(octaves), + persistence(persistence), lacunarity(lacunarity), seed(seed), offset(0, 0), + elevationMultiplier(elevation), waterLevel(waterlevel), landColor(0.2f, 0.8f, 0.2f, 1.0f), // Green waterColor(0.2f, 0.3f, 0.8f, 1.0f) // Blue { - noiseGenerator = std::make_unique(seed,Noise2::WORLEY,Noise2::PRECOMPUTED); + noiseGenerator = std::make_unique(seed,Noise2::PERLIN,Noise2::PRECOMPUTED); generateTerrain(); } diff --git a/util/noise/pnoise.hpp b/util/noise/pnoise.hpp new file mode 100644 index 0000000..9ebd088 --- /dev/null +++ b/util/noise/pnoise.hpp @@ -0,0 +1,204 @@ + +#ifndef PERLIN_NOISE_HPP +#define PERLIN_NOISE_HPP + +#include +#include +#include +#include +#include + +class PerlinNoise { +private: + std::vector permutation; + + // Fade function as defined by Ken Perlin + static double fade(double t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + // Linear interpolation + static double lerp(double t, double a, double b) { + return a + t * (b - a); + } + + // Gradient function + static double grad(int hash, double x, double y, double z) { + int h = hash & 15; + double u = h < 8 ? x : y; + double v = h < 4 ? y : (h == 12 || h == 14 ? x : z); + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + +public: + // Constructor with optional seed + PerlinNoise(unsigned int seed = 0) { + permutation.resize(256); + // Initialize with values 0-255 + for (int i = 0; i < 256; ++i) { + permutation[i] = i; + } + + // Shuffle using the seed + std::shuffle(permutation.begin(), permutation.end(), std::default_random_engine(seed)); + + // Duplicate the permutation vector + permutation.insert(permutation.end(), permutation.begin(), permutation.end()); + } + + // 1D Perlin noise + double noise(double x) const { + return noise(x, 0.0, 0.0); + } + + // 2D Perlin noise + double noise(double x, double y) const { + return noise(x, y, 0.0); + } + + // 3D Perlin noise (main implementation) + double noise(double x, double y, double z) const { + // Find unit cube that contains the point + int X = (int)floor(x) & 255; + int Y = (int)floor(y) & 255; + int Z = (int)floor(z) & 255; + + // Find relative x, y, z of point in cube + x -= floor(x); + y -= floor(y); + z -= floor(z); + + // Compute fade curves for x, y, z + double u = fade(x); + double v = fade(y); + double w = fade(z); + + // Hash coordinates of the 8 cube corners + int A = permutation[X] + Y; + int AA = permutation[A] + Z; + int AB = permutation[A + 1] + Z; + int B = permutation[X + 1] + Y; + int BA = permutation[B] + Z; + int BB = permutation[B + 1] + Z; + + // Add blended results from 8 corners of cube + double res = lerp(w, lerp(v, lerp(u, grad(permutation[AA], x, y, z), + grad(permutation[BA], x - 1, y, z)), + lerp(u, grad(permutation[AB], x, y - 1, z), + grad(permutation[BB], x - 1, y - 1, z))), + lerp(v, lerp(u, grad(permutation[AA + 1], x, y, z - 1), + grad(permutation[BA + 1], x - 1, y, z - 1)), + lerp(u, grad(permutation[AB + 1], x, y - 1, z - 1), + grad(permutation[BB + 1], x - 1, y - 1, z - 1)))); + return (res + 1.0) / 2.0; // Normalize to [0,1] + } + + // Fractal Brownian Motion (fBm) - multiple octaves of noise + double fractal(size_t octaves, double x, double y = 0.0, double z = 0.0) const { + double value = 0.0; + double amplitude = 1.0; + double frequency = 1.0; + double maxValue = 0.0; + + for (size_t i = 0; i < octaves; ++i) { + value += amplitude * noise(x * frequency, y * frequency, z * frequency); + maxValue += amplitude; + amplitude *= 0.5; + frequency *= 2.0; + } + + return value / maxValue; + } + + // Turbulence - absolute value of noise for more dramatic effects + double turbulence(size_t octaves, double x, double y = 0.0, double z = 0.0) const { + double value = 0.0; + double amplitude = 1.0; + double frequency = 1.0; + double maxValue = 0.0; + + for (size_t i = 0; i < octaves; ++i) { + value += amplitude * std::abs(noise(x * frequency, y * frequency, z * frequency)); + maxValue += amplitude; + amplitude *= 0.5; + frequency *= 2.0; + } + + return value / maxValue; + } + + // Ridged multi-fractal - creates ridge-like patterns + double ridgedMultiFractal(size_t octaves, double x, double y = 0.0, double z = 0.0, + double lacunarity = 2.0, double gain = 0.5, double offset = 1.0) const { + double value = 0.0; + double amplitude = 1.0; + double frequency = 1.0; + double prev = 1.0; + double weight; + + for (size_t i = 0; i < octaves; ++i) { + double signal = offset - std::abs(noise(x * frequency, y * frequency, z * frequency)); + signal *= signal; + signal *= prev; + + weight = std::clamp(signal * gain, 0.0, 1.0); + value += signal * amplitude; + prev = weight; + amplitude *= weight; + frequency *= lacunarity; + } + + return value; + } +}; + +// Utility functions for common noise operations +namespace PerlinUtils { + // Create a 1D noise array + static std::vector generate1DNoise(int width, double scale = 1.0, unsigned int seed = 0) { + PerlinNoise pn(seed); + std::vector result(width); + + for (int x = 0; x < width; ++x) { + result[x] = pn.noise(x * scale); + } + + return result; + } + + // Create a 2D noise array + static std::vector> generate2DNoise(int width, int height, + double scale = 1.0, unsigned int seed = 0) { + PerlinNoise pn(seed); + std::vector> result(height, std::vector(width)); + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + result[y][x] = pn.noise(x * scale, y * scale); + } + } + + return result; + } + + // Create a 3D noise array + static std::vector>> generate3DNoise(int width, int height, int depth, + double scale = 1.0, unsigned int seed = 0) { + PerlinNoise pn(seed); + std::vector>> result( + depth, std::vector>( + height, std::vector(width))); + + for (int z = 0; z < depth; ++z) { + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + result[z][y][x] = pn.noise(x * scale, y * scale, z * scale); + } + } + } + + return result; + } +} + +#endif \ No newline at end of file diff --git a/util/noise2.hpp b/util/noise2.hpp index 005c8ae..88509de 100644 --- a/util/noise2.hpp +++ b/util/noise2.hpp @@ -34,40 +34,6 @@ public: PRECOMPUTED }; -private: - std::mt19937 rng; - std::uniform_real_distribution dist; - - // Precomputed gradient directions for 8 directions - static constexpr std::array grads = { - Grad{1.0f, 0.0f}, - Grad{0.707f, 0.707f}, - Grad{0.0f, 1.0f}, - Grad{-0.707f, 0.707f}, - Grad{-1.0f, 0.0f}, - Grad{-0.707f, -0.707f}, - Grad{0.0f, -1.0f}, - Grad{0.707f, -0.707f} - }; - - NoiseType currentType; - GradientType gradType; - uint32_t currentSeed; - - // Permutation table for Simplex noise - std::array perm; - - // For Worley noise - std::vector featurePoints; - - // For Gabor noise - float gaborFrequency; - float gaborBandwidth; - - // For wavelet noise - std::vector waveletCoefficients; - -public: Noise2(uint32_t seed = 0, NoiseType type = PERLIN, GradientType gradType = PRECOMPUTED) : rng(seed), dist(0.0f, 1.0f), currentType(type), gradType(gradType), currentSeed(seed), gaborFrequency(4.0f), gaborBandwidth(0.5f) @@ -384,12 +350,8 @@ public: } // Generate terrain-like noise (useful for heightmaps) - Grid2 generateTerrainNoise(int width, int height, - float scale = 1.0f, - int octaves = 4, - float persistence = 0.5f, - uint32_t seed = 0, - const Vec2& offset = Vec2(0, 0)) { + Grid2 generateTerrainNoise(int width, int height, float scale = 1.0f, int octaves = 4, + float persistence = 0.5f, uint32_t seed = 0, const Vec2& offset = Vec2(0, 0)) { if (seed != 0) setSeed(seed); Grid2 grid(width * height); @@ -453,6 +415,38 @@ public: } private: + std::mt19937 rng; + std::uniform_real_distribution dist; + + // Precomputed gradient directions for 8 directions + static constexpr std::array grads = { + Grad{1.0f, 0.0f}, + Grad{0.707f, 0.707f}, + Grad{0.0f, 1.0f}, + Grad{-0.707f, 0.707f}, + Grad{-1.0f, 0.0f}, + Grad{-0.707f, -0.707f}, + Grad{0.0f, -1.0f}, + Grad{0.707f, -0.707f} + }; + + NoiseType currentType; + GradientType gradType; + uint32_t currentSeed; + + // Permutation table for Simplex noise + std::array perm; + + // For Worley noise + std::vector featurePoints; + + // For Gabor noise + float gaborFrequency; + float gaborBandwidth; + + // For wavelet noise + std::vector waveletCoefficients; + // Initialize permutation table for Simplex noise void initializePermutationTable(uint32_t seed) { std::mt19937 localRng(seed); diff --git a/web/index.html b/web/index.html index 5bda12f..aa7f6d6 100644 --- a/web/index.html +++ b/web/index.html @@ -16,6 +16,53 @@ + + + + +
diff --git a/web/script.js b/web/script.js index 5702ab5..10f01f2 100644 --- a/web/script.js +++ b/web/script.js @@ -215,4 +215,109 @@ document.addEventListener('DOMContentLoaded', function() { console.log('Not in all mode, using default'); refreshImage(); }); -}); \ No newline at end of file +}); + +function toggleParameters() { + const panel = document.getElementById('parameterPanel'); + const btn = document.getElementById('paramsBtn'); + + if (panel.style.display === 'none') { + panel.style.display = 'block'; + btn.textContent = 'Hide Parameters'; + loadCurrentParameters(); + } else { + panel.style.display = 'none'; + btn.textContent = 'Show Parameters'; + } +} + +function loadCurrentParameters() { + // This would ideally fetch current parameters from the server + // For now, we'll just initialize the sliders with their current values + setupSlider('scale', 'scaleValue', 4.0); + setupSlider('octaves', 'octavesValue', 4); + setupSlider('persistence', 'persistenceValue', 0.5); + setupSlider('lacunarity', 'lacunarityValue', 2.0); + setupSlider('elevation', 'elevationValue', 1.0); + setupSlider('waterLevel', 'waterLevelValue', 0.3); +} + +function setupSlider(sliderId, valueId, defaultValue) { + const slider = document.getElementById(sliderId); + const value = document.getElementById(valueId); + + slider.value = defaultValue; + value.textContent = defaultValue; + + slider.addEventListener('input', function() { + value.textContent = this.value; + }); +} + +function applyParameters() { + const params = { + scale: parseFloat(document.getElementById('scale').value), + octaves: parseInt(document.getElementById('octaves').value), + persistence: parseFloat(document.getElementById('persistence').value), + lacunarity: parseFloat(document.getElementById('lacunarity').value), + elevation: parseFloat(document.getElementById('elevation').value), + waterLevel: parseFloat(document.getElementById('waterLevel').value), + seed: parseInt(document.getElementById('seed').value) + }; + + console.log("Sending parameters:", params); // Debug log + + fetch('/api/set-parameters', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params) + }) + .then(response => response.json()) + .then(data => { + console.log("Server response:", data); // Debug log + if (data.status === 'success') { + updateStatus('Parameters applied successfully'); + if (currentMode === 'terrain') { + refreshTerrain(); + } + } else { + updateStatus('Error applying parameters', 'error'); + } + }) + .catch(error => { + console.error('Error applying parameters:', error); + updateStatus('Error applying parameters', 'error'); + }); +} + +function resetParameters() { + document.getElementById('scale').value = 4.0; + document.getElementById('scaleValue').textContent = '4.0'; + + document.getElementById('octaves').value = 4; + document.getElementById('octavesValue').textContent = '4'; + + document.getElementById('persistence').value = 0.5; + document.getElementById('persistenceValue').textContent = '0.5'; + + document.getElementById('lacunarity').value = 2.0; + document.getElementById('lacunarityValue').textContent = '2.0'; + + document.getElementById('elevation').value = 1.0; + document.getElementById('elevationValue').textContent = '1.0'; + + document.getElementById('waterLevel').value = 0.3; + document.getElementById('waterLevelValue').textContent = '0.3'; + + document.getElementById('seed').value = 42; + + updateStatus('Parameters reset to defaults'); +} + +function randomizeSeed() { + const newSeed = Math.floor(Math.random() * 1000000); + document.getElementById('seed').value = newSeed; + updateStatus('Random seed generated: ' + newSeed); +} \ No newline at end of file diff --git a/web/style.css b/web/style.css index 063600e..2940c97 100644 --- a/web/style.css +++ b/web/style.css @@ -114,3 +114,59 @@ button.danger:hover { border-radius: 5px; font-weight: bold; } + +.parameter-panel { + margin: 20px 0; + padding: 20px; + background: rgba(255, 255, 255, 0.15); + border-radius: 10px; + text-align: left; +} + +.param-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 15px; + margin-bottom: 20px; +} + +.param-group { + display: flex; + align-items: center; + gap: 10px; +} + +.param-group label { + min-width: 150px; + font-weight: bold; +} + +.param-group input[type="range"] { + flex: 1; +} + +.param-group input[type="number"] { + width: 80px; + padding: 5px; + border: none; + border-radius: 4px; +} + +.param-group span { + min-width: 40px; + text-align: right; +} + +.param-actions { + display: flex; + gap: 10px; + justify-content: center; +} + + +@media (max-width: 768px) { + .param-grid { + grid-template-columns: 1fr; + } + +} \ No newline at end of file