diff --git a/tests/g3etest.cpp b/tests/g3etest.cpp index 83873e6..d0642ac 100644 --- a/tests/g3etest.cpp +++ b/tests/g3etest.cpp @@ -14,6 +14,7 @@ #include "../util/output/frame.hpp" #include "../util/timing_decorator.cpp" #include "../util/noise/pnoise2.hpp" +#include "../util/noise/pnoise.cpp" #include "../util/output/aviwriter.hpp" #include "../imgui/imgui.h" @@ -61,67 +62,6 @@ struct stardefaults { bool enabled = true; }; -enum class NoiseType { - Perlin = 0, - Value = 1, - Fractal = 2, - Turbulence = 3, - Ridged = 4, - Billow = 5, - WhiteNoise = 6, - WorleyNoise = 7, - VoronoiNoise = 8, - CrystalNoise = 9, - DomainWarp = 10, - CurlNoise = 11 -}; - -enum class BlendMode { - Add = 0, - Subtract = 1, - Multiply = 2, - Min = 3, - Max = 4, - Replace = 5, - DomainWarp = 6 // Uses this layer's value to distort coordinates for NEXT layers -}; - -struct NoiseLayer { - bool enabled = true; - char name[32] = "Layer"; - - NoiseType type = NoiseType::Perlin; - BlendMode blend = BlendMode::Add; - - // Params specific to this layer - int seedOffset = 0; - float scale = 0.02f; - float strength = 1.0f; // blending weight or warp strength - - // Fractal specific - int octaves = 4; - float persistence = 0.5f; - float lacunarity = 2.0f; - float ridgeOffset = 1.0f; -}; - -struct NoisePreviewState { - int width = 512; - int height = 512; - - // Global settings - int masterSeed = 1337; - float offset[2] = {0.0f, 0.0f}; - - // The Stack - std::vector layers; - - // Visuals - GLuint textureId = 0; - std::vector pixelBuffer; - bool needsUpdate = true; -}; - std::mutex PreviewMutex; GLuint textu = 0; bool textureInitialized = false; @@ -144,161 +84,6 @@ const std::chrono::seconds STATS_UPDATE_INTERVAL(60); std::string cachedStats; bool statsNeedUpdate = true; -float sampleNoiseLayer(PNoise2& gen, NoiseType type, Eigen::Vector2f point, const NoiseLayer& layer) { - float val = 0.0f; - - switch (type) { - case NoiseType::Perlin: - val = gen.permute(point); - break; - case NoiseType::Value: - val = gen.valueNoise(point); - break; - case NoiseType::Fractal: - val = gen.fractalNoise(point, layer.octaves, layer.persistence, layer.lacunarity); - break; - case NoiseType::Turbulence: - val = gen.turbulence(point, layer.octaves); - val = (val * 2.0f) - 1.0f; - break; - case NoiseType::Ridged: - val = gen.ridgedNoise(point, layer.octaves, layer.ridgeOffset); - val = (val * 0.5f) - 1.0f; - break; - case NoiseType::Billow: - val = gen.billowNoise(point, layer.octaves); - val = (val * 2.0f) - 1.0f; - break; - case NoiseType::WhiteNoise: - val = gen.whiteNoise(point); - break; - case NoiseType::WorleyNoise: - val = gen.worleyNoise(point); - break; - case NoiseType::VoronoiNoise: - val = gen.voronoiNoise(point); - break; - case NoiseType::CrystalNoise: - val = gen.crystalNoise(point); - break; - // Simplified: DomainWarp and Curl inside a layer act as standard noise - // that we will use to manipulate coordinates manually in the loop - case NoiseType::DomainWarp: - val = gen.domainWarp(point, 1.0f); - break; - case NoiseType::CurlNoise: - // For single channel return, we just take length or one component - // Ideally Curl returns a vector, but for this helper we return magnitude - // to allow it to be used as a heightmap factor if needed. - Eigen::Vector2f flow = gen.curlNoise(point); - val = flow.norm(); - break; - } - return val; -} - -// Helper to generate the 2D noise texture -void updateNoiseTexture(NoisePreviewState& state) { - if (state.textureId == 0) glGenTextures(1, &state.textureId); - - state.pixelBuffer.resize(state.width * state.height * 3); - - // Create one generator. We will re-seed it per layer or use offsets. - // PNoise2 is fast to init? If not, create one and change seed if supported, - // or create a vector of generators. Assuming lightweight: - - #pragma omp parallel for - for (int y = 0; y < state.height; ++y) { - // Optimization: Create generator on thread stack if PNoise2 is lightweight, - // otherwise pass seed to methods if possible. - // Assuming we construct one per thread or rely on internal state: - PNoise2 generator(state.masterSeed); - - for (int x = 0; x < state.width; ++x) { - - // 1. Initial Coordinate - float nx = (x + state.offset[0]); - float ny = (y + state.offset[1]); - Eigen::Vector2f point(nx, ny); - - float finalValue = 0.0f; - - // 2. Process Layer Stack - for (const auto& layer : state.layers) { - if (!layer.enabled) continue; - - // Adjust coordinate frequency for this layer - Eigen::Vector2f samplePoint = point * layer.scale; - - // Re-seed logic (simulated by large coordinate offsets if re-seeding isn't exposed) - // Or if PNoise2 allows reseeding: generator.setSeed(state.masterSeed + layer.seedOffset); - // Here we cheat by offsetting coordinates based on seed to avoid constructor overhead in loop - samplePoint += Eigen::Vector2f((float)layer.seedOffset * 10.5f, (float)layer.seedOffset * -10.5f); - - // Special Case: Coordinate Mutators (Domain Warp / Curl) - if (layer.blend == BlendMode::DomainWarp) { - if (layer.type == NoiseType::CurlNoise) { - Eigen::Vector2f flow = generator.curlNoise(samplePoint); - point += flow * layer.strength * 100.0f; // Update the BASE point for future layers - } else { - // Standard Domain Warp using simple noise offsets - float warpX = sampleNoiseLayer(generator, layer.type, samplePoint, layer); - // Sample Y slightly offset to get different direction - float warpY = sampleNoiseLayer(generator, layer.type, samplePoint + Eigen::Vector2f(5.2f, 1.3f), layer); - point += Eigen::Vector2f(warpX, warpY) * layer.strength * 100.0f; - } - continue; // Don't add to finalValue, we just warped space - } - - // Standard Arithmetic Layers - float nVal = sampleNoiseLayer(generator, layer.type, samplePoint, layer); - - switch (layer.blend) { - case BlendMode::Replace: - finalValue = nVal * layer.strength; - break; - case BlendMode::Add: - finalValue += nVal * layer.strength; - break; - case BlendMode::Subtract: - finalValue -= nVal * layer.strength; - break; - case BlendMode::Multiply: - finalValue *= (nVal * layer.strength); - break; - case BlendMode::Max: - finalValue = std::max(finalValue, nVal * layer.strength); - break; - case BlendMode::Min: - finalValue = std::min(finalValue, nVal * layer.strength); - break; - } - } - - // 3. Normalize for display (Map -1..1 to 0..1 usually, but stack can go higher) - // Tanh is good for soft clamping unbounded stacks - float norm = std::tanh(finalValue); - norm = (norm + 1.0f) * 0.5f; - norm = std::clamp(norm, 0.0f, 1.0f); - - uint8_t color = static_cast(norm * 255); - int idx = (y * state.width + x) * 3; - state.pixelBuffer[idx] = color; - state.pixelBuffer[idx+1] = color; - state.pixelBuffer[idx+2] = color; - } - } - - glBindTexture(GL_TEXTURE_2D, state.textureId); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, state.width, state.height, - 0, GL_RGB, GL_UNSIGNED_BYTE, state.pixelBuffer.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - state.needsUpdate = false; -} - void createSphere(const defaults& config, const spheredefaults& sconfig, Octree& grid) { if (!grid.empty()) grid.clear(); @@ -1004,119 +789,7 @@ int main() { ImGui::End(); } - { - ImGui::Begin("2D Noise Lab"); - - // Master Controls - bool changed = false; - changed |= ImGui::InputInt("Master Seed", &noiseState.masterSeed); - changed |= ImGui::DragFloat2("Pan Offset", noiseState.offset, 1.0f); - - ImGui::Separator(); - - if (ImGui::Button("Add Layer")) { - NoiseLayer l; - sprintf(l.name, "Layer %zu", noiseState.layers.size()); - noiseState.layers.push_back(l); - changed = true; - } - - ImGui::SameLine(); - if (ImGui::Button("Clear All")) { - noiseState.layers.clear(); - changed = true; - } - - ImGui::BeginChild("LayersScroll", ImVec2(0, 300), true); - - // Layer List - for (int i = 0; i < noiseState.layers.size(); ++i) { - NoiseLayer& layer = noiseState.layers[i]; - - ImGui::PushID(i); // Important for ImGui inside loops! - - // Header - bool open = ImGui::CollapsingHeader(layer.name, ImGuiTreeNodeFlags_DefaultOpen); - - // Context menu to move/delete - if (ImGui::BeginPopupContextItem()) { - if (ImGui::MenuItem("Move Up", nullptr, false, i > 0)) { - std::swap(noiseState.layers[i], noiseState.layers[i-1]); - changed = true; - ImGui::CloseCurrentPopup(); - } - if (ImGui::MenuItem("Move Down", nullptr, false, i < noiseState.layers.size()-1)) { - std::swap(noiseState.layers[i], noiseState.layers[i+1]); - changed = true; - ImGui::CloseCurrentPopup(); - } - if (ImGui::MenuItem("Duplicate")) { - noiseState.layers.insert(noiseState.layers.begin() + i, layer); - changed = true; - ImGui::CloseCurrentPopup(); - } - if (ImGui::MenuItem("Delete")) { - noiseState.layers.erase(noiseState.layers.begin() + i); - changed = true; - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - ImGui::PopID(); - continue; // Break loop safely - } - ImGui::EndPopup(); - } - - if (open) { - ImGui::Checkbox("##enabled", &layer.enabled); - ImGui::SameLine(); - ImGui::InputText("##name", layer.name, 32); - - // Type and Blend - const char* types[] = { "Perlin", "Value", "Fractal", "Turbulence", "Ridged", "Billow", "White", "Worley", "Voronoi", "Crystal", "Domain Warp", "Curl" }; - const char* blends[] = { "Add", "Subtract", "Multiply", "Min", "Max", "Replace", "Domain Warp (Coord)" }; - - if (ImGui::Combo("Type", (int*)&layer.type, types, IM_ARRAYSIZE(types))) changed = true; - if (ImGui::Combo("Blend", (int*)&layer.blend, blends, IM_ARRAYSIZE(blends))) changed = true; - - // Common Params - if (ImGui::SliderFloat("Scale", &layer.scale, 0.0001f, 0.2f, "%.5f")) changed = true; - if (ImGui::SliderFloat("Strength/Weight", &layer.strength, 0.0f, 5.0f)) changed = true; - if (ImGui::SliderInt("Seed Offset", &layer.seedOffset, 0, 100)) changed = true; - - // Conditional Params - if (layer.type == NoiseType::Fractal || layer.type == NoiseType::Turbulence || - layer.type == NoiseType::Ridged || layer.type == NoiseType::Billow) { - - if (ImGui::SliderInt("Octaves", &layer.octaves, 1, 10)) changed = true; - if (layer.type == NoiseType::Fractal) { - if (ImGui::SliderFloat("Persistence", &layer.persistence, 0.0f, 1.0f)) changed = true; - if (ImGui::SliderFloat("Lacunarity", &layer.lacunarity, 1.0f, 4.0f)) changed = true; - } - if (layer.type == NoiseType::Ridged) { - if (ImGui::SliderFloat("Ridge Offset", &layer.ridgeOffset, 0.0f, 2.0f)) changed = true; - } - } - - ImGui::Separator(); - } - - ImGui::PopID(); - } - ImGui::EndChild(); - - ImGui::Separator(); - - // Preview - ImGui::Text("Preview Output"); - ImGui::Image((void*)(intptr_t)noiseState.textureId, ImVec2((float)noiseState.width, (float)noiseState.height)); - - // Auto update logic - if (changed || noiseState.needsUpdate) { - updateNoiseTexture(noiseState); - } - - ImGui::End(); - } + drawNoiseLab(noiseState); ImGui::Render(); int display_w, display_h; diff --git a/util/noise/pnoise.cpp b/util/noise/pnoise.cpp new file mode 100644 index 0000000..f41acfb --- /dev/null +++ b/util/noise/pnoise.cpp @@ -0,0 +1,313 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "./pnoise2.hpp" +#include "../../imgui/imgui.h" +#include + +enum class NoiseType { + Perlin = 0, + Value = 1, + Fractal = 2, + Turbulence = 3, + Ridged = 4, + Billow = 5, + WhiteNoise = 6, + WorleyNoise = 7, + VoronoiNoise = 8, + CrystalNoise = 9, + DomainWarp = 10, + CurlNoise = 11 +}; + +enum class BlendMode { + Add = 0, + Subtract = 1, + Multiply = 2, + Min = 3, + Max = 4, + Replace = 5, + DomainWarp = 6 +}; + +struct NoiseLayer { + bool enabled = true; + char name[32] = "Layer"; + + NoiseType type = NoiseType::Perlin; + BlendMode blend = BlendMode::Add; + + int seedOffset = 0; + float scale = 0.02f; + float strength = 1.0f; + int octaves = 4; + float persistence = 0.5f; + float lacunarity = 2.0f; + float ridgeOffset = 1.0f; +}; + +struct NoisePreviewState { + int width = 512; + int height = 512; + int masterSeed = 1337; + float offset[2] = {0.0f, 0.0f}; + std::vector layers; + GLuint textureId = 0; + std::vector pixelBuffer; + bool needsUpdate = true; +}; + +inline float sampleNoiseLayer(PNoise2& gen, NoiseType type, Eigen::Vector2f point, const NoiseLayer& layer) { + float val = 0.0f; + switch (type) { + case NoiseType::Perlin: + val = gen.permute(point); + break; + case NoiseType::Value: + val = gen.valueNoise(point); + break; + case NoiseType::Fractal: + val = gen.fractalNoise(point, layer.octaves, layer.persistence, layer.lacunarity); + break; + case NoiseType::Turbulence: + val = gen.turbulence(point, layer.octaves); + val = (val * 2.0f) - 1.0f; + break; + case NoiseType::Ridged: + val = gen.ridgedNoise(point, layer.octaves, layer.ridgeOffset); + val = (val * 0.5f) - 1.0f; + break; + case NoiseType::Billow: + val = gen.billowNoise(point, layer.octaves); + val = (val * 2.0f) - 1.0f; + break; + case NoiseType::WhiteNoise: + val = gen.whiteNoise(point); + break; + case NoiseType::WorleyNoise: + val = gen.worleyNoise(point); + break; + case NoiseType::VoronoiNoise: + val = gen.voronoiNoise(point); + break; + case NoiseType::CrystalNoise: + val = gen.crystalNoise(point); + break; + case NoiseType::DomainWarp: + val = gen.domainWarp(point, 1.0f); + break; + case NoiseType::CurlNoise: + Eigen::Vector2f flow = gen.curlNoise(point); + val = flow.norm(); + break; + } + return val; +} + +inline void updateNoiseTexture(NoisePreviewState& state) { + if (state.textureId == 0) glGenTextures(1, &state.textureId); + + state.pixelBuffer.resize(state.width * state.height * 3); + + #pragma omp parallel for + for (int y = 0; y < state.height; ++y) { + PNoise2 generator(state.masterSeed); + + for (int x = 0; x < state.width; ++x) { + + // 1. Initial Coordinate + float nx = (x + state.offset[0]); + float ny = (y + state.offset[1]); + Eigen::Vector2f point(nx, ny); + + float finalValue = 0.0f; + + // 2. Process Layer Stack + for (const auto& layer : state.layers) { + if (!layer.enabled) continue; + + // Adjust coordinate frequency for this layer + Eigen::Vector2f samplePoint = point * layer.scale; + + // Cheat seed offset + samplePoint += Eigen::Vector2f((float)layer.seedOffset * 10.5f, (float)layer.seedOffset * -10.5f); + + // Special Case: Coordinate Mutators + if (layer.blend == BlendMode::DomainWarp) { + if (layer.type == NoiseType::CurlNoise) { + Eigen::Vector2f flow = generator.curlNoise(samplePoint); + point += flow * layer.strength * 100.0f; + } else { + float warpX = sampleNoiseLayer(generator, layer.type, samplePoint, layer); + float warpY = sampleNoiseLayer(generator, layer.type, samplePoint + Eigen::Vector2f(5.2f, 1.3f), layer); + point += Eigen::Vector2f(warpX, warpY) * layer.strength * 100.0f; + } + continue; + } + + // Standard Arithmetic Layers + float nVal = sampleNoiseLayer(generator, layer.type, samplePoint, layer); + + switch (layer.blend) { + case BlendMode::Replace: + finalValue = nVal * layer.strength; + break; + case BlendMode::Add: + finalValue += nVal * layer.strength; + break; + case BlendMode::Subtract: + finalValue -= nVal * layer.strength; + break; + case BlendMode::Multiply: + finalValue *= (nVal * layer.strength); + break; + case BlendMode::Max: + finalValue = std::max(finalValue, nVal * layer.strength); + break; + case BlendMode::Min: + finalValue = std::min(finalValue, nVal * layer.strength); + break; + } + } + + // 3. Normalize for display + float norm = std::tanh(finalValue); + norm = (norm + 1.0f) * 0.5f; + norm = std::clamp(norm, 0.0f, 1.0f); + + uint8_t color = static_cast(norm * 255); + int idx = (y * state.width + x) * 3; + state.pixelBuffer[idx] = color; + state.pixelBuffer[idx+1] = color; + state.pixelBuffer[idx+2] = color; + } + } + + glBindTexture(GL_TEXTURE_2D, state.textureId); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, state.width, state.height, + 0, GL_RGB, GL_UNSIGNED_BYTE, state.pixelBuffer.data()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + state.needsUpdate = false; +} + +inline void drawNoiseLab(NoisePreviewState& noiseState) { + ImGui::Begin("2D Noise Lab"); + + // Master Controls + bool changed = false; + changed |= ImGui::InputInt("Master Seed", &noiseState.masterSeed); + changed |= ImGui::DragFloat2("Pan Offset", noiseState.offset, 1.0f); + + ImGui::Separator(); + + if (ImGui::Button("Add Layer")) { + NoiseLayer l; + sprintf(l.name, "Layer %zu", noiseState.layers.size()); + noiseState.layers.push_back(l); + changed = true; + } + + ImGui::SameLine(); + if (ImGui::Button("Clear All")) { + noiseState.layers.clear(); + changed = true; + } + + ImGui::BeginChild("LayersScroll", ImVec2(0, 300), true); + + // Layer List + for (int i = 0; i < noiseState.layers.size(); ++i) { + NoiseLayer& layer = noiseState.layers[i]; + + ImGui::PushID(i); + + // Header + bool open = ImGui::CollapsingHeader(layer.name, ImGuiTreeNodeFlags_DefaultOpen); + + // Context menu to move/delete + if (ImGui::BeginPopupContextItem()) { + if (ImGui::MenuItem("Move Up", nullptr, false, i > 0)) { + std::swap(noiseState.layers[i], noiseState.layers[i-1]); + changed = true; + ImGui::CloseCurrentPopup(); + } + if (ImGui::MenuItem("Move Down", nullptr, false, i < noiseState.layers.size()-1)) { + std::swap(noiseState.layers[i], noiseState.layers[i+1]); + changed = true; + ImGui::CloseCurrentPopup(); + } + if (ImGui::MenuItem("Duplicate")) { + noiseState.layers.insert(noiseState.layers.begin() + i, layer); + changed = true; + ImGui::CloseCurrentPopup(); + } + if (ImGui::MenuItem("Delete")) { + noiseState.layers.erase(noiseState.layers.begin() + i); + changed = true; + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + ImGui::PopID(); + continue; + } + ImGui::EndPopup(); + } + + if (open) { + ImGui::Checkbox("##enabled", &layer.enabled); + ImGui::SameLine(); + ImGui::InputText("##name", layer.name, 32); + + // Type and Blend + const char* types[] = { "Perlin", "Value", "Fractal", "Turbulence", "Ridged", "Billow", "White", "Worley", "Voronoi", "Crystal", "Domain Warp", "Curl" }; + const char* blends[] = { "Add", "Subtract", "Multiply", "Min", "Max", "Replace", "Domain Warp (Coord)" }; + + if (ImGui::Combo("Type", (int*)&layer.type, types, IM_ARRAYSIZE(types))) changed = true; + if (ImGui::Combo("Blend", (int*)&layer.blend, blends, IM_ARRAYSIZE(blends))) changed = true; + + // Common Params + if (ImGui::SliderFloat("Scale", &layer.scale, 0.0001f, 0.2f, "%.5f")) changed = true; + if (ImGui::SliderFloat("Strength/Weight", &layer.strength, 0.0f, 5.0f)) changed = true; + if (ImGui::SliderInt("Seed Offset", &layer.seedOffset, 0, 100)) changed = true; + + // Conditional Params + if (layer.type == NoiseType::Fractal || layer.type == NoiseType::Turbulence || + layer.type == NoiseType::Ridged || layer.type == NoiseType::Billow) { + + if (ImGui::SliderInt("Octaves", &layer.octaves, 1, 10)) changed = true; + if (layer.type == NoiseType::Fractal) { + if (ImGui::SliderFloat("Persistence", &layer.persistence, 0.0f, 1.0f)) changed = true; + if (ImGui::SliderFloat("Lacunarity", &layer.lacunarity, 1.0f, 4.0f)) changed = true; + } + if (layer.type == NoiseType::Ridged) { + if (ImGui::SliderFloat("Ridge Offset", &layer.ridgeOffset, 0.0f, 2.0f)) changed = true; + } + } + + ImGui::Separator(); + } + + ImGui::PopID(); + } + ImGui::EndChild(); + + ImGui::Separator(); + + // Preview + ImGui::Text("Preview Output"); + ImGui::Image((void*)(intptr_t)noiseState.textureId, ImVec2((float)noiseState.width, (float)noiseState.height)); + + // Auto update logic + if (changed || noiseState.needsUpdate) { + updateNoiseTexture(noiseState); + } + + ImGui::End(); +} \ No newline at end of file