mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-05 16:43:52 +00:00
feat(editor): object scatter, camera bookmarks, shortcut hints
- Object scatter tool: place N copies of selected M2/WMO in a radius with random rotation and scale range (Min/Max Scale slider) - Camera bookmarks: F5 saves current position, View > Load Bookmark to jump back — useful for working on different parts of a large zone - Shortcut hints shown at bottom of Object panel (G=move, R=rotate, T=scale, Del=remove) - DragFloatRange2 for min/max scale in scatter UI
This commit is contained in:
parent
48026421c9
commit
5daa359e74
5 changed files with 90 additions and 0 deletions
|
|
@ -189,6 +189,7 @@ void EditorApp::processEvents() {
|
|||
if (event.type == SDL_KEYDOWN) {
|
||||
auto sc = event.key.keysym.scancode;
|
||||
if (sc == SDL_SCANCODE_F3) setWireframe(!isWireframe());
|
||||
if (sc == SDL_SCANCODE_F5) saveBookmark("");
|
||||
// Transform shortcuts (Blender-style)
|
||||
if (objectPlacer_.getSelected()) {
|
||||
if (sc == SDL_SCANCODE_G) startGizmoMode(TransformMode::Move);
|
||||
|
|
@ -603,6 +604,19 @@ void EditorApp::setGizmoAxis(TransformAxis axis) {
|
|||
viewport_.getGizmo().setTarget(sel->position, sel->scale);
|
||||
}
|
||||
|
||||
void EditorApp::saveBookmark(const std::string& name) {
|
||||
CameraBookmark bm;
|
||||
bm.pos = camera_.getCamera().getPosition();
|
||||
bm.yaw = 0; bm.pitch = 0; // EditorCamera doesn't expose these directly
|
||||
bm.name = name.empty() ? ("Bookmark " + std::to_string(bookmarks_.size() + 1)) : name;
|
||||
bookmarks_.push_back(bm);
|
||||
}
|
||||
|
||||
void EditorApp::loadBookmark(int index) {
|
||||
if (index < 0 || index >= static_cast<int>(bookmarks_.size())) return;
|
||||
camera_.setPosition(bookmarks_[index].pos);
|
||||
}
|
||||
|
||||
void EditorApp::addAdjacentTile(int offsetX, int offsetY) {
|
||||
if (!terrain_.isLoaded()) return;
|
||||
int newX = loadedTileX_ + offsetX;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,12 @@ public:
|
|||
|
||||
// Multi-tile support
|
||||
void addAdjacentTile(int offsetX, int offsetY);
|
||||
|
||||
// Camera bookmarks
|
||||
struct CameraBookmark { glm::vec3 pos; float yaw; float pitch; std::string name; };
|
||||
void saveBookmark(const std::string& name);
|
||||
void loadBookmark(int index);
|
||||
const std::vector<CameraBookmark>& getBookmarks() const { return bookmarks_; }
|
||||
TransformGizmo& getGizmo() { return viewport_.getGizmo(); }
|
||||
bool shouldOpenContextMenu() const { return openContextMenu_; }
|
||||
void clearContextMenuFlag() { openContextMenu_ = false; }
|
||||
|
|
@ -102,6 +108,7 @@ private:
|
|||
bool objectsDirty_ = false;
|
||||
bool openContextMenu_ = false;
|
||||
std::string lastSavePath_;
|
||||
std::vector<CameraBookmark> bookmarks_;
|
||||
size_t lastObjectCount_ = 0;
|
||||
EditorMode mode_ = EditorMode::Sculpt;
|
||||
float waterHeight_ = 100.0f;
|
||||
|
|
|
|||
|
|
@ -94,6 +94,16 @@ void EditorUI::renderMenuBar(EditorApp& app) {
|
|||
bool wf = app.isWireframe();
|
||||
if (ImGui::MenuItem("Wireframe", "F3", &wf)) app.setWireframe(wf);
|
||||
if (ImGui::MenuItem("Reset Camera")) app.resetCamera();
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Save Bookmark", "F5")) app.saveBookmark("");
|
||||
auto& bmarks = app.getBookmarks();
|
||||
if (!bmarks.empty() && ImGui::BeginMenu("Load Bookmark")) {
|
||||
for (int i = 0; i < static_cast<int>(bmarks.size()); i++) {
|
||||
if (ImGui::MenuItem(bmarks[i].name.c_str()))
|
||||
app.loadBookmark(i);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
|
|
@ -379,6 +389,32 @@ void EditorUI::renderObjectPanel(EditorApp& app) {
|
|||
if (ImGui::Button("Deselect", ImVec2(100, 0)))
|
||||
placer.clearSelection();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
// Object scatter
|
||||
if (ImGui::CollapsingHeader("Scatter Objects")) {
|
||||
static int objScatterCount = 8;
|
||||
static float objScatterRadius = 60.0f;
|
||||
static float objMinScale = 0.8f;
|
||||
static float objMaxScale = 1.5f;
|
||||
ImGui::SliderInt("Count##objsc", &objScatterCount, 1, 50);
|
||||
ImGui::SliderFloat("Radius##objsc", &objScatterRadius, 10.0f, 300.0f);
|
||||
ImGui::DragFloatRange2("Scale##objsc", &objMinScale, &objMaxScale, 0.05f, 0.1f, 10.0f);
|
||||
auto& brush = app.getTerrainEditor().brush();
|
||||
if (ImGui::Button("Scatter at Cursor##obj", ImVec2(-1, 0))) {
|
||||
if (brush.isActive() && !placer.getActivePath().empty()) {
|
||||
placer.scatter(brush.getPosition(), objScatterRadius,
|
||||
objScatterCount, objMinScale, objMaxScale);
|
||||
app.markObjectsDirty();
|
||||
}
|
||||
}
|
||||
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1),
|
||||
"Scatters selected model with random rotation/scale");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.9f, 0.7f, 1), "Left-click: place | Ctrl+click: select");
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.9f, 0.7f, 1), "G: move | R: rotate | T: scale | Del: remove");
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "core/logger.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
|
|
@ -95,6 +96,34 @@ void ObjectPlacer::deleteSelected() {
|
|||
selectedIdx_ = -1;
|
||||
}
|
||||
|
||||
void ObjectPlacer::scatter(const glm::vec3& center, float radius, int count,
|
||||
float minScale, float maxScale) {
|
||||
if (activePath_.empty()) return;
|
||||
std::mt19937 rng(static_cast<uint32_t>(center.x * 100 + center.y * 37 + objects_.size()));
|
||||
std::uniform_real_distribution<float> distAngle(0.0f, 6.2831853f);
|
||||
std::uniform_real_distribution<float> distDist(0.0f, 1.0f);
|
||||
std::uniform_real_distribution<float> distRot(0.0f, 360.0f);
|
||||
std::uniform_real_distribution<float> distScale(minScale, maxScale);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
float angle = distAngle(rng);
|
||||
float dist = std::sqrt(distDist(rng)) * radius;
|
||||
glm::vec3 pos = center + glm::vec3(std::cos(angle) * dist, std::sin(angle) * dist, 0.0f);
|
||||
|
||||
PlacedObject obj;
|
||||
obj.type = activeType_;
|
||||
obj.path = activePath_;
|
||||
obj.nameId = 0;
|
||||
obj.uniqueId = nextUniqueId();
|
||||
obj.position = pos;
|
||||
obj.rotation = glm::vec3(0.0f, distRot(rng), 0.0f);
|
||||
obj.scale = distScale(rng);
|
||||
obj.selected = false;
|
||||
objects_.push_back(obj);
|
||||
}
|
||||
LOG_INFO("Scattered ", count, " objects in radius ", radius);
|
||||
}
|
||||
|
||||
void ObjectPlacer::undoLastPlace() {
|
||||
if (undoStack_.empty()) return;
|
||||
int idx = undoStack_.back();
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ public:
|
|||
bool canUndoPlace() const { return !undoStack_.empty(); }
|
||||
void undoLastPlace();
|
||||
|
||||
// Scatter: place multiple copies with random offset/rotation
|
||||
void scatter(const glm::vec3& center, float radius, int count,
|
||||
float minScale, float maxScale);
|
||||
|
||||
private:
|
||||
uint32_t nextUniqueId();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue