mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 00:53:52 +00:00
feat(editor): multi-select objects, time-of-day lighting, WOT loading
- Multi-select: Ctrl+Shift+Click adds objects to selection, transforms (move/rotate/scale/delete) operate on all selected objects at once - Time-of-day slider (0-24h) with automatic sun angle, light color, ambient, fog, and sky color transitions (dawn/day/dusk/night) - View > Sky/Lighting menu: color pickers for light/ambient/fog, fog distance sliders, preset buttons (Dawn/Noon/Dusk/Night) - loadADT prefers WOT/WHM open format from custom_zones/output dirs - Selection count display when multiple objects selected - setSkyPreset now delegates to setTimeOfDay for consistency
This commit is contained in:
parent
d44eaec487
commit
ddf97e9b8a
7 changed files with 147 additions and 30 deletions
|
|
@ -428,15 +428,25 @@ void EditorApp::processEvents() {
|
|||
showToast("Start point set — click terrain for end");
|
||||
}
|
||||
}
|
||||
// Ctrl+click = select object (any mode)
|
||||
// Ctrl+click = select (Ctrl+Shift+click = add to selection)
|
||||
else if ((event.key.keysym.mod & KMOD_CTRL) || (SDL_GetModState() & KMOD_CTRL)) {
|
||||
bool additive = (SDL_GetModState() & KMOD_SHIFT) != 0;
|
||||
auto ext = window_->getVkContext()->getSwapchainExtent();
|
||||
rendering::Ray ray = camera_.getCamera().screenToWorldRay(
|
||||
static_cast<float>(event.button.x),
|
||||
static_cast<float>(event.button.y),
|
||||
static_cast<float>(ext.width),
|
||||
static_cast<float>(ext.height));
|
||||
objectPlacer_.selectAt(ray, 200.0f);
|
||||
if (additive) {
|
||||
int prevSel = objectPlacer_.getSelectedIndex();
|
||||
int hit = objectPlacer_.selectAt(ray, 200.0f);
|
||||
if (hit >= 0) {
|
||||
if (prevSel >= 0) objectPlacer_.addToSelection(prevSel);
|
||||
objectPlacer_.addToSelection(hit);
|
||||
}
|
||||
} else {
|
||||
objectPlacer_.selectAt(ray, 200.0f);
|
||||
}
|
||||
} else if (mode_ == EditorMode::NPC) {
|
||||
auto ext = window_->getVkContext()->getSwapchainExtent();
|
||||
rendering::Ray ray = camera_.getCamera().screenToWorldRay(
|
||||
|
|
@ -1049,18 +1059,9 @@ void EditorApp::updateToasts(float dt) {
|
|||
|
||||
void EditorApp::setSkyPreset(int preset) {
|
||||
switch (preset) {
|
||||
case 0: // Day
|
||||
viewport_.setClearColor(0.4f, 0.6f, 0.9f);
|
||||
viewport_.setLightDir(glm::normalize(glm::vec3(0.5f, -1.0f, 0.8f)));
|
||||
break;
|
||||
case 1: // Dusk
|
||||
viewport_.setClearColor(0.6f, 0.3f, 0.2f);
|
||||
viewport_.setLightDir(glm::normalize(glm::vec3(0.8f, -0.3f, 0.1f)));
|
||||
break;
|
||||
case 2: // Night
|
||||
viewport_.setClearColor(0.05f, 0.05f, 0.12f);
|
||||
viewport_.setLightDir(glm::normalize(glm::vec3(0.2f, -0.5f, 0.8f)));
|
||||
break;
|
||||
case 0: viewport_.setTimeOfDay(12.0f); break;
|
||||
case 1: viewport_.setTimeOfDay(18.0f); break;
|
||||
case 2: viewport_.setTimeOfDay(22.0f); break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ public:
|
|||
NpcPresets& getNpcPresets() { return npcPresets_; }
|
||||
QuestEditor& getQuestEditor() { return questEditor_; }
|
||||
AssetBrowser& getAssetBrowser() { return assetBrowser_; }
|
||||
EditorViewport& getViewport() { return viewport_; }
|
||||
rendering::TerrainRenderer* getTerrainRenderer();
|
||||
rendering::M2Renderer* getM2Renderer() { return viewport_.getM2Renderer(); }
|
||||
pipeline::AssetManager* getAssetManager() { return assetManager_.get(); }
|
||||
|
|
|
|||
|
|
@ -382,9 +382,21 @@ void EditorUI::renderMenuBar(EditorApp& app) {
|
|||
if (ImGui::MenuItem("Center on Terrain", "Home")) app.centerOnTerrain();
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginMenu("Sky / Lighting")) {
|
||||
if (ImGui::MenuItem("Day")) app.setSkyPreset(0);
|
||||
if (ImGui::MenuItem("Dusk")) app.setSkyPreset(1);
|
||||
if (ImGui::MenuItem("Night")) app.setSkyPreset(2);
|
||||
auto& vp = app.getViewport();
|
||||
float tod = vp.getTimeOfDay();
|
||||
if (ImGui::SliderFloat("Time of Day", &tod, 0.0f, 24.0f, "%.1fh"))
|
||||
vp.setTimeOfDay(tod);
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Dawn (6:30)")) vp.setTimeOfDay(6.5f);
|
||||
if (ImGui::MenuItem("Noon (12:00)")) vp.setTimeOfDay(12.0f);
|
||||
if (ImGui::MenuItem("Dusk (18:00)")) vp.setTimeOfDay(18.0f);
|
||||
if (ImGui::MenuItem("Night (22:00)")) vp.setTimeOfDay(22.0f);
|
||||
ImGui::Separator();
|
||||
ImGui::ColorEdit3("Light", &vp.getLightColor().x, ImGuiColorEditFlags_Float);
|
||||
ImGui::ColorEdit3("Ambient", &vp.getAmbientColor().x, ImGuiColorEditFlags_Float);
|
||||
ImGui::ColorEdit3("Fog", &vp.getFogColor().x, ImGuiColorEditFlags_Float);
|
||||
ImGui::SliderFloat("Fog Near", &vp.getFogNear(), 100.0f, 10000.0f);
|
||||
ImGui::SliderFloat("Fog Far", &vp.getFogFar(), 500.0f, 20000.0f);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
|
@ -1424,7 +1436,10 @@ void EditorUI::renderObjectPanel(EditorApp& app) {
|
|||
}
|
||||
if (auto* sel = placer.getSelected()) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0.9f, 0.3f, 1));
|
||||
ImGui::Text("Selected: %s", sel->path.c_str());
|
||||
if (placer.isMultiSelected())
|
||||
ImGui::Text("Selected: %zu objects", placer.selectionCount());
|
||||
else
|
||||
ImGui::Text("Selected: %s", sel->path.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
bool changed = false;
|
||||
|
|
|
|||
|
|
@ -730,6 +730,41 @@ void EditorViewport::destroyPerFrameResources() {
|
|||
}
|
||||
}
|
||||
|
||||
void EditorViewport::setTimeOfDay(float t) {
|
||||
timeOfDay_ = std::clamp(t, 0.0f, 24.0f);
|
||||
float hour = timeOfDay_;
|
||||
|
||||
// Sun angle: noon=overhead, 6am/6pm=horizon, night=below
|
||||
float sunAngle = (hour - 6.0f) / 12.0f * 3.14159f;
|
||||
lightDir_ = glm::normalize(glm::vec3(std::cos(sunAngle) * 0.5f, -1.0f, std::sin(sunAngle)));
|
||||
|
||||
// Dawn/dusk warm tones, noon white, night blue
|
||||
if (hour >= 6.0f && hour <= 8.0f) {
|
||||
float t2 = (hour - 6.0f) / 2.0f;
|
||||
lightColor_ = glm::mix(glm::vec3(1.0f, 0.5f, 0.2f), glm::vec3(1.0f, 0.95f, 0.85f), t2);
|
||||
ambientColor_ = glm::mix(glm::vec3(0.15f, 0.1f, 0.2f), glm::vec3(0.3f, 0.3f, 0.35f), t2);
|
||||
fogColor_ = glm::mix(glm::vec3(0.5f, 0.3f, 0.3f), glm::vec3(0.6f, 0.7f, 0.8f), t2);
|
||||
} else if (hour >= 17.0f && hour <= 19.0f) {
|
||||
float t2 = (hour - 17.0f) / 2.0f;
|
||||
lightColor_ = glm::mix(glm::vec3(1.0f, 0.95f, 0.85f), glm::vec3(1.0f, 0.4f, 0.15f), t2);
|
||||
ambientColor_ = glm::mix(glm::vec3(0.3f, 0.3f, 0.35f), glm::vec3(0.1f, 0.08f, 0.15f), t2);
|
||||
fogColor_ = glm::mix(glm::vec3(0.6f, 0.7f, 0.8f), glm::vec3(0.4f, 0.25f, 0.3f), t2);
|
||||
} else if (hour < 6.0f || hour > 19.0f) {
|
||||
lightColor_ = glm::vec3(0.15f, 0.15f, 0.25f);
|
||||
ambientColor_ = glm::vec3(0.05f, 0.05f, 0.1f);
|
||||
fogColor_ = glm::vec3(0.1f, 0.1f, 0.15f);
|
||||
} else {
|
||||
lightColor_ = glm::vec3(1.0f, 0.95f, 0.85f);
|
||||
ambientColor_ = glm::vec3(0.3f, 0.3f, 0.35f);
|
||||
fogColor_ = glm::vec3(0.6f, 0.7f, 0.8f);
|
||||
}
|
||||
|
||||
// Sky/clear color follows fog
|
||||
clearR_ = fogColor_.x * 0.7f;
|
||||
clearG_ = fogColor_.y * 0.7f;
|
||||
clearB_ = fogColor_.z * 0.7f;
|
||||
}
|
||||
|
||||
void EditorViewport::updatePerFrameUBO() {
|
||||
uint32_t frame = vkCtx_->getCurrentFrame();
|
||||
|
||||
|
|
@ -738,11 +773,11 @@ void EditorViewport::updatePerFrameUBO() {
|
|||
data.projection = camera_->getProjectionMatrix();
|
||||
data.lightSpaceMatrix = glm::mat4(1.0f);
|
||||
data.lightDir = glm::vec4(lightDir_, 0.0f);
|
||||
data.lightColor = glm::vec4(1.0f, 0.95f, 0.85f, 0.0f);
|
||||
data.ambientColor = glm::vec4(0.3f, 0.3f, 0.35f, 0.0f);
|
||||
data.lightColor = glm::vec4(lightColor_, 0.0f);
|
||||
data.ambientColor = glm::vec4(ambientColor_, 0.0f);
|
||||
data.viewPos = glm::vec4(camera_->getPosition(), 0.0f);
|
||||
data.fogColor = glm::vec4(0.6f, 0.7f, 0.8f, 0.0f);
|
||||
data.fogParams = glm::vec4(5000.0f, 10000.0f, 0.0f, 0.0f);
|
||||
data.fogColor = glm::vec4(fogColor_, 0.0f);
|
||||
data.fogParams = glm::vec4(fogNear_, fogFar_, 0.0f, 0.0f);
|
||||
data.shadowParams = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
std::memcpy(perFrameUBOMapped_[frame], &data, sizeof(data));
|
||||
|
|
|
|||
|
|
@ -62,6 +62,14 @@ public:
|
|||
void setLightDir(const glm::vec3& d) { lightDir_ = d; }
|
||||
glm::vec3 getLightDir() const { return lightDir_; }
|
||||
|
||||
void setTimeOfDay(float t);
|
||||
float getTimeOfDay() const { return timeOfDay_; }
|
||||
glm::vec3& getLightColor() { return lightColor_; }
|
||||
glm::vec3& getAmbientColor() { return ambientColor_; }
|
||||
glm::vec3& getFogColor() { return fogColor_; }
|
||||
float& getFogNear() { return fogNear_; }
|
||||
float& getFogFar() { return fogFar_; }
|
||||
|
||||
rendering::TerrainRenderer* getTerrainRenderer() { return terrainRenderer_.get(); }
|
||||
rendering::M2Renderer* getM2Renderer() { return m2Renderer_.get(); }
|
||||
|
||||
|
|
@ -95,6 +103,11 @@ private:
|
|||
bool wireframe_ = false;
|
||||
float clearR_ = 0.15f, clearG_ = 0.15f, clearB_ = 0.2f;
|
||||
glm::vec3 lightDir_ = glm::normalize(glm::vec3(0.5f, -1.0f, 0.3f));
|
||||
glm::vec3 lightColor_ = glm::vec3(1.0f, 0.95f, 0.85f);
|
||||
glm::vec3 ambientColor_ = glm::vec3(0.3f, 0.3f, 0.35f);
|
||||
glm::vec3 fogColor_ = glm::vec3(0.6f, 0.7f, 0.8f);
|
||||
float fogNear_ = 5000.0f, fogFar_ = 10000.0f;
|
||||
float timeOfDay_ = 12.0f;
|
||||
|
||||
// Ghost preview state
|
||||
std::string ghostModelPath_;
|
||||
|
|
|
|||
|
|
@ -75,7 +75,32 @@ int ObjectPlacer::selectAt(const rendering::Ray& ray, float maxDist) {
|
|||
return bestIdx;
|
||||
}
|
||||
|
||||
void ObjectPlacer::addToSelection(int idx) {
|
||||
if (idx < 0 || idx >= static_cast<int>(objects_.size())) return;
|
||||
for (int si : selectedIndices_) { if (si == idx) return; }
|
||||
selectedIndices_.push_back(idx);
|
||||
objects_[idx].selected = true;
|
||||
selectedIdx_ = idx;
|
||||
}
|
||||
|
||||
void ObjectPlacer::toggleSelection(int idx) {
|
||||
if (idx < 0 || idx >= static_cast<int>(objects_.size())) return;
|
||||
auto it = std::find(selectedIndices_.begin(), selectedIndices_.end(), idx);
|
||||
if (it != selectedIndices_.end()) {
|
||||
objects_[idx].selected = false;
|
||||
selectedIndices_.erase(it);
|
||||
selectedIdx_ = selectedIndices_.empty() ? -1 : selectedIndices_.back();
|
||||
} else {
|
||||
addToSelection(idx);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectPlacer::clearSelection() {
|
||||
for (int idx : selectedIndices_) {
|
||||
if (idx >= 0 && idx < static_cast<int>(objects_.size()))
|
||||
objects_[idx].selected = false;
|
||||
}
|
||||
selectedIndices_.clear();
|
||||
if (selectedIdx_ >= 0 && selectedIdx_ < static_cast<int>(objects_.size()))
|
||||
objects_[selectedIdx_].selected = false;
|
||||
selectedIdx_ = -1;
|
||||
|
|
@ -87,22 +112,43 @@ PlacedObject* ObjectPlacer::getSelected() {
|
|||
}
|
||||
|
||||
void ObjectPlacer::moveSelected(const glm::vec3& delta) {
|
||||
if (auto* obj = getSelected()) obj->position += delta;
|
||||
if (selectedIndices_.size() > 1) {
|
||||
for (int idx : selectedIndices_) objects_[idx].position += delta;
|
||||
} else if (auto* obj = getSelected()) {
|
||||
obj->position += delta;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectPlacer::rotateSelected(const glm::vec3& deltaDeg) {
|
||||
if (auto* obj = getSelected()) obj->rotation += deltaDeg;
|
||||
if (selectedIndices_.size() > 1) {
|
||||
for (int idx : selectedIndices_) objects_[idx].rotation += deltaDeg;
|
||||
} else if (auto* obj = getSelected()) {
|
||||
obj->rotation += deltaDeg;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectPlacer::scaleSelected(float delta) {
|
||||
if (auto* obj = getSelected())
|
||||
if (selectedIndices_.size() > 1) {
|
||||
for (int idx : selectedIndices_)
|
||||
objects_[idx].scale = std::max(0.1f, objects_[idx].scale + delta);
|
||||
} else if (auto* obj = getSelected()) {
|
||||
obj->scale = std::max(0.1f, obj->scale + delta);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectPlacer::deleteSelected() {
|
||||
if (selectedIdx_ < 0 || selectedIdx_ >= static_cast<int>(objects_.size())) return;
|
||||
objects_.erase(objects_.begin() + selectedIdx_);
|
||||
selectedIdx_ = -1;
|
||||
if (!selectedIndices_.empty()) {
|
||||
std::sort(selectedIndices_.begin(), selectedIndices_.end(), std::greater<int>());
|
||||
for (int idx : selectedIndices_) {
|
||||
if (idx >= 0 && idx < static_cast<int>(objects_.size()))
|
||||
objects_.erase(objects_.begin() + idx);
|
||||
}
|
||||
selectedIndices_.clear();
|
||||
selectedIdx_ = -1;
|
||||
} else if (selectedIdx_ >= 0 && selectedIdx_ < static_cast<int>(objects_.size())) {
|
||||
objects_.erase(objects_.begin() + selectedIdx_);
|
||||
selectedIdx_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectPlacer::scatter(const glm::vec3& center, float radius, int count,
|
||||
|
|
|
|||
|
|
@ -34,13 +34,18 @@ public:
|
|||
// Place object at world position
|
||||
void placeObject(const glm::vec3& position);
|
||||
|
||||
// Select object nearest to ray
|
||||
// Select object nearest to ray (Shift adds to selection)
|
||||
int selectAt(const rendering::Ray& ray, float maxDist = 50.0f);
|
||||
void addToSelection(int idx);
|
||||
void toggleSelection(int idx);
|
||||
void clearSelection();
|
||||
int getSelectedIndex() const { return selectedIdx_; }
|
||||
PlacedObject* getSelected();
|
||||
const std::vector<int>& getSelectedIndices() const { return selectedIndices_; }
|
||||
size_t selectionCount() const { return selectedIndices_.size(); }
|
||||
bool isMultiSelected() const { return selectedIndices_.size() > 1; }
|
||||
|
||||
// Transform selected
|
||||
// Transform selected (operates on all selected objects)
|
||||
void moveSelected(const glm::vec3& delta);
|
||||
void rotateSelected(const glm::vec3& deltaDeg);
|
||||
void scaleSelected(float delta);
|
||||
|
|
@ -85,6 +90,7 @@ private:
|
|||
std::vector<PlacedObject> objects_;
|
||||
std::vector<int> undoStack_; // indices of recently placed objects
|
||||
int selectedIdx_ = -1;
|
||||
std::vector<int> selectedIndices_;
|
||||
uint32_t uniqueIdCounter_ = 1;
|
||||
float placementRotY_ = 0.0f;
|
||||
float placementScale_ = 1.0f;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue