feat(editor): voronoi cell noise for organic terrain patterns

- Voronoi noise generator: creates cell-like terrain patterns with
  ridge features at cell boundaries (F2-F1 distance field)
- Configurable cell count (5-100) and amplitude
- Toggle between Value noise and Voronoi in the noise generator section
- Creates interesting mesa/plateau formations and organic shapes
- Random cell centers with per-cell height variation
This commit is contained in:
Kelsi 2026-05-05 07:59:10 -07:00
parent e65fc7caa2
commit b113c218bd
3 changed files with 59 additions and 1 deletions

View file

@ -505,7 +505,22 @@ void EditorUI::renderBrushPanel(EditorApp& app) {
ImGui::InputInt("Seed", &noiseSeed);
ImGui::SameLine();
if (ImGui::SmallButton("Random##seed")) noiseSeed = static_cast<int>(std::rand());
if (ImGui::Button("Apply Noise", ImVec2(140, 0))) {
static int noiseType = 0;
ImGui::RadioButton("Value##nt", &noiseType, 0);
ImGui::SameLine();
ImGui::RadioButton("Voronoi##nt", &noiseType, 1);
if (noiseType == 1) {
static int voronoiCells = 20;
ImGui::SliderInt("Cells##vor", &voronoiCells, 5, 100);
if (ImGui::Button("Apply Voronoi", ImVec2(-1, 0))) {
app.getTerrainEditor().applyVoronoiNoise(voronoiCells, noiseAmp,
static_cast<uint32_t>(noiseSeed));
app.showToast("Voronoi noise applied");
}
}
if (noiseType == 0 && ImGui::Button("Apply Noise", ImVec2(140, 0))) {
app.getTerrainEditor().applyNoise(noiseFreq, noiseAmp, noiseOctaves,
static_cast<uint32_t>(noiseSeed));
app.showToast("Noise applied");

View file

@ -860,6 +860,46 @@ void TerrainEditor::createHill(const glm::vec3& center, float radius, float heig
dirty_ = true;
}
void TerrainEditor::applyVoronoiNoise(int cellCount, float amplitude, uint32_t seed) {
if (!terrain_) return;
float tileNW_X = (32.0f - static_cast<float>(terrain_->coord.y)) * TILE_SIZE;
float tileNW_Y = (32.0f - static_cast<float>(terrain_->coord.x)) * TILE_SIZE;
// Generate random cell centers
std::mt19937 rng(seed);
std::uniform_real_distribution<float> distX(tileNW_X - TILE_SIZE, tileNW_X);
std::uniform_real_distribution<float> distY(tileNW_Y - TILE_SIZE, tileNW_Y);
std::uniform_real_distribution<float> distH(0.0f, amplitude);
struct Cell { float x, y, h; };
std::vector<Cell> cells(cellCount);
for (auto& c : cells) { c.x = distX(rng); c.y = distY(rng); c.h = distH(rng); }
for (int ci = 0; ci < 256; ci++) {
auto& chunk = terrain_->chunks[ci];
if (!chunk.hasHeightMap()) continue;
for (int v = 0; v < 145; v++) {
glm::vec3 pos = chunkVertexWorldPos(ci, v);
// Find nearest two cells
float d1 = 1e30f, d2 = 1e30f;
float h1 = 0;
for (const auto& c : cells) {
float d = (pos.x - c.x) * (pos.x - c.x) + (pos.y - c.y) * (pos.y - c.y);
if (d < d1) { d2 = d1; d1 = d; h1 = c.h; }
else if (d < d2) { d2 = d; }
}
// F2-F1 creates ridge patterns at cell boundaries
float edge = std::sqrt(d2) - std::sqrt(d1);
float edgeNorm = std::min(edge / 30.0f, 1.0f);
chunk.heightMap.heights[v] += h1 * (1.0f - edgeNorm * 0.5f);
}
dirtyChunks_.push_back(ci);
}
for (int ci = 0; ci < 256; ci++) stitchEdges(ci);
dirty_ = true;
}
void TerrainEditor::offsetHeights(float amount) {
if (!terrain_) return;
for (int ci = 0; ci < 256; ci++) {

View file

@ -111,6 +111,9 @@ public:
// Offset all heights by a constant
void offsetHeights(float amount);
// Voronoi cell noise — creates cell-like terrain patterns
void applyVoronoiNoise(int cellCount, float amplitude, uint32_t seed);
// Fill entire tile with water at a height
void fillWater(float height, uint16_t liquidType);