- Zone export now converts all placed M2 models to WOM open format
- Deduplicates: each unique M2 path converted only once
- WOM files saved to output/MapName/models/ with original path structure
- Uses WoweeModelLoader::fromM2() for M2→WOM conversion
- Combined with PNG texture export, zones can now be fully distributed
without any Blizzard proprietary files
Full open format export pipeline:
Terrain: ADT→WOT/WHM | Textures: BLP→PNG | Data: DBC→JSON
Models: M2→WOM | Buildings: WMO→WOB (manual) | Map: WDT→zone.json
ALL 6 BLIZZARD FORMATS NOW HAVE OPEN REPLACEMENTS.
WOB format: binary building file with groups, portals, and doodads.
- WOB1 magic header
- Groups: vertices (pos+normal+uv+color), indices, texture paths,
bounding box, indoor/outdoor flag
- Portals: connect groups with polygon boundaries
- Doodad placements: reference .wom models with transform
WoweeBuildingLoader: load/save/exists for .wob files.
Complete format replacement table:
- ADT → WOT/WHM (terrain heightmaps + metadata)
- WDT → zone.json (map definition)
- BLP → PNG (textures)
- DBC → JSON (data tables)
- M2 → WOM (static models)
- WMO → WOB (buildings with groups/portals/doodads)
The wowee project now has a complete suite of novel, open file
formats for creating and distributing custom WoW content without
any dependency on Blizzard proprietary file formats.
- TextureExporter: converts Blizzard BLP textures to standard PNG
for fully open redistribution of custom zones
- collectUsedTextures(): finds all texture paths referenced by terrain
- exportTexturesAsPng(): loads BLP via asset manager, writes RGBA PNG
using stb_image_write to output/MapName/textures/
- Zone export now automatically converts all used textures to PNG
- Client's PNG override system already loads these automatically
(checks for .png alongside .blp before loading)
Format replacement progress:
- DONE: ADT→WOT/WHM (terrain)
- DONE: WDT→zone.json (map definition)
- DONE: BLP→PNG (textures — auto-exported on zone save)
- TODO: DBC→JSON, M2→open model, WMO→open building
- File > Load Custom Zone scans output/ and custom_zones/ for zone.json
- Lists all discovered custom zones with name and quest indicator [Q]
- Tooltip shows description and author on hover
- Click to load the zone's WOT/WHM terrain into the editor
- Parses tile coords from WOT filename for correct positioning
- Full round-trip: export → discover → reload for iterative editing
- CustomZoneDiscovery scans directories for zone.json manifest files
- Discovers custom zones in custom_zones/ and output/ directories
- Reports: name, author, description, creature/quest availability
- Client can list all available custom expansions at startup
- Foundation for a zone selection menu in the client UI
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.
- File > Project > Git submenu: Init, Commit, Push, Pull operations
- Init Git Repo: initializes git in the project directory with initial commit
- Commit Changes: auto-saves zone then commits all changes
- Push/Pull: sync with remote repositories for team collaboration
- Git Status: shows current repo state directly in the menu
- Teams can collaborate on custom expansions using standard git workflows
- File > Project submenu: New/Save/Load project, add zones
- "Add Current Zone to Project" captures loaded map/tile info
- Project path editable in the menu
- Zone count shown for quick reference
- Foundation for multi-zone custom expansion workflow:
create project → add zones → edit each → export all as WCP
- EditorProject: manages multiple zones in a single project file
(wowee-project-1.0 JSON format)
- Project stores: name, author, description, version, zone list
- Each zone has: mapName, tileX/Y, biome, description
- Save/load project files (.json) with full round-trip
- Foundation for creating custom expansions with multiple maps,
quest chains spanning zones, and campaign progression
- getZoneOutputDir() resolves per-zone output paths
- File > Export Open Format (.wot/.whm) menu item for standalone export
- Every zone export (Ctrl+S, Export Zone) now also writes .wot/.whm
alongside the ADT/WDT — dual format output
- Export package now contains: ADT + WDT + WOT + WHM + JSON files
- Both Blizzard-compatible (for existing servers) and open format
(for wowee-native loading and redistribution)
Novel open terrain format unique to wowee:
.wot (Wowee Open Terrain) — JSON metadata:
- Tile coordinates, chunk grid dimensions, texture list
- Per-chunk layer assignments and hole bitmasks
- Water data per chunk (type, height)
- Format version "wot-1.0"
.whm (Wowee HeightMap) — binary heightmap:
- "WHM1" magic header
- 256 chunks × (baseHeight + 145 float heights) = 148KB
- Direct float storage, no compression, fully portable
Both formats are entirely novel — no ADT, no WDT, no Blizzard
structures. The WCP content pack can bundle .wot/.whm instead of
ADT/WDT for fully open redistribution.
Import/export functions: WoweeTerrain::exportOpen() / importOpen()
- File > Export Content Pack (.wcp) saves zone + packs into WCP archive
- Auto-saves zone first, then bundles into the novel WCP format
- WCP is a custom format unique to wowee (not MPQ, not CASC)
- Output: output/MapName.wcp ready for distribution
- Toast notification on success/failure
- WCP format: simple binary archive with magic header, JSON manifest,
and concatenated file data. Not tied to any proprietary format.
- ContentPacker::packZone(): bundles all zone files from output dir
into a single .wcp file (terrain, objects, creatures, quests, manifest)
- ContentPacker::unpackZone(): extracts .wcp to a directory
- ContentPacker::readInfo(): reads pack metadata without extracting
- Format: "WCP1" magic + fileCount + infoJSON + file table + data
- Foundation for distributing custom zones to other wowee users
and private servers
Note: currently bundles ADT/WDT files as-is. Future: convert terrain
to open format (heightmap + JSON) for fully open redistribution.
- "Create + Generate" was setting a flag but never calling generateCompleteZone()
- Now properly chains: create terrain → run full procedural pipeline
(noise → smooth → normals → height paint → slope paint → detail → water → beaches)
- Uses generateAfterCreate_ flag to defer generation until after terrain is created
- Pick height, Punch Hole, Fill Hole, Delete All in Radius buttons
no longer require cursor to be actively on terrain
- All buttons now use last known brush position consistently
- Fixes the fundamental UX issue where clicking a UI button moves
the cursor off terrain, making the button do nothing
- About dialog was broken because ImGui::OpenPopup inside BeginMenu
has wrong ID scope — popup never appeared
- Changed to a proper ImGui::Begin window with showAbout_ flag
- Closeable with X button or clicking About again
- Shows version, author, feature summary, and tech stack
- Removed isActive() requirement from all "Create at Cursor" buttons
(Mesa, Crater, Hill, Valley, Stamp, Ridge, Flatten Platform, Holes)
- Buttons now use the last known brush position which persists when
cursor moves to the UI panel to click the button
- Fixes: "Create Mesa at Cursor can't work if cursor is clicking
that button" — now it uses wherever the cursor was last on terrain
- Set Start/End buttons for river/road no longer require brush to be
actively on terrain at click time — uses last known brush position
which persists when cursor moves to the UI panel
- Shows cursor coordinates above the buttons for visual confirmation
- Edge ramp confirmed to only affect terrain heights, not water levels
(water has its own height independent of terrain)
- Ghost model state (ID, path, active flag) properly reset when
clearObjects() wipes the M2 renderer — prevents stale ghost
references causing crashes on subsequent hover
- Ghost model ID uses 59999 to avoid collision with placed object IDs
- Ghost loadModel failure now handled gracefully (returns early)
- Ctrl+Shift+Z redo now works globally (was only in Sculpt mode)
- Ctrl+Z undo works in all modes with toast confirmation
- centerOnTerrain() uses actual mesh chunk positions instead of formula
- Toast shows "Undo" / "Redo" / "Undo placement" for user feedback
- Camera now positions at the center of actual loaded terrain chunks
instead of using formula-based coordinates that can be wrong for
instanced maps
- Falls back to formula if no valid chunks loaded
- Fixes camera starting far from terrain on some maps
- ADT doodad/WMO positions now converted from ADT space to render coords
via core::coords::adtToWorld() — fixes objects appearing at wrong positions
when loading existing WoW maps
- Selecting a map in the Load dialog now auto-finds the first valid tile
(no more clicking "Find Tile" manually — it's automatic)
- Better error messages with toast for failed terrain loads
- Validates mesh has valid chunks before attempting GPU upload
Root cause of ADT loading failures: instanced maps (dungeons, raids)
have arbitrary internal coord values (e.g. tile 62,0 for a file named
_28_27.adt). The terrain mesh generator uses these coords to compute
world positions, resulting in chunks placed thousands of units away
from the camera → "Failed to upload terrain to GPU".
Fix: override terrain_.coord with the tileX/tileY from the filename
before mesh generation. This ensures chunks are positioned correctly
regardless of what the ADT file's internal header says.
- Quick Biome Paint: select a biome (Grassland/Forest/Desert/Snow/Swamp/Barrens)
and apply its full texture set in one click
- Sets base texture for all chunks + scatters variation patches
- Each biome has a primary ground texture + secondary variation
- Much faster than manual painting for initial zone texturing
- Scatter Patches: randomly places texture circles across the terrain
for natural-looking surface variation (dirt patches, rock outcrops)
- Configurable count, min/max radius, and seed
- Preset buttons for dirt and rock patches
- Uses the existing paint() method with random positions
- Adds visual variety to otherwise uniform terrain texturing
- Rotate 90 CW button in Mirror/Rotate section
- Snapshots outer vertex heights into 129x129 grid, rotates in-place,
writes back with inner vertices averaged from surrounding outers
- Useful for reorienting terrain features or creating rotational
symmetry (rotate + mirror for 4-way symmetric arenas)
- Gradient Blend: smoothly transitions base texture from one biome
to another across the entire tile (horizontal or vertical)
- Preset buttons: Grass→Sand and Grass→Snow gradients
- Creates natural biome transition zones without manual painting
- Alpha blending from 0% to 100% across 16 chunks
- Detail Noise: adds high-frequency roughness to existing terrain
without destroying the overall shape (amplitude 0.5-10, freq 0.01-0.5)
- Useful after smooth/generate to break up unnaturally smooth surfaces
- Workflow: generate → smooth → detail noise for natural look
- Separate seed from main noise generator for independent control