The scatter tool spawned creatures all at the cursor's z-height which
made them hover when scattered over uneven terrain. Added a Snap to
Ground checkbox (defaults to on) that raycasts each scattered NPC and
places it on the actual surface.
Without bounds checks, a corrupted WOM3 file with invalid indexStart/
indexCount/textureIndex would feed bad ranges to the M2 renderer and
crash on draw. Now each batch is validated against the loaded indices
and texturePaths arrays; out-of-range batches are warned and dropped.
Selected NPCs now display the move/rotate/scale gizmo and respond to
drag operations. Rotate uses only the yaw axis (NPCs have a single
orientation field, not full euler rotation). Move/scale work the same
way as objects.
Right-click context menu on a selected NPC now mirrors the object menu:
Snap to Ground drops it to terrain elevation, Face Camera rotates the NPC
to face the current camera position. Also annotates Duplicate with the
Ctrl+D shortcut hint.
The editor's orientation field is stored in degrees (matches the UI slider
and the M2 renderer's glm::radians() call), but AzerothCore's creature.
orientation column expects radians. Without conversion every exported NPC
faces the wrong direction in-game (off by 57x).
The patrol list now shows total loop distance and gives each waypoint a
draggable wait-time field (0-60s). Helps tune patrol pacing without re-saving
the JSON manually.
Once an NPC is placed there was no way to resize it without removing and
re-placing. Added a Scale DragFloat in the selected-creature editor with
the same 0.1-50 range used for placed objects.
WoweeBuildingLoader::fromWMO previously left the portals array empty,
losing portal/PVS data essential for indoor visibility culling. Now reads
the WMO portal vertex polygons and pairs them with their two adjacent
groups via MOPR refs, populating WoweeBuilding::Portal fully.
Plain left-click within 4u of an existing NPC now selects that NPC rather
than dropping a new spawn on top. Shift+click forces placement at the cursor
even if it overlaps an existing NPC, preserving the rapid-placement workflow.
When an NPC with Patrol behavior is selected in NPC mode, pressing W
appends a waypoint at the current cursor position. Faster than clicking
the panel button for laying out long routes.
TextureExporter::collectUsedTextures only picked up terrain textures, so
exported zones were missing every texture referenced by NPC creature models
and placed M2 doodads. Added collectM2Textures() and unified the export
collection to include terrain + all referenced M2 paths, so the rendered
zone is fully self-contained in the PNG/WOM open formats.
Previously only placed M2 objects were converted to the WOM open format.
NPC creature models stayed as references to game M2/skin files, which
meant exported zones still depended on proprietary Blizzard assets to
render their NPCs. Now the exporter walks both placed objects and NPC
spawns and emits a WOM for every unique M2 path, making zones fully
self-contained.
NPC markers now show a yellow ground-triangle pointing in the orientation
direction so users can see facing without selecting. Object panel gained
the Ctrl+Wheel rotation hint to match the NPC panel.
WOM1/WOM2 had a single mesh with one texture, which lost the multi-submesh
structure of complex M2 models (body+hair+eyes+armor each need different
textures and blend modes).
WOM3 adds a Batch array: each batch has indexStart/indexCount + a textureIndex
into texturePaths + blendMode + flags. Loader is fully backward compatible:
WOM1/WOM2 files still load, and WOM3 with no batches block falls back to a
single full-mesh batch. fromM2 now extracts batches with materials, and toM2
emits matching M2 batches so the renderer can draw them correctly.
Adds WoweeModelLoader::toM2() and tryLoadByGamePath() to deduplicate the
identical conversion code that lived in editor_viewport for both objects
and NPCs. Cuts ~70 lines of duplicated logic and makes WOM->M2 reusable
across the codebase.
Adds setPatrolPath() that draws a multi-segment orange ribbon between the
NPC's spawn position and each waypoint, plus diamond markers at each point
(green for start = NPC home, white for waypoints). Renders only while the
NPC is selected and has a patrol path defined.
Added orientation slider in NPC panel with random button. Ctrl+Wheel now
rotates the placement preview (objects and NPCs) instead of zooming —
Shift makes the step finer (5 deg vs 15 deg). Ghost preview now shows
the actual orientation that the placed NPC will have.
setGhostPreview reused modelId 59999 for every preview, but loadModel
returns true without doing anything when the ID is already cached. So
selecting a new NPC kept the old ghost model in GPU memory and createInstance
used the stale model. Added M2Renderer::unloadModel public API and call it
from clearGhostPreview.
Without prepareRender(), animated NPC creature instances had megaBoneOffset=0
which caused the render loop to skip them (filtered out at the bones check).
This is why all loaded NPC M2s produced 0 draw calls despite valid GPU buffers,
material sets, and instance creation. Matches the main game's render flow.
ROOT CAUSE: createInstance() checks mdlRef.isValid() which requires
vertexBuffer != VK_NULL_HANDLE. But vertex buffers are uploaded via
staging and only finalized by waitAllUploads(). Instances were being
created BEFORE the upload flush, so vertexBuffer was still null,
cachedIsValid was set to false, and all instances were skipped during
render (0 draws despite loaded models).
Fix: split rebuildObjects into two phases:
1. Load all models (upload geometry to staging)
2. waitAllUploads + pollUploadBatches (finalize GPU buffers)
3. Create all instances (vertexBuffer is now valid, isValid() = true)
This matches the client's terrain_manager pattern where models are
loaded on background threads and instances created after finalization.
Log showed: models=2, instances=3, draws=0 — models loaded and
instances created but zero draw calls. The M2 renderer skips
instances where cachedIsValid is false, which depends on the GPU
vertex buffer being valid after upload.
The per-model waitAllUploads/pollUploadBatches calls inside the
loading loop may have corrupted the upload batch context (started
at beginUploadBatch but flushed mid-loop). Now all models upload
in a single batch with one final waitAllUploads+pollUploadBatches
at the end of rebuildObjects.
ROOT CAUSE of NPC models not rendering: every NPC placement triggered
an immediate full clear+rebuild of ALL M2 models. During rapid clicking,
this created a destroy-reload cycle where models were cleared faster
than they could render — the log showed rebuild firing every ~200ms
with models loading OK but being destroyed before the next frame.
Fix: debounce rebuilds with a 0.5s timer. Multiple rapid placements
reset the timer, so the rebuild only fires once after the user stops
clicking. Models stay loaded and visible between placements.
Before: click → clear all → reload all → click → clear all → reload...
After: click → click → click → (0.5s pause) → single rebuild
ROOT CAUSE of NPCs not rendering: rebuildObjects() called createInstance()
BEFORE beginFrame(), causing instance SSBO writes to use the wrong frame
index. The M2 renderer writes instance transforms to a per-frame buffer
indexed by getCurrentFrame(), but the frame index isn't valid until after
beginFrame() returns.
This is the same bug documented in the project memory:
"M2 models not rendering (draws=0): update() was called before
beginFrame(), causing frame index mismatch in instance SSBO"
Models loaded correctly (log confirmed 2192v 7926i 8b) but instances
were invisible because their transform data was written to the wrong
frame buffer.
Fix: move the rebuild block after beginFrame(), alongside update().
Release builds set default log level to WARNING — all the NPC loading
diagnostic messages were at LOG_INFO/LOG_DEBUG and completely invisible.
This is why the log showed zero NPC messages despite NPCs being placed.
All NPC loading messages now use LOG_WARNING:
- "NPC rebuild: N creatures to load" — confirms rebuild loop runs
- "NPC M2 OK: path (Nv Ni Nb)" — model loaded successfully
- "NPC loaded from WOM: path" — WOM format used
- "NPC model file not found: path" — file missing
- "NPC model invalid: path" — parse failed
- "NPC M2 loadModel failed: path" — GPU upload failed
- "NPC has empty modelPath: name" — no model selected
NPC model loading diagnostics were at LOG_DEBUG level which doesn't
appear in the default log output. Changed all NPC model loading
messages to LOG_WARNING/LOG_INFO:
- "NPC model file not found" now WARNING (was DEBUG, invisible)
- "NPC has empty modelPath" new WARNING for missing model selection
- "Loading N NPC models..." at loop entry to confirm rebuild runs
- "NPC M2 loaded" at INFO level shows successful loads
This will reveal exactly where the NPC rendering pipeline fails.
Changed NPC model invalid and load success messages from LOG_DEBUG to
LOG_WARNING/LOG_INFO so they appear in the log output. This helps
diagnose why specific creature models fail to render — the log will
show vertex/index/batch counts for each load attempt.
Many WoW instances (Dire Maul, Blackrock Depths, etc.) are WMO-only
maps with no ADT terrain tiles. The editor now handles these:
- loadWMOInstance(): reads WDT, detects WDTF_GLOBAL_WMO flag, loads
the root WMO path from MWMO chunk, places it as an object with
correct position/rotation from MODF chunk
- Automatic fallback: when loadADT fails to find tiles, tries
WMO-only loading before showing error
- Load dialog: "Find Tile" detects WMO-only instances and shows
"WMO-only instance — click Load to open" instead of "not found"
- Camera positioned near the WMO for immediate editing
- Blank terrain floor generated as ground reference
rebuildObjects() calls clearObjects() which clears the M2 renderer,
but didn't start a new upload batch before loading models. The M2
renderer needs an active upload batch context to upload vertex/index
buffers to the GPU. Without it, loadModel may silently fail.
Now calls vkCtx_->beginUploadBatch() after clear and before the
model loading loop. Also adds diagnostic logging when loadModel
fails for NPCs (shows vertex/index/batch counts).
Applied same two fixes from NPC renderer to placed object renderer:
1. Check WOM open format before M2 fallback (custom_zones/output dirs)
2. Always load skin file regardless of initial isValid state
Both placed objects (M2 doodads from ADT import or manual placement)
and NPC creatures now have consistent WOM→M2 fallback pipeline with
proper skin file loading.
NPC creature renderer now checks for WOM open format models in
custom_zones/models/ and output/models/ before falling back to M2
game data files. This completes the WOM integration — both placed
objects (via terrain_manager) and NPC creatures (via editor viewport)
can render from the novel open format.
WOM→M2Model conversion includes bone weights, textures, render batch,
and material setup — same pipeline as the client-side WOM loader.
Loading priority: WOM (open format) → M2 + skin (game data)
WotLK M2 models store geometry in separate .skin files. The NPC
renderer was only loading skin files when M2Loader::load() returned
invalid (empty vertices). But some M2 files have vertices in the
header yet need the skin file for indices, batches, and submeshes.
Now always attempts to load the skin file regardless of initial
isValid() state. This fixes creature models not rendering even
when the M2 and skin files exist on disk.
Also improved debug logging to show vertex/index counts when models
fail to load, making it easier to diagnose remaining issues.
"Count" button next to "Find Tile" scans all 64x64 tile coordinates
and shows how many ADT tiles exist for the selected map. Helps users
understand the scope of a map before loading individual tiles.
Two bugs fixed:
- Loading a new ADT tile now clears all previous objects, NPCs, quests,
path state, and terrain before loading. Was accumulating old state
across multiple loads
- ADT doodad/WMO rotation conversion now matches client's transform:
renderRotX = -adtRotZ, renderRotY = -adtRotX, renderRotZ = adtRotY+180
Was copying raw ADT rotations without coordinate system conversion,
causing models to appear at wrong orientations
- NPC markers reduced from 30-unit poles to 8-unit poles (was
overwhelming and obscuring terrain). Base 1.5u, diamond top 1u
- "Show Position Markers" checkbox in NPC panel to toggle visibility
- Markers hidden when checkbox unchecked — useful when M2 creature
models are rendering and markers are redundant
- Marker alpha reduced for less visual noise
When loading ADT tiles, the editor overrides terrain_.coord with the
filename-derived tile coordinates (instanced maps have arbitrary
internal values). But it wasn't recomputing the per-chunk world
positions to match, causing terrain to render at wrong coordinates.
Now recalculates all 256 chunk positions from the corrected tile
coordinates using the standard WoW formula:
chunkX = (32 - tileX) * 533.33 - cx * 33.33
chunkY = (32 - tileY) * 533.33 - cy * 33.33
This fixes terrain appearing at the wrong location in the editor
when loading instanced maps or tiles with mismatched internal coords.
NPC position markers were silently not rendering when no object was
selected because they relied on the gizmo's pipeline being bound, but
the gizmo only binds its pipeline when active. Now explicitly binds
the water pipeline (same pos+color vertex format with alpha blend)
before drawing NPC markers, ensuring they always appear regardless of
gizmo state.
Note: M2 creature models still require extracted game data files to
render. When model files aren't found, the colored markers (poles with
diamond tops) provide reliable visual feedback for NPC positions.