Mirrors the WOM tryLoadByGamePath API: probes custom_zones/buildings/ +
output/buildings/ by default, with optional extraPrefixes (e.g. per-zone
output/<map>/buildings/) checked first. Both the editor and the main
game's terrain_manager now use the helper, removing duplicate inline
lookup loops in two more places.
The shared helper only probed custom_zones/models/ + output/models/, but
the editor's exportZone writes to output/<map>/models/. Added an
extraPrefixes parameter that's tried before the defaults — main game's
terrain_manager now passes ['output/<map>/models/', 'custom_zones/<map>/
models/'] so per-zone WOM exports override globals. Also removes the
last duplicate WOM-loading code from terrain_manager.
Mirrors the editor's WMO scale fix. WMOReady gains a scale field that
is computed from the loaded MODF placement.scale (u16 / 1024) and
forwarded to wmoRenderer->createInstance(). Without this the main
game ignored MODF scale even on WotLK ADTs that use it.
The editor's rebuildObjects path was destroying every cached model and
re-uploading it on every (debounced) change. Added M2Renderer::clearInstances
that drops only the instance list while keeping models loaded. Editor's
clearObjects switches to clearInstances (M2) + clearInstances (WMO),
and persistent path->modelId maps survive across rebuilds. clearTerrain
fully evicts when loading a new zone.
terrain_manager.cpp had a 70-line duplicate of the WOM->M2 conversion that
ignored WOM3 multi-batch support. Replaced with a single toM2() call.
Also extended toM2 to copy bones and animation sequence headers so the
shared helper now produces a fully renderable M2Model from any WOM
version, with main game and editor both using the same code path.
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.
- TerrainManager loads WOC collision meshes alongside WOT/WHM terrain
from both custom_zones/ and output/ directories
- CollisionData stored per-tile with triangle array + bounds
- isPositionWalkable(x, y): returns whether a world position is on
walkable terrain (barycentric point-in-triangle test)
- getCollisionFlags(x, y): returns per-triangle flags (walkable,
water, steep, indoor) for movement system integration
- Defaults to walkable when no collision data is loaded (backward compat)
- Custom zone players now have proper terrain physics boundaries
- WOM→M2 conversion now creates proper render batch (indexStart,
indexCount, vertexStart, vertexCount), texture references, material
with opaque blend mode — WOM models are now actually visible in the
client renderer instead of being invisible geometry-only shells
- 5 new JSON DBC test cases: basic load with strings/ints, float values,
empty records rejection, missing records key rejection, findRecordById
lookup across 3 records
- Total: 278 assertions across 80 test cases, all passing
- Wire WOB buildings into WMO render pipeline (loads→converts→renders)
- Implement JSON DBC loading in DBCFile::loadJSON() with nlohmann/json
- Wire JSON DBC override into AssetManager (custom_zones/output scan)
- Add WMO→WOB conversion with full geometry (fromWMO)
- Replace placeholder WOB export with real WMO→WOB conversion in editor
- Add --convert-wmo CLI flag for batch WMO→WOB conversion
- Store discovered custom zones on Renderer with getCustomZones() accessor
- Add isCustomZone_ member to TerrainManager
All 6 Blizzard format replacements now fully load in the client:
ADT→WOT/WHM, WDT→zone.json, BLP→PNG, DBC→JSON, M2→WOM, WMO→WOB
- WoweeBuildingLoader::toWMOModel() converts WOB groups to WMOModel
with vertices, indices, normals, texCoords, and vertex colors
- TerrainManager now loads WOB files from custom_zones/buildings/
and converts to WMOModel for the WMO renderer pipeline
- WMOGroup indices converted from uint32 to uint16 for renderer compat
Client open format support — 4 of 6 now loading:
- FULL: WOT/WHM terrain, PNG textures, WOM models
- LOAD: WOB buildings (converts to WMOModel, render pipeline TODO)
- DETECT: zone.json (scanned), JSON DBC (scanned)
- Renderer::initializeRenderers() now scans custom_zones/ and output/
for zone.json manifests on first initialization
- Logs all discovered custom zones with name, directory, and
NPC/quest availability indicators
- One-time scan (cached after first run)
- Foundation for custom zone selection menu in client UI
Client open format support:
- FULL: WOT/WHM terrain, PNG textures, WOM models (load + render)
- DETECT: zone.json (scanned at startup), WOB buildings, JSON DBC
- TerrainManager now checks for .wob files before loading WMO buildings
(searches custom_zones/buildings/ and output/MapName/buildings/)
- AssetManager::loadDBC() scans custom_zones/*/data/ for JSON DBC
overrides exported by the editor
- WOB detection logs when found (full WOB→WMOModel conversion pending)
- JSON DBC detection logs when found (full JSON→DBCFile loading pending)
Client open format support status:
- WOT/WHM terrain: FULL (loads and renders)
- PNG textures: FULL (override system)
- WOM models: FULL (loads and renders)
- zone.json: DETECTION (CustomZoneDiscovery scans)
- WOB buildings: DETECTION (found, conversion pending)
- JSON DBC: DETECTION (found, loading pending)
- TerrainManager now checks for .wom files before loading M2 models
- Searches custom_zones/models/ and output/MapName/models/ directories
- Converts WOM vertices/indices to M2Model struct for the renderer
- Full pipeline: editor exports M2→WOM → client loads WOM directly
- Falls back to standard M2 loading if no WOM found
The client can now render custom zone content using entirely
open formats: WOT/WHM terrain + WOM models + PNG textures
The wowee client can now load custom zones exported from the editor
using the novel WOT/WHM format — no Blizzard files needed.
Loading priority in TerrainManager::prepareTile():
1. Check custom_zones/{mapName}/{mapName}_{x}_{y}.wot/.whm
2. Check output/{mapName}/{mapName}_{x}_{y}.wot/.whm (editor output)
3. Fall back to World\Maps\...\*.adt (standard extracted data)
Pipeline:
- WoweeTerrainLoader in src/pipeline/ (shared between client + editor)
- Loads .whm binary heightmap (WHM1 magic, 256 chunks × 145 floats)
- Loads .wot JSON metadata (textures, layers, holes, water)
- Populates the same ADTTerrain struct the mesh generator uses
- obj0 merge only runs for ADT-loaded tiles (custom zones have no obj0)
To use: export zone from editor → files appear in output/ → client
loads them automatically on next terrain request for that map name.
Standalone wowee_editor tool for creating custom WoW zones.
This is a rough initial implementation — many features work but
M2/WMO rendering still has issues (frame sync, texture layout
transitions) and needs further polish.
Terrain:
- Create new blank terrain with 10 biome types (Grassland, Forest,
Jungle, Desert, Barrens, Snow, Swamp, Rocky, Beach, Volcanic)
- Load existing ADT tiles from extracted game data
- Sculpt brushes: Raise, Lower, Smooth, Flatten, Level
- Chunk edge stitching prevents seams between tiles
- Undo/redo (100-deep stack, Ctrl+Z/Ctrl+Shift+Z)
- Save to WoW ADT/WDT format
Texture Painting:
- Paint/Erase/Replace Base modes
- Full tileset texture browser (1285 textures from manifest)
- Per-zone directory filtering and search
- Alpha map editing with 4-layer limit (auto-replaces weakest)
Object Placement:
- M2 and WMO model placement with full manifest browser (11k M2s, 2k WMOs)
- M2Renderer + WMORenderer integrated (loads .skin files for WotLK)
- Ghost preview follows cursor before placing
- Ctrl+click selection, right-click context menu
- Transform gizmo (Move/Rotate/Scale with axis constraints)
- Position/rotation/scale editing in properties panel
NPC/Monster System:
- 631 creature presets scanned from manifest, categorized
(Critters, Beasts, Humanoids, Undead, Demons, etc.)
- Stats editor: level, health, mana, damage, armor, faction
- Behavior: Stationary, Patrol, Wander, Scripted
- Aggro/leash radius, respawn time, flags (hostile/vendor/etc.)
- Save creature spawns to JSON
Water:
- Place water at configurable height per chunk
- Liquid types: Water, Ocean, Magma, Slime
- Rendered as translucent colored quads
- Saved in ADT MH2O format
Infrastructure:
- Free-fly camera (WASD/QE, right-drag look, scroll speed)
- 5-mode toolbar: Sculpt | Paint | Objects | Water | NPCs
- Asset browser indexes full manifest on startup
- Editor water/marker shaders (pos+color vertex format)
- forceNoCull added to M2Renderer for editor use
- AssetManifest::getEntries() and AssetManager::getManifest() exposed
Known issues:
- M2/WMO rendering may not display on first placement (frame index
sync between update/render was misaligned — now fixed but untested
end-to-end)
- Validation layer errors on shutdown (resource cleanup ordering)
- Object placement on steep terrain can miss raycast
- No undo for texture painting or object placement yet
Re-allocate megaBoneSet_[0..1] in M2Renderer::clear() after vkResetDescriptorPool invalidates all
sets from boneDescPool_. Stale handles were bound to command buffers during rendering, causing
cascading validation errors. Also add ImGui::Dummy() after SetCursorScreenPos in the shaman totem
bar to satisfy ImGui's window boundary extension assertion.
- Remove stale kVOffset (-0.15) from zone_highlight_layer hover detection;
the offset was removed from rendering but left in the hit-test path,
shifting hover ~15% vertically
- Add null guard for cachedGameHandler_ in ChatPanel::inputTextCallback
to prevent dereference before first render frame
- Zero WindowBorderSize in world map ImGui window to eliminate gap
between window edge and map content
- Replace hardcoded cosmic highlight multipliers with displayH×displayH
square rendering, preserving 1:1 aspect ratio at any resolution
- Skip transport waypoints where serverToCanonical zeroes nonzero input
instead of silently building paths with broken (0,0,0) coordinates
- Use length-squared check (posLenSq > 1.0) for spline endpoint
validation instead of per-component != 0 comparison, so entities
near the world origin are no longer skipped
- Fix off-by-one in ChatPanel::insertChatLink buffer capacity check
- Remove the -0.15 vertical offset (kVOffset) from coordinate_projection,
coordinate_display, and zone_highlight_layer; continent UV math is now
identical to zone UV math
- Switch world_map_facade aspect ratio to MAP_W/MAP_H (1002×668) and crop
the FBO image with MAP_U_MAX/MAP_V_MAX instead of stretching the full
1024×768 FBO
- Account for ImGui title bar height (GetFrameHeight) in window sizing and
zone highlight screen-space rect coordinates
- Add ZMP 128×128 grid pixel-accurate hover detection in zone_highlight_layer;
falls back to AABB when ZMP data is unavailable
- Upgrade PlayerMarkerLayer with full Vulkan lifecycle (initialize,
clearTexture, destructor); loads MinimapArrow.blp and renders a rotated
32×32 textured quad via AddImageQuad; red triangle retained as fallback
- Expose arrowRotation_ / arrowDS_ accessors on Minimap; clean up arrow DS
and texture in Minimap::shutdown()
- Wire PlayerMarkerLayer::initialize() into WorldMapFacade::initialize()
- Update coordinate-projection test: continent and zone UV are now equal
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Break the monolithic 1360-line world_map.cpp into 16 focused modules
under src/rendering/world_map/:
Architecture:
- world_map_facade: public API composing all components (PIMPL)
- world_map_types: Vulkan-free domain types (Zone, ViewLevel, etc.)
- data_repository: DBC zone loading, ZMP pixel map, POI/overlay storage
- coordinate_projection: UV projection, zone/continent lookups
- composite_renderer: Vulkan tile pipeline + off-screen compositing
- exploration_state: server mask + local exploration tracking
- view_state_machine: COSMIC→WORLD→CONTINENT→ZONE navigation
- input_handler: keyboard/mouse input → InputAction mapping
- overlay_renderer: layer-based ImGui overlay system (OCP)
- map_resolver: cross-map navigation (Outland, Northrend, etc.)
- zone_metadata: level ranges and faction data
Overlay layers (each an IOverlayLayer):
- player_marker, party_dot, taxi_node, poi_marker, quest_poi,
corpse_marker, zone_highlight, coordinate_display, subzone_tooltip
Fixes:
- Player marker no longer bleeds across continents (only shown when
player is in a zone belonging to the displayed continent)
- Zone hover uses DBC-projected AABB rectangles (restored from
original working behavior)
- Exploration overlay rendering for zone view subzones
Tests:
- 6 new test files covering coordinate projection, exploration state,
map resolver, view state machine, zone metadata, and integration
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
- Mark swapchain dirty in Application's SDL resize handler (was only done
in Window::pollEvents which is never called)
- Skip swapchain recreation when window is minimized (0×0 extent violates
Vulkan spec and crashes vmaCreateImage)
- Guard aspect ratio division by zero when height is 0
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Add complete spell visual pipeline resolving the DBC chain
(Spell → SpellVisual → SpellVisualKit → SpellVisualEffectName → M2)
with precast/cast/impact phases, bone-attached positioning, and
automatic dual-hand mirroring.
Ribbon rendering fixes:
- Parse visibility track as uint8 (was read as float, suppressing
all ribbon edges due to ~1.4e-45 failing the >0.5 check)
- Filter garbage emitters with bone=UINT_MAX unconditionally
- Guard against NaN spine positions from corrupt bone data
- Resolve ribbon textures via direct index, not textureLookup table
- Fall back to bone 0 when ribbon bone index is out of range
Particle rendering fixes:
- Reduce spell particle scale from 5x to 1.5x (was oversized)
- Exempt spell effect instances from position-based deduplication
Spell handler integration:
- Trigger precast visuals on SMSG_SPELL_START with server castTimeMs
- Trigger cast/impact visuals on SMSG_SPELL_GO
- Cancel precast visuals on cast interrupt/failure/movement
M2 classifier expansion:
- Add AmbientEmitterType enum for sound system integration
- Add 20+ foliage tokens, 4 spell effect tokens, isSmallFoliage flag
- Add markModelAsSpellEffect() to override disableAnimation
DBC layouts:
- Add SpellVisualID field to Spell.dbc for all expansion configs
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
HiZ occlusion culling built ~11 mip levels with per-level barriers
behind a blocking vkWaitForFences every frame — the main frame-rate
bottleneck. Disable the pyramid build and fall back to GPU frustum-
only culling which is nearly free behind the fence.
WMO interiors now receive full-strength directional shadows but clamp
minimum brightness at 0.45 with a 0.35 ambient floor, so interiors
get real shadow contrast without going too dark.
All M2 pipelines used VK_CULL_MODE_NONE, so back-facing polygons always
rendered. On NPCs whose torso meshes are single-layer geometry this
made the interior cavity visible through the back.
Create backface-culled pipeline variants (VK_CULL_MODE_BACK_BIT) and
select them at draw time unless the material has the TwoSided flag
(0x04). Foliage/ground-detail forceCutout batches and the shadow
pipeline keep VK_CULL_MODE_NONE since those cards are inherently
two-sided.
M2Renderer::clear() reset descriptor pools but left the texture cache
and failed-texture tracking intact. On re-login the stale cache filled
the budget, failedTextureRetryAt_ blocked reloads, and the client
entered an infinite model-load loop. Match the cleanup already done in
shutdown() and CharacterRenderer::clear().
Implement GPU-driven Hierarchical-Z occlusion culling for M2 doodads
using a depth pyramid built from the previous frame's depth buffer.
The cull shader projects bounding spheres via prevViewProj (temporal
reprojection) and samples the HiZ pyramid to reject hidden objects
before the main render pass.
Key implementation details:
- Separate early compute submission (beginSingleTimeCommands + fence
wait) eliminates 2-frame visibility staleness
- Conservative safeguards prevent false culls: screen-edge guard,
full VP row-vector AABB projection (Cauchy-Schwarz), 50% sphere
inflation, depth bias, mip+1, min screen size threshold, camera
motion dampening (auto-disable on fast rotations), and per-instance
previouslyVisible flag tracking
- Graceful fallback to frustum-only culling if HiZ init fails
Fix dark WMO interiors by gating shadow map sampling on isInterior==0
in the WMO fragment shader. Interior groups (flag 0x2000) now rely
solely on pre-baked MOCV vertex-color lighting + MOHD ambient color.
Disable interiorDarken globally (was incorrectly darkening outdoor M2s
when camera was inside a WMO). Use isInsideInteriorWMO() instead of
isInsideWMO() for correct indoor detection.
New files:
- hiz_system.hpp/cpp: pyramid image management, compute pipeline,
descriptors, mip-chain build dispatch, resize handling
- hiz_build.comp.glsl: MAX-depth 2x2 reduction compute shader
- m2_cull_hiz.comp.glsl: frustum + HiZ occlusion cull compute shader
- test_indoor_shadows.cpp: 14 unit tests for shadow/interior contracts
Modified:
- CullUniformsGPU expanded 128->272 bytes (HiZ params, viewProj,
prevViewProj)
- Depth buffer images gain VK_IMAGE_USAGE_SAMPLED_BIT for HiZ reads
- wmo.frag.glsl: interior branch before unlit, shadow skip for 0x2000
- Render graph: hiz_build + compute_cull disabled (run in early compute)
- .gitignore: ignore compiled .spv binaries
- MEGA_BONE_MAX_INSTANCES: 2048 -> 4096
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
Demote 44 more LOG_WARNING messages to LOG_DEBUG: warden module chunk
progress, entire shutdown/teardown sequence, transport manager connect,
inventory right-click slot, and warden handshake diagnostics. Keeps real
warnings (texture not found, slow handlers, stalls, integrity hash)
visible in the log.
When spline parsing consumes the wrong number of bytes, the subsequent
blockCount read lands on garbage data (e.g. 71 instead of ~5 for UNIT).
Previously the parser logged a warning but continued, reading garbage
mask/field data until hitting truncation. Now it returns false for
CREATE_OBJECT blocks with suspicious counts, letting the block loop
skip cleanly to the next entity.
Also downgrade ~44 diagnostic LOG_WARNING messages to LOG_DEBUG across
17 files (equipment, transport, DBC, heartbeat, chat, GO raypick, etc.)
to reduce log noise and make real warnings visible.
Resolve conflicts:
- audio_callback_handler.cpp: keep PR's animation_controller include
- movement_handler.cpp: use PR accessors with master's transportResolved logic
- world_packets.cpp: keep PR's decomposed version (functions moved to split files)
Apply overkill field fix to world_packets_entity.cpp (WotLK
SMSG_ATTACKERSTATEUPDATE missing uint32 overkill between damage and
subDamageCount).
If the capability probe ran before the model was fully loaded, all melee
animation IDs would be 0 and auto-attack swings would silently fall back
to STAND (no visible animation). Now re-probes when a melee swing fires
but hasMelee is false.
Added WARNING-level logging to triggerMeleeSwing and CombatFSM to
diagnose the night elf stationary combat animation issue.
Emote animations: fix DBC chain for /laugh, /flirt, /sleep, /fart, /stink.
Previously all emotes with AnimID=0 used emoteRef as animId (wrong DBC
record IDs). Now resolves through Emotes.dbc properly, with per-emote
overrides for emotes whose DBC chain yields 0. Adds Emotes.dbc load
failure warning and diagnostic logging.
WMO culling: skip portal culling when camera is outside all groups (fixes
vanishing Stormwind ground tiles). Also handle indoor/outdoor AABB overlap
by showing all groups when position is in both indoor and outdoor AABBs.
Transport: clear ONTRANSPORT flag and transport state when transport not
found, preventing stale transport data from teleporting player to map
origin. Add area trigger safety net near (0,0,0) on Eastern Kingdoms.
MAX_CULL_INSTANCES was 16384 but game object instances were allocated
at indices 20000+, beyond the cull buffer. Increased to 65536.
Also compute fallback boundRadius from vertex extents when M2 header
reports 0, and seed bones for global-sequence-only animated models.