moved noise to its own file
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
#include "../util/output/frame.hpp"
|
#include "../util/output/frame.hpp"
|
||||||
#include "../util/timing_decorator.cpp"
|
#include "../util/timing_decorator.cpp"
|
||||||
#include "../util/noise/pnoise2.hpp"
|
#include "../util/noise/pnoise2.hpp"
|
||||||
|
#include "../util/noise/pnoise.cpp"
|
||||||
#include "../util/output/aviwriter.hpp"
|
#include "../util/output/aviwriter.hpp"
|
||||||
|
|
||||||
#include "../imgui/imgui.h"
|
#include "../imgui/imgui.h"
|
||||||
@@ -61,67 +62,6 @@ struct stardefaults {
|
|||||||
bool enabled = true;
|
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<NoiseLayer> layers;
|
|
||||||
|
|
||||||
// Visuals
|
|
||||||
GLuint textureId = 0;
|
|
||||||
std::vector<uint8_t> pixelBuffer;
|
|
||||||
bool needsUpdate = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::mutex PreviewMutex;
|
std::mutex PreviewMutex;
|
||||||
GLuint textu = 0;
|
GLuint textu = 0;
|
||||||
bool textureInitialized = false;
|
bool textureInitialized = false;
|
||||||
@@ -144,161 +84,6 @@ const std::chrono::seconds STATS_UPDATE_INTERVAL(60);
|
|||||||
std::string cachedStats;
|
std::string cachedStats;
|
||||||
bool statsNeedUpdate = true;
|
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<uint8_t>(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<int>& grid) {
|
void createSphere(const defaults& config, const spheredefaults& sconfig, Octree<int>& grid) {
|
||||||
if (!grid.empty()) grid.clear();
|
if (!grid.empty()) grid.clear();
|
||||||
|
|
||||||
@@ -1004,119 +789,7 @@ int main() {
|
|||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
drawNoiseLab(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); // 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
int display_w, display_h;
|
int display_w, display_h;
|
||||||
|
|||||||
313
util/noise/pnoise.cpp
Normal file
313
util/noise/pnoise.cpp
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "./pnoise2.hpp"
|
||||||
|
#include "../../imgui/imgui.h"
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
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<NoiseLayer> layers;
|
||||||
|
GLuint textureId = 0;
|
||||||
|
std::vector<uint8_t> 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<uint8_t>(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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user