feat(editor): smooth beaches tool for natural water-land transitions

- Smooth Beaches button in Water panel: creates gentle beach slopes
  near water level by blending terrain toward waterline height
- Configurable beach width (15 units default)
- Quadratic falloff creates natural concave beach profile
- Use after filling water to soften harsh land-water edges
- Workflow: sculpt → fill water → smooth beaches → paint sand
This commit is contained in:
Kelsi 2026-05-05 08:06:20 -07:00
parent c8897eca83
commit 426863f19a
3 changed files with 30 additions and 0 deletions

View file

@ -1513,6 +1513,10 @@ void EditorUI::renderWaterPanel(EditorApp& app) {
app.getTerrainEditor().removeWater(brush.getPosition(), s.radius); app.getTerrainEditor().removeWater(brush.getPosition(), s.radius);
} }
} }
if (ImGui::Button("Smooth Beaches", ImVec2(-1, 0))) {
app.getTerrainEditor().smoothBeaches(app.getWaterHeight(), 15.0f);
app.showToast("Beaches smoothed");
}
if (ImGui::Button("Remove ALL Water", ImVec2(-1, 0))) { if (ImGui::Button("Remove ALL Water", ImVec2(-1, 0))) {
for (int ci = 0; ci < 256; ci++) for (int ci = 0; ci < 256; ci++)
app.getTerrainEditor().getTerrain()->waterData[ci].layers.clear(); app.getTerrainEditor().getTerrain()->waterData[ci].layers.clear();

View file

@ -962,6 +962,29 @@ void TerrainEditor::fillWater(float height, uint16_t liquidType) {
dirty_ = true; dirty_ = true;
} }
void TerrainEditor::smoothBeaches(float waterHeight, float beachWidth) {
if (!terrain_) return;
for (int ci = 0; ci < 256; ci++) {
auto& chunk = terrain_->chunks[ci];
if (!chunk.hasHeightMap()) continue;
bool modified = false;
for (int v = 0; v < 145; v++) {
float absH = chunk.position[2] + chunk.heightMap.heights[v];
float distToWater = std::abs(absH - waterHeight);
if (distToWater < beachWidth) {
// Smooth toward water level with gentle slope
float t = distToWater / beachWidth;
float target = waterHeight + (absH > waterHeight ? 1.0f : -1.0f) * beachWidth * t * t;
float relTarget = target - chunk.position[2];
chunk.heightMap.heights[v] = chunk.heightMap.heights[v] * 0.3f + relTarget * 0.7f;
modified = true;
}
}
if (modified) { stitchEdges(ci); dirtyChunks_.push_back(ci); }
}
dirty_ = true;
}
void TerrainEditor::thermalErosion(int iterations, float talusAngle) { void TerrainEditor::thermalErosion(int iterations, float talusAngle) {
if (!terrain_) return; if (!terrain_) return;
float unitSize = CHUNK_SIZE / 8.0f; float unitSize = CHUNK_SIZE / 8.0f;

View file

@ -117,6 +117,9 @@ public:
// Fill entire tile with water at a height // Fill entire tile with water at a height
void fillWater(float height, uint16_t liquidType); void fillWater(float height, uint16_t liquidType);
// Smooth terrain near water level to create natural beaches
void smoothBeaches(float waterHeight, float beachWidth);
// Import/export heightmap (raw 16-bit grayscale, 129x129) // Import/export heightmap (raw 16-bit grayscale, 129x129)
bool importHeightmap(const std::string& path, float heightScale); bool importHeightmap(const std::string& path, float heightScale);
bool exportHeightmap(const std::string& path, float heightScale); bool exportHeightmap(const std::string& path, float heightScale);