feat(editor): invert heights, fill entire tile with water, remove all water

- Invert Heights: flips terrain upside-down around midpoint (mountains
  become valleys and vice versa)
- Fill Entire Tile with Water: one-click fills all 256 chunks at the
  configured water height and liquid type
- Remove ALL Water: clears water from every chunk instantly
- Water panel now has three water operations: fill tile, remove under
  brush, remove all
- fillWater() and invertHeights() methods on TerrainEditor
This commit is contained in:
Kelsi 2026-05-05 07:49:48 -07:00
parent bd1356bd08
commit 825939db3b
3 changed files with 65 additions and 1 deletions

View file

@ -1470,13 +1470,21 @@ void EditorUI::renderWaterPanel(EditorApp& app) {
app.setWaterType(static_cast<uint16_t>(typeIdx));
ImGui::Separator();
if (ImGui::Button("Fill Entire Tile with Water", ImVec2(-1, 0))) {
app.getTerrainEditor().fillWater(app.getWaterHeight(), app.getWaterType());
app.showToast("Tile filled with water");
}
if (ImGui::Button("Remove Water Under Brush", ImVec2(-1, 0))) {
auto& brush = app.getTerrainEditor().brush();
if (brush.isActive()) {
app.getTerrainEditor().removeWater(brush.getPosition(), s.radius);
app.getEditorCamera(); // trigger dirty
}
}
if (ImGui::Button("Remove ALL Water", ImVec2(-1, 0))) {
for (int ci = 0; ci < 256; ci++)
app.getTerrainEditor().getTerrain()->waterData[ci].layers.clear();
app.showToast("All water removed");
}
ImGui::Separator();
ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f), "Left-click to place water");

View file

@ -860,6 +860,56 @@ void TerrainEditor::createHill(const glm::vec3& center, float radius, float heig
dirty_ = true;
}
void TerrainEditor::invertHeights() {
if (!terrain_) return;
// Find midpoint
float minH = 1e30f, maxH = -1e30f;
for (int ci = 0; ci < 256; ci++) {
auto& chunk = terrain_->chunks[ci];
if (!chunk.hasHeightMap()) continue;
for (int v = 0; v < 145; v++) {
minH = std::min(minH, chunk.heightMap.heights[v]);
maxH = std::max(maxH, chunk.heightMap.heights[v]);
}
}
float mid = (minH + maxH) * 0.5f;
for (int ci = 0; ci < 256; ci++) {
auto& chunk = terrain_->chunks[ci];
if (!chunk.hasHeightMap()) continue;
for (int v = 0; v < 145; v++)
chunk.heightMap.heights[v] = mid - (chunk.heightMap.heights[v] - mid);
dirtyChunks_.push_back(ci);
}
for (int ci = 0; ci < 256; ci++) stitchEdges(ci);
dirty_ = true;
}
void TerrainEditor::fillWater(float height, uint16_t liquidType) {
if (!terrain_) return;
for (int ci = 0; ci < 256; ci++) {
auto& water = terrain_->waterData[ci];
if (water.layers.empty()) {
pipeline::ADTTerrain::WaterLayer wl;
wl.liquidType = liquidType;
wl.flags = 0;
wl.minHeight = height;
wl.maxHeight = height;
wl.x = 0; wl.y = 0; wl.width = 9; wl.height = 9;
wl.heights.assign(81, height);
wl.mask.assign(8, 0xFF);
water.layers.push_back(wl);
} else {
auto& wl = water.layers[0];
wl.liquidType = liquidType;
wl.minHeight = height;
wl.maxHeight = height;
std::fill(wl.heights.begin(), wl.heights.end(), height);
}
dirtyChunks_.push_back(ci);
}
dirty_ = true;
}
void TerrainEditor::thermalErosion(int iterations, float talusAngle) {
if (!terrain_) return;
float unitSize = CHUNK_SIZE / 8.0f;

View file

@ -105,6 +105,12 @@ public:
// Thermal erosion: material falls downhill based on angle of repose
void thermalErosion(int iterations, float talusAngle);
// Invert terrain (flip heights around midpoint)
void invertHeights();
// Fill entire tile with water at a height
void fillWater(float height, uint16_t liquidType);
// Import/export heightmap (raw 16-bit grayscale, 129x129)
bool importHeightmap(const std::string& path, float heightScale);
bool exportHeightmap(const std::string& path, float heightScale);