feat(editor): in-editor "Snap All Spawns to Ground" menu

Adds EditorApp::snapAllSpawnsToGround() and a menu item under
Generate, mirroring the --snap-zone-to-ground CLI for users who
want the action without context-switching to a terminal.

Walks every NPC + object, casts a downward ray from baseZ+500y,
and writes the hit Z into spawn.position.z. Patrol waypoints on
each NPC are snapped too. Marks the project dirty + auto-save
pending so the change persists without a manual Ctrl+S.

Existing per-selection "Snap Ground" button on the right panel is
unchanged; this is the bulk version for "I just edited terrain
under a populated area."
This commit is contained in:
Kelsi 2026-05-07 14:12:39 -07:00
parent f589fa20ce
commit f870a516dd
3 changed files with 50 additions and 0 deletions

View file

@ -1792,6 +1792,44 @@ void EditorApp::randomPopulateZone(int creatureCount, int objectCount,
" creatures + " + std::to_string(placedObjects) + " objects");
}
void EditorApp::snapAllSpawnsToGround() {
if (!terrain_.isLoaded()) {
showToast("Load a tile first");
return;
}
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 terrainEditor_.raycastTerrain(ray, hit);
};
int snappedC = 0, snappedO = 0;
for (auto& s : npcSpawner_.getSpawns()) {
glm::vec3 hit;
if (castDown(s.position, hit)) {
s.position.z = hit.z;
snappedC++;
}
for (auto& wp : s.patrolPath) {
if (castDown(wp.position, hit)) wp.position.z = hit.z;
}
}
for (auto& o : objectPlacer_.getObjects()) {
glm::vec3 hit;
if (castDown(o.position, hit)) {
o.position.z = hit.z;
snappedO++;
}
}
if (snappedC > 0 || snappedO > 0) {
objectsDirty_ = true;
autoSavePendingChanges_ = true;
viewport_.updateNpcMarkers(npcSpawner_.getSpawns());
}
showToast("Snapped " + std::to_string(snappedC) + " creature(s) + " +
std::to_string(snappedO) + " object(s) to ground");
}
void EditorApp::clearAllObjects() {
vkDeviceWaitIdle(window_->getVkContext()->getDevice());
objectPlacer_.clearAll();

View file

@ -102,6 +102,10 @@ public:
// tile's world bbox. Mirrors the --random-populate-zone CLI
// command so users can do bulk population from inside the editor.
void randomPopulateZone(int creatureCount, int objectCount, uint32_t seed);
// Re-snap every creature + object Z to actual terrain. In-editor
// mirror of the --snap-zone-to-ground CLI; useful after terrain
// edits or random population to fix floating/buried spawns.
void snapAllSpawnsToGround();
void centerOnTerrain();
// Multi-tile support

View file

@ -395,6 +395,14 @@ void EditorUI::renderMenuBar(EditorApp& app) {
"Same seed → same population (reproducible)");
ImGui::EndMenu();
}
if (ImGui::MenuItem("Snap All Spawns to Ground", nullptr, false,
app.hasTerrainLoaded())) {
app.snapAllSpawnsToGround();
}
if (ImGui::IsItemHovered())
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("Clear All Objects/NPCs", nullptr, false, app.hasTerrainLoaded())) {
if (app.getObjectPlacer().objectCount() > 0 || app.getNpcSpawner().spawnCount() > 0)
app.clearAllObjects();