Commit Graph

433 Commits

Author SHA1 Message Date
Kelsi
4d9a36dae5 Fix quest marker types on maps 2026-07-12 18:56:34 -07:00
Kelsi
df0efc3063 fix weapon sheathing placement and animation 2026-07-12 17:40:41 -07:00
Kelsi
2858c68e89 fix(character): render ghost opacity through blended pipeline
Route faded character instances through a dedicated translucent character
pipeline so dead and ghost characters fade as a whole model instead of only
showing translucent hair.

Opaque body batches previously wrote the shader alpha from instance opacity,
but the opaque pipeline had blending disabled, so the body stayed fully
solid. Hair went through the alpha-test path, where reduced alpha affected
coverage, making only hair appear faded.

Add a translucent pipeline with alpha blending and depth writes enabled. This
keeps normal self-occlusion for faded characters while allowing the whole
model to respect instance opacity. Reuse the existing per-batch alpha-test
flag so cutout materials such as hair keep crisp edges while fading with the
rest of the body.

Also make the whole-model fallback path bind an explicit opaque or
translucent pipeline instead of inheriting the previously bound pipeline.

Create, recreate, validate, and destroy the new pipeline alongside the other
character pipelines.

Build passes.
2026-07-12 16:12:31 -07:00
Kelsi
4ba6b51b2e feat(camera): ray-march the terrain heightfield so hills can't clip through the orbit camera
Terrain was only a floor clamp at the camera's final XY; a hill between the
pivot and the camera sliced straight through the view. March the heightfield
along the pivot ray (coarse steps + bisection) and feed the limit through the
same asymmetric pull-in/recover smoothing as the WMO raycast, snapping 1:1
while actively rotating.
2026-07-12 16:02:47 -07:00
Kelsi
10ebc18471 fix(render): stop the character alpha heuristics erasing the backdrop's buildings
Stormwind's walls, trim and roofs are DXT5 with an unused alpha channel — mean
alpha 17/255, every texel below the cutoff. The renderer infers an alpha cutout
for an opaque batch whenever its texture "has alpha", a rescue for character
textures, so all of Stormwind was being discarded and the sky showed through the
buildings with speckle where a stray texel cleared the threshold.

Mark backdrops as scene models and take them at their word: only an alpha-key
material (blendMode 1) cuts out. The hair-geoset and skin-composite guesses are
skipped for them too — a scene's submesh ids are all 0, which the hair guess reads
as a hair geoset for every batch.
2026-07-12 13:55:32 -07:00
Kelsi
36d2705c2f fix(ui): stop preview weapons and enchants leaking between characters
Two bugs, one symptom each.

attachWeapons() ran at the end of applyEquipment(), past an early return taken by
any character whose body skin could not be composited. Those characters showed no
weapon at all, and kept the previously selected character's weapon and enchant,
because detaching happens in attachWeapons() too. Weapon attachment depends on
nothing in the geoset/skin work, so do it first and unconditionally.

Weapon and effect models were also loaded under fixed ids. CharacterRenderer's
model cache is keyed by id and skips loading when the id is present, so every
character after the first was handed the first one's weapon model. Key the id by
the asset path instead.

Also drop the renderUnlit plumbing added for the backdrop: preview instances take
the simple-texture shader path, which never reaches the lighting branch it
changed, so it was a no-op.
2026-07-12 13:47:18 -07:00
Kelsi
b17db99790 fix(render): draw the character-select backdrop with its authored colours
The glue scenes carry their lighting baked into their textures, but 21 of the 29
materials in the human scene are flagged lit, so all of Stormwind was being shaded
by the portrait light rig that exists to light a character.

Render backdrops unlit. The unlit path multiplies the texture by (1 + emissiveBoost),
which is what an enchant glow wants but would wash a backdrop out, so backdrops
cancel the boost and show the texture exactly as painted.
2026-07-12 13:37:22 -07:00
Kelsi
ee297a177a fix(ui): place the racial backdrop using the scene's own camera
The glue scenes are authored in their own space — the human one's geometry sits
around x=-230, hundreds of units from its origin — so an instance at the origin
put the whole scene off-camera, and the 130-unit cull radius would have dropped it
anyway. Nothing was visible.

Parse the camera M2s already carry (M2Loader ignored them) and use it: the scene
stays in its authored coordinates, the character stands on its attachment 0 mark
facing the camera, and the preview camera keeps its framing distance but swings
round to the artist's viewing angle. Backdrops are exempt from culling, since
their origin says nothing about where their geometry is.

Also show the weapon's enchant glint in the preview: SMSG_CHAR_ENUM reports an
equipped item's enchant as its ItemVisual id, so it needs no SpellItemEnchantment
lookup — split resolveItemVisualModels() out to enter the chain at that point.
2026-07-12 13:31:12 -07:00
Kelsi
7d36826214 feat(ui): bigger character-select preview with weapons and racial backdrop
The preview was a 400x500 portrait clamped to 320px in a 360px panel, showing an
unarmed character against nothing. Widen the panel, let the image take the height
the details can spare, and raise the render target to 640x800 so the larger
display is not upscaled mush.

Attach the character's weapons to the preview's hand attachment points — the
equipment was already parsed from SMSG_CHAR_ENUM, but only ever applied as
geosets and textures.

Load the race's glue scene (Interface\Glues\Models\UI_<Race>) behind the
character, so humans stand in Stormwind and orcs in Durotar as they do in the
original client.
2026-07-12 09:46:14 -07:00
Kelsi
63f0866ad1 feat(render): show enchant visuals on enchanted weapons
A sharpened blade should glint. Resolve the enchant's visual through
SpellItemEnchantment -> ItemVisuals -> ItemVisualEffects and attach the effect M2
to the weapon model's item-visual attachment points, riding the weapon transform.

Such a model is nothing but the additive FX batches that attached weapons
otherwise drop, so effect instances are exempted from that cull, keep their
animation advanced, and are forced additive and unlit — their materials declare
Mod/alpha blending, which would otherwise composite the glow card's black
background as an opaque quad.

Applying an enchant leaves the displayInfoId untouched, so the equipment-dirty
check now also tracks per-slot enchant ids; the visual appears without re-equipping.
2026-07-12 09:24:15 -07:00
Kelsi
ac02a87ad4 perf(render): skip redundant mega-bone uploads and cache selection circle floor
prepareRender() re-uploaded every animated M2 instance's bone matrices
into the mega bone SSBO every frame — including the majority whose
bones were never recomputed because they are distance, frustum, or
frame-skip culled and keep their previous matrices. The bonesDirty
flag computeBoneMatrices() sets was never consumed. Track the slot
each instance last uploaded to per frame index and memcpy only when
the bones changed or the slot moved, cutting megabytes of redundant
host-visible writes per frame in doodad-dense scenes.

The selection circle also re-ran terrain/WMO/M2 floor raycasts every
frame; reuse the result while the target's position is unchanged,
refreshing every 30 frames so streamed-in geometry still snaps.
2026-07-12 08:30:15 -07:00
Kelsi
1eb25b8da4 perf: reduce per-frame entity and render overhead 2026-07-12 07:26:56 -07:00
Kelsi
a36cc3553c fix(rendering): hide sky birds until close enough for animation
Bird and bat doodads appeared frozen mid-flap in the sky because bone
computation stops beyond 150 units (LOD3 distance) but the model was
still rendered in its bind pose. Now classified as sky birds and their
render distance is capped to the bone-update range, fading in smoothly
as the player approaches.
2026-07-11 11:40:49 -07:00
Kelsi
70e46bfe1a fix(map): use Rotating-MinimapCorpseArrow.blp for corpse marker
Replace the procedural X with the actual WoW corpse icon texture loaded
from game data. Falls back to the X if the BLP fails to load.
2026-07-11 11:16:41 -07:00
Kelsi
8f1e22b258 fix(animation): keep nearby monster skeletons at full frame rate
CharacterRenderer began skipping bone updates beyond only 10 yards, reduced monsters to one update every four frames beyond 20 yards, and reached one update every eight frames beyond 40 yards. Animation time continued advancing between those updates, so poses jumped forward in large steps and locomotion looked like a low-frame-rate slideshow even when movement interpolation was smooth.

Keep character and creature bones at the render frame rate through 45 yards, use every-second-frame updates from 45 to 90 yards, and reserve every-fourth-frame updates for models beyond 90 yards. This brings the creature path in line with the generic M2 renderer while preserving meaningful distant-animation cost reduction.

Verification: full wowee build succeeded and all 31 CTest tests passed.
2026-07-11 10:24:53 -07:00
Kelsi Rae Davis
86927ced2b Merge pull request #89 from paleophyte/fix/tbc-deeprun-tram-boarding
Fix Deeprun Tram: boarding, positioning, orientation, and polish
2026-07-10 17:31:26 -07:00
Kelsi
11d45e32b3 fix(geosets): correct boot/shin geoset selection and DBC loading
The DBC loader skipped binary DBCs in Data/db/ on Linux because
filenames are lowercase but code requested mixed-case names.  This
caused the corrupted CSV fallback to be used, where nearly all
integer fields (including GeosetGroup) were misclassified as strings
and exported as string-block lookups instead of numeric values.

Add case-insensitive filename matching for the Data/db/ directory
scan and prioritize expansion overlay DBCs.  Fix kGeosetBareShins
from 503 to 501, add per-race lowestInGroup fallback (Gnome, Tauren),
and use DBC layout JSON for field indices in game_screen_hud.  Exclude
field 0 (record ID) from string detection in both dbc_to_csv and the
asset extractor to prevent future CSV corruption.
2026-07-10 17:17:24 -07:00
Josh Anderson
e0e732f0d2 Merge master (synced with upstream Kelsidavis/WoWee) into tram fix branch
Pulls in upstream's torso-twist strafing/camera work, integrity hash
hardening, and asset cleanup (removed unused AzerothCore SQL dumps and
list_mpq). Auto-merged cleanly, including camera_controller.hpp/cpp
where both this branch's suppressVerticalPhysics() addition and
upstream's strafing changes touched the file - verified with a full
rebuild afterward.
2026-07-10 13:04:14 -05:00
Josh Anderson
ccb97331a9 Fix invisible fall-velocity buildup during Deeprun tram rides
Z is intentionally locked to the tram deck while riding (the tunnel has
no real floor), but CameraController's own gravity/jump physics kept
running underneath that lock every frame since grounded never went
true on a moving M2 deck. verticalVelocity silently grew toward
terminal velocity for the whole ride and unleashed all at once the
instant the lock released at disembark, clipping the player through
the floor at "a weird angle." It also explains why jump appeared to
do nothing while riding, since canJump requires a recent grounded
frame that a tram ride never produces.

Added CameraController::suppressVerticalPhysics() (zeroes
verticalVelocity, forces grounded true) and call it every frame the
Deeprun tram Z-lock is active.
2026-07-10 10:11:38 -05:00
Josh Anderson
6adc272def Fix falling animation while riding, revert to flat tram orientation,
guard against corrupt-position disembark

Three separate fixes from this round's live testing:

1. Falling animation during a smooth ride ("kept like playing the
   falling animation the whole time"). The M2 ride-lock code
   overwrites render position directly every frame, so the camera
   controller's own gravity/collision correctly finds no real ground
   under a moving platform over open track and reports not-grounded -
   right for physics, but that fed straight into the animation FSM as
   "falling" for the whole ride. Added AnimationController::
   setM2TransportRiding() to override just the animation-facing
   grounded signal while actively riding, without touching the camera
   controller's real physics/collision (needed for walk-while-riding
   to keep working).

2. Reverted tram orientation from a clamped partial pitch back to a
   full flatten. The clamp was added to address an earlier "clipping
   into the ground" report, but a live comparison against the real
   game client confirmed real Deeprun Tram cars stay level through
   elevation changes - any visible tilt is wrong regardless of
   clipping trade-offs.

