diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index ec67b694..d5dbf034 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -212,10 +212,17 @@ void EditorApp::processEvents() { } } if (sc == SDL_SCANCODE_Z && (event.key.keysym.mod & KMOD_CTRL)) { - if (event.key.keysym.mod & KMOD_SHIFT) - terrainEditor_.redo(); - else - terrainEditor_.undo(); + if (mode_ == EditorMode::Sculpt) { + if (event.key.keysym.mod & KMOD_SHIFT) + terrainEditor_.redo(); + else + terrainEditor_.undo(); + } else if (mode_ == EditorMode::PlaceObject || mode_ == EditorMode::NPC) { + if (!(event.key.keysym.mod & KMOD_SHIFT) && objectPlacer_.canUndoPlace()) { + objectPlacer_.undoLastPlace(); + objectsDirty_ = true; + } + } } } if (!io.WantCaptureKeyboard) @@ -554,6 +561,21 @@ void EditorApp::setGizmoAxis(TransformAxis axis) { viewport_.getGizmo().setTarget(sel->position, sel->scale); } +void EditorApp::snapSelectedToGround() { + auto* sel = objectPlacer_.getSelected(); + if (!sel || !terrain_.isLoaded()) return; + + // Cast ray straight down from object position + rendering::Ray ray; + ray.origin = sel->position + glm::vec3(0, 0, 500); + ray.direction = glm::vec3(0, 0, -1); + glm::vec3 hitPos; + if (terrainEditor_.raycastTerrain(ray, hitPos)) { + sel->position.z = hitPos.z; + objectsDirty_ = true; + } +} + void EditorApp::resetCamera() { camera_.setPosition(glm::vec3(0.0f, 0.0f, 300.0f)); camera_.setYawPitch(0.0f, -30.0f); diff --git a/tools/editor/editor_app.hpp b/tools/editor/editor_app.hpp index 3519ef95..88e51276 100644 --- a/tools/editor/editor_app.hpp +++ b/tools/editor/editor_app.hpp @@ -61,6 +61,7 @@ public: void startGizmoMode(TransformMode mode); void setGizmoAxis(TransformAxis axis); + void snapSelectedToGround(); TransformGizmo& getGizmo() { return viewport_.getGizmo(); } bool shouldOpenContextMenu() const { return openContextMenu_; } void clearContextMenuFlag() { openContextMenu_ = false; } diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index b5f866ce..8cfb5413 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -360,8 +360,10 @@ void EditorUI::renderObjectPanel(EditorApp& app) { if (changed) app.markObjectsDirty(); - if (ImGui::Button("Delete", ImVec2(100, 0))) placer.deleteSelected(); + if (ImGui::Button("Snap Ground", ImVec2(100, 0))) + app.snapSelectedToGround(); ImGui::SameLine(); + if (ImGui::Button("Delete", ImVec2(100, 0))) placer.deleteSelected(); if (ImGui::Button("Duplicate", ImVec2(100, 0))) { PlacedObject copy = *sel; copy.uniqueId = 0; diff --git a/tools/editor/object_placer.cpp b/tools/editor/object_placer.cpp index 9ae37555..d7b8d0f6 100644 --- a/tools/editor/object_placer.cpp +++ b/tools/editor/object_placer.cpp @@ -29,6 +29,8 @@ void ObjectPlacer::placeObject(const glm::vec3& position) { obj.selected = false; objects_.push_back(obj); + undoStack_.push_back(static_cast(objects_.size() - 1)); + if (undoStack_.size() > 50) undoStack_.erase(undoStack_.begin()); LOG_INFO("Placed ", (activeType_ == PlaceableType::M2 ? "M2" : "WMO"), ": ", activePath_, " at (", position.x, ",", position.y, ",", position.z, ")"); } @@ -93,6 +95,19 @@ void ObjectPlacer::deleteSelected() { selectedIdx_ = -1; } +void ObjectPlacer::undoLastPlace() { + if (undoStack_.empty()) return; + int idx = undoStack_.back(); + undoStack_.pop_back(); + if (idx >= 0 && idx < static_cast(objects_.size())) { + if (selectedIdx_ == idx) selectedIdx_ = -1; + else if (selectedIdx_ > idx) selectedIdx_--; + objects_.erase(objects_.begin() + idx); + // Adjust remaining undo indices + for (auto& i : undoStack_) { if (i > idx) i--; } + } +} + void ObjectPlacer::syncToTerrain() { if (!terrain_) return; diff --git a/tools/editor/object_placer.hpp b/tools/editor/object_placer.hpp index f01945da..45abfef7 100644 --- a/tools/editor/object_placer.hpp +++ b/tools/editor/object_placer.hpp @@ -57,6 +57,10 @@ public: float getPlacementScale() const { return placementScale_; } void setPlacementScale(float s) { placementScale_ = s; } + // Undo last placement + bool canUndoPlace() const { return !undoStack_.empty(); } + void undoLastPlace(); + private: uint32_t nextUniqueId(); @@ -65,6 +69,7 @@ private: PlaceableType activeType_ = PlaceableType::M2; std::vector objects_; + std::vector undoStack_; // indices of recently placed objects int selectedIdx_ = -1; uint32_t uniqueIdCounter_ = 1; float placementRotY_ = 0.0f;