From 3a6f119f7a2a59e8b8fa600090eacf35b15efddf Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 7 May 2026 09:48:59 -0700 Subject: [PATCH] fix(editor): NPC nameplates align with NPC + togglable to selected only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three issues fixed in one pass: 1. Z-offset was 35 yards — nameplates floated way above the actual NPC position. Dropped to 3.5 yards (just above an average creature's head) so the label reads as attached. 2. Horizontal alignment was a fixed -30 px offset, which only looked centered for ~6-character names. Switched to ImGui::CalcTextSize and centering on the projected position. 3. Nameplates were always-on, which got noisy fast on populated zones. Added "Nameplate on selected only" checkbox in the NPC panel (default ON); when unchecked, all NPCs show their names. Vertical position also slightly tightened (sy - textSz.y - 2 vs sy - 10) so the text baseline aligns rather than floating below. --- tools/editor/editor_ui.cpp | 26 +++++++++++++++++++++++--- tools/editor/editor_viewport.hpp | 9 +++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 9486839f..f8ad5711 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -83,17 +83,34 @@ void EditorUI::render(EditorApp& app) { auto& cam = app.getEditorCamera().getCamera(); auto vp2 = ImGui::GetMainViewport(); glm::mat4 viewProj = cam.getProjectionMatrix() * cam.getViewMatrix(); - for (const auto& npc : app.getNpcSpawner().getSpawns()) { + bool selectedOnly = app.getViewport().getNpcNameplatesSelectedOnly(); + int selIdx = app.getNpcSpawner().getSelectedIndex(); + const auto& spawns = app.getNpcSpawner().getSpawns(); + for (size_t i = 0; i < spawns.size(); ++i) { + const auto& npc = spawns[i]; + // Toggle: when selectedOnly is on, skip every NPC except the + // selected one. Was previously always-on, which got noisy on + // populated zones. + if (selectedOnly && static_cast(i) != selIdx) continue; + // Z offset: was 35 yards (way too high; nameplates floated in + // the sky). Drop to ~3.5 yards which sits just above an + // average creature's head and reads as attached. glm::vec4 clip = viewProj * glm::vec4(npc.position.x, npc.position.y, - npc.position.z + 35.0f, 1.0f); + npc.position.z + 3.5f, 1.0f); if (clip.w <= 0.01f) continue; glm::vec3 ndc = glm::vec3(clip) / clip.w; float sx = (ndc.x * 0.5f + 0.5f) * vp2->Size.x; float sy = (ndc.y * 0.5f + 0.5f) * vp2->Size.y; if (sx < 0 || sx > vp2->Size.x || sy < 0 || sy > vp2->Size.y) continue; + // Center the text on the projected position by measuring the + // actual string width — the previous fixed -30 X offset + // misaligned every name that wasn't ~6 chars long. + ImVec2 textSz = ImGui::CalcTextSize(npc.name.c_str()); ImVec4 col = npc.hostile ? ImVec4(1, 0.3f, 0.3f, 0.9f) : ImVec4(0.3f, 1, 0.3f, 0.9f); - ImGui::GetForegroundDrawList()->AddText(ImVec2(sx - 30, sy - 10), ImGui::ColorConvertFloat4ToU32(col), + ImGui::GetForegroundDrawList()->AddText( + ImVec2(sx - textSz.x * 0.5f, sy - textSz.y - 2), + ImGui::ColorConvertFloat4ToU32(col), npc.name.c_str()); } } @@ -1818,6 +1835,9 @@ void EditorUI::renderNpcPanel(EditorApp& app) { bool showMarkers = app.getViewport().getShowNpcMarkers(); if (ImGui::Checkbox("Show Position Markers", &showMarkers)) app.getViewport().setShowNpcMarkers(showMarkers); + bool selOnly = app.getViewport().getNpcNameplatesSelectedOnly(); + if (ImGui::Checkbox("Nameplate on selected only", &selOnly)) + app.getViewport().setNpcNameplatesSelectedOnly(selOnly); ImGui::Separator(); // ---- Creature Browser ---- diff --git a/tools/editor/editor_viewport.hpp b/tools/editor/editor_viewport.hpp index 7927afa2..743ff0e8 100644 --- a/tools/editor/editor_viewport.hpp +++ b/tools/editor/editor_viewport.hpp @@ -62,6 +62,14 @@ public: void setWireframe(bool enabled); void setShowNpcMarkers(bool show) { showNpcMarkers_ = show; } bool getShowNpcMarkers() const { return showNpcMarkers_; } + + // Nameplates: when true, only show the floating name label on the + // currently-selected NPC; otherwise show on all of them. Default true + // to keep the viewport quiet — was previously always-on, which got + // noisy fast on populated zones. + void setNpcNameplatesSelectedOnly(bool sel) { npcNameplatesSelectedOnly_ = sel; } + bool getNpcNameplatesSelectedOnly() const { return npcNameplatesSelectedOnly_; } + bool isWireframe() const { return wireframe_; } void setClearColor(float r, float g, float b) { clearR_=r; clearG_=g; clearB_=b; } @@ -143,6 +151,7 @@ private: // NPC position markers bool showNpcMarkers_ = true; + bool npcNameplatesSelectedOnly_ = true; VkBuffer npcMarkerVB_ = VK_NULL_HANDLE; VmaAllocation npcMarkerVBAlloc_ = VK_NULL_HANDLE; uint32_t npcMarkerVertCount_ = 0;