diff --git a/tools/editor/editor_app.cpp b/tools/editor/editor_app.cpp index af1208b1..0327e3fc 100644 --- a/tools/editor/editor_app.cpp +++ b/tools/editor/editor_app.cpp @@ -1830,6 +1830,30 @@ void EditorApp::snapAllSpawnsToGround() { std::to_string(snappedO) + " object(s) to ground"); } +int EditorApp::auditSpawnsAgainstTerrain(float threshold) const { + if (!terrain_.isLoaded()) return 0; + auto castDown = [&](const glm::vec3& pos, glm::vec3& hit) { + rendering::Ray ray; + ray.origin = pos + glm::vec3(0, 0, 500); + ray.direction = glm::vec3(0, 0, -1); + return const_cast(terrainEditor_).raycastTerrain(ray, hit); + }; + int issues = 0; + for (const auto& s : npcSpawner_.getSpawns()) { + glm::vec3 hit; + if (castDown(s.position, hit)) { + if (std::fabs(s.position.z - hit.z) > threshold) issues++; + } + } + for (const auto& o : objectPlacer_.getObjects()) { + glm::vec3 hit; + if (castDown(o.position, hit)) { + if (std::fabs(o.position.z - hit.z) > threshold) issues++; + } + } + return issues; +} + void EditorApp::clearAllObjects() { vkDeviceWaitIdle(window_->getVkContext()->getDevice()); objectPlacer_.clearAll(); diff --git a/tools/editor/editor_app.hpp b/tools/editor/editor_app.hpp index fc12a82b..adcd0cfe 100644 --- a/tools/editor/editor_app.hpp +++ b/tools/editor/editor_app.hpp @@ -106,6 +106,11 @@ public: // mirror of the --snap-zone-to-ground CLI; useful after terrain // edits or random population to fix floating/buried spawns. void snapAllSpawnsToGround(); + // Count spawns whose Z is more than `threshold` yards off from + // the terrain. Returns the issue count; 0 means clean. Used by + // the in-editor "Audit Spawns" menu to surface placement bugs + // without dropping to CLI. + int auditSpawnsAgainstTerrain(float threshold = 5.0f) const; void centerOnTerrain(); // Multi-tile support diff --git a/tools/editor/editor_ui.cpp b/tools/editor/editor_ui.cpp index 112c09b5..0ead3ecb 100644 --- a/tools/editor/editor_ui.cpp +++ b/tools/editor/editor_ui.cpp @@ -403,6 +403,19 @@ void EditorUI::renderMenuBar(EditorApp& app) { ImGui::SetTooltip( "Re-snap every creature + object's Z to actual terrain height.\n" "Run after terrain edits to fix floating/buried spawns."); + if (ImGui::MenuItem("Audit Spawns Against Terrain", nullptr, false, + app.hasTerrainLoaded())) { + int issues = app.auditSpawnsAgainstTerrain(5.0f); + if (issues == 0) + app.showToast("Audit clean — every spawn within 5y of terrain"); + else + app.showToast(std::to_string(issues) + + " spawn(s) more than 5y off terrain — try Snap All"); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip( + "Count spawns whose Z is more than 5y off terrain.\n" + "Surfaces placement bugs without modifying anything."); if (ImGui::MenuItem("Clear All Objects/NPCs", nullptr, false, app.hasTerrainLoaded())) { if (app.getObjectPlacer().objectCount() > 0 || app.getNpcSpawner().spawnCount() > 0) app.clearAllObjects();