From 5df007b7b96b9015e2c50d7734881d06a2b9fe72 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 5 May 2026 04:57:42 -0700 Subject: [PATCH] feat(editor): random rotation, placed object list, quality of life - Random Rotation checkbox: each placed object gets a random Y rotation (great for natural-looking tree/rock placement without manual tweaking) - Placed Object List: collapsible list in Object panel showing all placed objects with name and position, click to select - Both features reduce repetitive manual work when building dense zones --- tools/editor/editor_ui.cpp | 24 ++++++++++++++++++++++++ tools/editor/object_placer.cpp | 8 +++++++- tools/editor/object_placer.hpp | 3 +++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index ded58532..bad3acb4 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -463,6 +463,9 @@ void EditorUI::renderObjectPanel(EditorApp& app) { float rot = placer.getPlacementRotationY(); if (ImGui::SliderFloat("Y Rotation", &rot, 0.0f, 360.0f, "%.0f deg")) placer.setPlacementRotationY(rot); + bool randRot = placer.getRandomRotation(); + if (ImGui::Checkbox("Random Rotation", &randRot)) + placer.setRandomRotation(randRot); float scale = placer.getPlacementScale(); if (ImGui::SliderFloat("Scale", &scale, 0.1f, 10.0f, "%.2f")) placer.setPlacementScale(scale); @@ -513,6 +516,27 @@ void EditorUI::renderObjectPanel(EditorApp& app) { ImGui::Separator(); ImGui::Text("Placed: %zu objects", placer.objectCount()); + if (placer.objectCount() > 0 && ImGui::CollapsingHeader("Object List")) { + ImGui::BeginChild("ObjPlacedList", ImVec2(0, 100), true); + for (int i = 0; i < static_cast(placer.objectCount()); i++) { + auto& o = const_cast&>(placer.getObjects())[i]; + std::string disp = o.path; + auto sl = disp.rfind('\\'); + if (sl != std::string::npos) disp = disp.substr(sl + 1); + char lbl[128]; + std::snprintf(lbl, sizeof(lbl), "%s (%.0f,%.0f,%.0f)##obj%d", + disp.c_str(), o.position.x, o.position.y, o.position.z, i); + if (ImGui::Selectable(lbl, o.selected)) { + placer.clearSelection(); + // Select by creating a ray through the object position + rendering::Ray r; + r.origin = o.position + glm::vec3(0, 0, 1); + r.direction = glm::vec3(0, 0, -1); + placer.selectAt(r, 1000.0f); + } + } + ImGui::EndChild(); + } if (auto* sel = placer.getSelected()) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0.9f, 0.3f, 1)); ImGui::Text("Selected: %s", sel->path.c_str()); diff --git a/tools/editor/object_placer.cpp b/tools/editor/object_placer.cpp index b9247026..e8c397b1 100644 --- a/tools/editor/object_placer.cpp +++ b/tools/editor/object_placer.cpp @@ -25,7 +25,13 @@ void ObjectPlacer::placeObject(const glm::vec3& position) { obj.nameId = 0; obj.uniqueId = nextUniqueId(); obj.position = position; - obj.rotation = glm::vec3(0.0f, placementRotY_, 0.0f); + float rotY = placementRotY_; + if (randomRotation_) { + static std::mt19937 rng(42); + std::uniform_real_distribution dist(0.0f, 360.0f); + rotY = dist(rng); + } + obj.rotation = glm::vec3(0.0f, rotY, 0.0f); obj.scale = placementScale_; obj.selected = false; diff --git a/tools/editor/object_placer.hpp b/tools/editor/object_placer.hpp index 6a2c60f7..af46b761 100644 --- a/tools/editor/object_placer.hpp +++ b/tools/editor/object_placer.hpp @@ -56,6 +56,8 @@ public: void setPlacementRotationY(float deg) { placementRotY_ = deg; } float getPlacementScale() const { return placementScale_; } void setPlacementScale(float s) { placementScale_ = s; } + bool getRandomRotation() const { return randomRotation_; } + void setRandomRotation(bool v) { randomRotation_ = v; } // Undo last placement bool canUndoPlace() const { return !undoStack_.empty(); } @@ -78,6 +80,7 @@ private: uint32_t uniqueIdCounter_ = 1; float placementRotY_ = 0.0f; float placementScale_ = 1.0f; + bool randomRotation_ = false; }; } // namespace editor