diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 2d834e5c..01fdf713 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -627,7 +627,11 @@ void EditorUI::renderBrushPanel(EditorApp& app) { ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1), "Set start then end to apply"); } - if (ImGui::CollapsingHeader("Mirror Terrain")) { + if (ImGui::CollapsingHeader("Mirror / Rotate")) { + if (ImGui::Button("Rotate 90 CW", ImVec2(-1, 0))) { + app.getTerrainEditor().rotateTerrain90(); + app.showToast("Terrain rotated 90 degrees"); + } if (ImGui::Button("Mirror X (Left<>Right)", ImVec2(-1, 0))) { app.getTerrainEditor().mirrorX(); app.showToast("Terrain mirrored X"); diff --git a/tools/editor/terrain_editor.cpp b/tools/editor/terrain_editor.cpp index 1a0efa79..94baedb9 100644 --- a/tools/editor/terrain_editor.cpp +++ b/tools/editor/terrain_editor.cpp @@ -900,6 +900,51 @@ void TerrainEditor::applyVoronoiNoise(int cellCount, float amplitude, uint32_t s dirty_ = true; } +void TerrainEditor::rotateTerrain90() { + if (!terrain_) return; + // Snapshot all outer vertex heights into a 129x129 grid + std::array, 129> grid{}; + for (int cy = 0; cy < 16; cy++) { + for (int cx = 0; cx < 16; cx++) { + auto& chunk = terrain_->chunks[cy * 16 + cx]; + if (!chunk.hasHeightMap()) continue; + for (int v = 0; v < 145; v++) { + int row = v / 17, col = v % 17; + if (col > 8) continue; + grid[cy * 8 + row][cx * 8 + col] = chunk.heightMap.heights[v]; + } + } + } + // Rotate 90 degrees CW: new[x][128-y] = old[y][x] + std::array, 129> rotated{}; + for (int y = 0; y < 129; y++) + for (int x = 0; x < 129; x++) + rotated[x][128 - y] = grid[y][x]; + // Write back + for (int cy = 0; cy < 16; cy++) { + for (int cx = 0; cx < 16; cx++) { + auto& chunk = terrain_->chunks[cy * 16 + cx]; + if (!chunk.hasHeightMap()) continue; + for (int v = 0; v < 145; v++) { + int row = v / 17, col = v % 17; + if (col > 8) { + // Inner vertex: average of surrounding outer + int innerCol = col - 9; + int gy = cy * 8 + row, gx = cx * 8 + innerCol; + if (gy < 128 && gx < 128) + chunk.heightMap.heights[v] = (rotated[gy][gx] + rotated[gy][gx+1] + + rotated[gy+1][gx] + rotated[gy+1][gx+1]) * 0.25f; + } else { + chunk.heightMap.heights[v] = rotated[cy * 8 + row][cx * 8 + col]; + } + } + dirtyChunks_.push_back(cy * 16 + cx); + } + } + 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++) { diff --git a/tools/editor/terrain_editor.hpp b/tools/editor/terrain_editor.hpp index 065e0acc..436ec910 100644 --- a/tools/editor/terrain_editor.hpp +++ b/tools/editor/terrain_editor.hpp @@ -126,6 +126,9 @@ public: // Add random detail noise to existing terrain (preserves shape, adds roughness) void addDetailNoise(float amplitude, float frequency, uint32_t seed); + // Rotate terrain 90 degrees clockwise + void rotateTerrain90(); + // Import/export heightmap (raw 16-bit grayscale, 129x129) bool importHeightmap(const std::string& path, float heightScale); bool exportHeightmap(const std::string& path, float heightScale);