3. Live data caught a transient position corruption: after riding
   smoothly for ~70s, one frame produced player=(-44.9,2309.03,..) vs
   tram=(2309.83,-45.4,..) - the player's X and Y are each within ~1
   unit of the tram's Y and X respectively, an axis swap rather than a
   real position, yielding horizDist~3330 and an instant disembark
   right as the tram approached the destination platform ("kicked me
   off when I almost got to the other station"). Root cause not yet
   found (suspected: a raw server-coordinate movement update landing
   without the usual serverToCanonical swap - that conversion has the
   same X/Y-swap shape as the corruption, and a "MSG_MOVE_TELEPORT_ACK:
   not enough data for movement info" was logged moments later in the
   same session). Added a sanity guard: skip the disembark check
   entirely on a physically-impossible single-frame jump rather than
   trusting it, so a future occurrence can't eject the player over
   open track on bad data while the real source gets tracked down.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 06:28:54 -05:00
Kelsi
c1f8e71885 perf: eliminate per-frame allocations and redundant lookups in render loop
Pre-classify kobold candle flame at model load time instead of per-batch
string alloc+lowercase+find in the render loop. Add O(1) normal map index
(normalMapByTexPtr_) replacing O(n) textureCache scan per batch. Reuse
toUpdate_ vector across frames instead of heap-allocating every frame.
Reuse preLogicalOp in handlePacket() instead of calling fromWire() twice.
Fix double hash lookups in loadModel() and createInstance().
2026-07-09 23:00:55 -07:00
Kelsi
0073be897f chore: remove 3 dead header files
i_anim_renderer.hpp (unused interface), chat_fwd.hpp (unused forward
declarations), ui_constants.hpp (unused colour constants) — none are
included or referenced anywhere in the codebase.
2026-07-09 22:43:33 -07:00
Kelsi
8ab008212f feat: torso-twist strafing, camera snap, and mount strafe anims
Replace dedicated strafe/run-left/right animations with plain walk/run
plus a SpineLow bone yaw twist driven by the difference between travel
heading and camera facing. Adds setInstanceTorsoYaw() and a travelYaw
tracker in CameraController.

Camera smoothing now snaps 1:1 while actively dragging or keyboard
turning instead of always lerping, reducing perceived input lag.

Mounts use MOUNT_RUN_LEFT/RIGHT animations for strafing when available.
Default mouse invert changed to off.
2026-07-09 22:10:24 -07:00
Josh Anderson
628542da63 wip(tbc): preserve deeprun tram and portal work 2026-07-09 19:17:25 -05:00
Josh Anderson
72852c88ab fix(tbc): map server emote IDs before animations 2026-07-07 18:02:01 -05:00
Josh Anderson
b83f323790 fix(ui): stabilize logout mouse capture 2026-07-06 00:24:47 -05:00
Josh Anderson
8a4ad002c5 fix(rendering): stabilize character preview 2026-07-05 23:21:41 -05:00
Josh Anderson
5d6b2b51d8 feat(ui): polish TBC playtest HUD 2026-07-05 19:17:54 -05:00
Kelsi
6b75c7b5c5 perf(weather): single-pass particle update + position copy, hoist camera read
Weather::update walked the particle vector twice (update, then copy
position into the GPU upload buffer) and called camera.getPosition()
once per particle inside updateParticle. Fold both passes into one
loop and hoist the camera read once. For storms with thousands of
particles, that's one pass instead of two plus N fewer member reads.
2026-05-14 13:24:59 -07:00
Kelsi
e81673c4bf perf(m2-cull): precompute static cull factors on each M2 instance
The M2 cull SSBO upload and the CPU-fallback culling path both
recomputed worldRadius / cullRadius / effectiveMaxDistSqFactor /
paddedRadius for every instance every frame, even though all of
those values depend only on static instance state (bound radius,
scale, disable-animation flag, ground-detail flag).

Add recomputeCachedCullFactors() to M2Instance and call it once
when an instance is added (both addInstance overloads). The
per-frame upload reduces to one multiply (maxRenderDistanceSq *
cached factor) plus flag packing, and the CPU-fallback culling
path drops the same redundant math. For 5000+ doodad instances
that's measurable.
2026-05-14 12:40:43 -07:00
Kelsi
274516ad24 perf(wmo): cache ModelData pointer on draw list, skip duplicate hashmap find
WMORenderer::render did loadedModels.find(instance.modelId) three times
per visible instance per frame:
- once in the cullInstance lambda
- once in the main draw-list loop
- and the build pass earlier already gated visibility with .count()

Stash the const ModelData* on each InstanceDrawList during culling and
have the draw loop read straight through it. One hash lookup per
instance per frame instead of two.
2026-05-14 12:34:36 -07:00
Kelsi
e75aa8269f perf(vk): reuse the immediate command buffer across submits
beginSingleTimeCommands was calling vkAllocateCommandBuffers and pairing
it with vkFreeCommandBuffers in endSingleTimeCommands — one allocate/
free round-trip every immediate submit. The M2 frustum-cull dispatch
fires this every frame in beginFrame(), so that's two kernel calls per
frame just to obtain and release a command buffer.

The immediate pool already has RESET_COMMAND_BUFFER_BIT set, so cache
one buffer and reset it on each begin instead. The pool destruction
in shutdown reclaims the underlying allocation automatically.
2026-05-14 12:31:22 -07:00
Kelsi
54b934577d perf(render): cache static sorts, skip WMO portal vector copy
- character_renderer: precompute the (priorityPlane, materialLayer)
  batch render order on the M2 model at load time. Was allocating a
  std::vector<size_t> and running stable_sort per character per frame
  even though the result depends only on static model metadata.
- wmo_renderer: per-instance portal visibility was built into an
  unordered_set, then copied to a vector, sorted, and binary-searched
  per group. Use the set directly with find() — O(1) per group, drops
  the per-instance copy + sort. Removed the now-dead scratch vector.
2026-05-14 11:47:23 -07:00
Kelsi
500ad2e711 fix(camera): NaN/range guards on Camera setters
setPosition/setRotation/setAspectRatio/setFov now reject:
- NaN/inf inputs (would produce NaN view/proj matrix → frozen GPU
  on some drivers, garbage frustum culling everywhere)
- aspectRatio <= 0 (degenerate perspective)
- fov <= 0 or >= 180 (degenerate perspective)

Camera is constructed and set from many code paths; pushing the
guards into the setters means none of them need to remember.
2026-05-06 08:57:31 -07:00
Kelsi
1d05bb0f13 fix(terrain): main game also propagates MODF scale to WMO instances
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.
2026-05-06 03:55:14 -07:00
Kelsi
5241bbd669 perf(editor): cache M2/WMO models across rebuilds, only clear instances
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.
2026-05-06 03:23:06 -07:00
Kelsi
1c3307a0b6 fix(editor): unload ghost preview model when path changes
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.
2026-05-06 00:51:22 -07:00
Kelsi
cff8d359e4 feat: client-side WOC collision loading + walkability queries
- 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
2026-05-05 15:32:48 -07:00
Kelsi
4fc0361f7a feat: complete client integration for all 6 open formats
- 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
2026-05-05 12:41:19 -07:00
Kelsi
2980ca83e7 feat(editor): add standalone world editor (rough/WIP)
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
2026-05-05 03:47:03 -07:00
Pavel Okhlopkov
97c95941f4 feat(world-map): remove kVOffset hack, ZMP hover, textured player arrow
- 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>
2026-04-12 20:02:50 +03:00
Pavel Okhlopkov
fff06fc932 refactor: decompose world map into modular component architecture
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>
2026-04-12 09:52:51 +03:00
Pavel Okhlopkov
b79d9b8fea feat(rendering): implement spell visual effects with bone-tracked ribbons and particles
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>
2026-04-07 11:27:59 +03:00
Kelsi
4dcea08b90 Revert "fix(rendering): enable backface culling for one-sided M2 materials (#57)"
This reverts commit 7b746a3045.
2026-04-06 18:27:52 -07:00
Kelsi
7b746a3045 fix(rendering): enable backface culling for one-sided M2 materials (#57)
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.
2026-04-06 18:18:05 -07:00
Pavel Okhlopkov
97106bd6ae fix(render): code quality cleanup
Magic number elimination:
- Create protocol_constants.hpp, warden_constants.hpp,
  render_constants.hpp, ui_constants.hpp
- Replace ~55 magic numbers across game_handler, warden_handler,
  m2_renderer_render

Reduce nesting depth:
- Extract 5 parseEffect* methods from handleSpellLogExecute
  (max indent 52 → 16 cols)
- Extract resolveSpellSchool/playSpellCastSound/playSpellImpactSound
  from 3× duplicate audio blocks in handleSpellGo
- Flatten SMSG_INVENTORY_CHANGE_FAILURE with early-return guards
- Extract drawScreenEdgeVignette() for 3 duplicate vignette blocks

DRY extract patterns:
- Replace 12 compound expansion checks with isPreWotlk() across
  movement_handler (9), chat_handler (1), social_handler (1)

const to constexpr:
- Promote 23+ static const arrays/scalars to static constexpr across
  12 source files

Error handling:
- Convert PIN auth from exceptions to std::optional<PinProof>
- Add [[nodiscard]] to 15+ initialize/parse methods
- Wrap ~20 unchecked initialize() calls with LOG_WARNING/LOG_ERROR

Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
2026-04-06 22:43:13 +03:00
Pavel Okhlopkov
2e8856bacd memory, threading, network hardening
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
2026-04-06 21:19:37 +03:00
Pavel Okhlopkov
312994be83 world loading memory pressure detector
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
2026-04-06 21:05:20 +03:00
Pavel Okhlopkov
4b9b3026f4 feat(rendering): add HiZ occlusion culling & fix WMO interior shadows
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>
2026-04-06 16:40:59 +03:00
Paul
34c0e3ca28 chore(refactor): god-object decomposition and mega-file splits
Split all mega-files by single-responsibility concern and
partially extracting AudioCoordinator and
OverlaySystem from the Renderer facade. No behavioral changes.

Splits:
- game_handler.cpp (5,247 LOC) → core + callbacks + packets (3 files)
- world_packets.cpp (4,453 LOC) → economy/entity/social/world (4 files)
- game_screen.cpp  (5,786 LOC) → core + frames + hud + minimap (4 files)
- m2_renderer.cpp  (3,343 LOC) → core + instance + particles + render (4 files)
- chat_panel.cpp   (3,140 LOC) → core + commands + utils (3 files)
- entity_spawner.cpp (2,750 LOC) → core + player + processing (3 files)

Extractions:
- AudioCoordinator: include/audio/ + src/audio/ (owned by Renderer)
- OverlaySystem: include/rendering/ + src/rendering/overlay_system.*

CMakeLists.txt: registered all 17 new translation units.
Related handler/callback files: minor include fixups post-split.
2026-04-05 19:30:44 +03:00