SweepCollider in a scene with no WMO and no terrain returned the feet
again instead of feet + move: SweepGround copied the feet into the end
before its ground check. With no ground the body now moves the full
distance, as the doc says; while a WMO loads the feet still stay put.
Water::Update made the scene's ripple pool with a zero clock, so a
ripple added in the frame the camera first entered water died on the
next age. It now goes through Ensure, which sets the scene's clock.
Trace read 0x008029D4 under a second name, VAR_TRACE_RUN_EPSILON; it
now reads VAR_TWO_POW_MINUS_22 and the duplicate is gone.
The paint builds its view volume once and hands it to every pass that
built its own: the camera probe (0x006809E2), the portal walk for the
sky, the WMO draw and the terrain's chunk, magma and water passes. The
sky's draw (0x006D4940) only saves and restores the viewport, and
FrustumStore (0x00682D90) only copies from the volume, so one volume
serves the whole paint.
The SFile wrapper is one class (model/SceneFile.h) for the WMO and the
terrain readers. The terrain's decal and liquid box tests take their
chunk rectangle and shifted box from ChunkWindow, and AreaAt its row
and chunk from Cell and ChunkAt, in the same rounding as before.
An actor whose movement is on now leaves the world's ripples in shallow
liquid: foam rings while it stands, turns or splashes, wakes along its
motion while it moves.
The world keeps its ripples in one pool of 128 records (0x50 bytes) that
its liquid query fills and its liquid pass draws. A scene keeps a pool
of its own with the same records (model/SceneWater.cpp), and the steps
are transcribed:
- CGUnit_C::EmitWaterRipple (0x005FA760), each actor update (as
0x0060802E) and after the swim decision's splash (kind 0xC9, as
0x0060319F): with the unit's liquid and bit 0x40 (flag 0x1 of the
area's LiquidType, kept now by the placement's liquid leg), the depth
under twice the collision height (at least 1.0) and the next time
(+0xC78) reached. The kind (moving 0xF, turning 0x30, still, splash)
and the table 0x00860A4C {0, 0, 1, 0} make a wake or a ring; the size,
lifetime and growth come from three draws of the client random stream
and the function's own constants, scaled down past half the depth and
for a still unit (0x0080C474); a ring takes a fourth draw for its
heading, a wake the motion's; the next time follows the speed, or
400 ms and a draw's share of 50.
- AddSplash's immediate leg (0x0068BDA0, gated by waterRipples
0x0086A03C and mode 0x00C89DB8), AddRipple (0x0068F9F0: the active
mover's range 0 to 31) and EmitRipple (0x0068F5C0): the record, its
rise and decay at 0.4 of the life (0x00810348), and the liquid
triangles TestWorldBox finds in its box with class 0xF0000, here
World::LiquidTriangles over the WMO and the scene's terrain.
- UpdateRipples (0x0068FAE0), its ageing in the scene's update and its
draw in the scene's liquid phase: blend 3, fog, depth write, culling
and lighting off, the depth bias footstepBias x 1/1024, the world
stack at Translate(-eye), per category its texture (0x00C800E4) over
format 4 vertices with the alpha's byte over white and the uv through
BuildDecalTransforms (0x006D6FA0) of the box turned by the heading.
The scene's terrain box test (TestTerrainBox) now has TestChunkBox's
liquid leg (0x006AD75E): GatherLiquidCellFacets on each slot the class
names, into the same batches. The shadow decal's class asks for liquid
too, so actor shadows lie on terrain water as in the world.
The scene's clock times the ripples, so a paused scene holds them.
A scene's camera in water, ocean or magma now sees the world's drift
cloud: the motes that float around the camera under a liquid.
The world builds one cloud at start-up (0x0066F971: 0xFA40 bytes,
CreateDriftCloud 0x0068E5A0 with the size base 1/36, the edge 30.0 and
Textures\WaterPoop02.blp at 0x00867A34) and drives it from its own
camera. A scene now owns a cloud made the same way the first time its
camera enters a liquid, and runs the world's steps from its camera:
- CameraEnvironment's change leg (0x00680AEE): on a change to water or
ocean the scatter (0x0068E720) in the new liquid's mode and the size
base 1/36 (0x0068E670), 1/9 for magma, and the enable byte +0xFA14;
slime clears it.
- The world frame's advect (0x0066FEE0 to 0x0068E930), with the scene's
eye staged in the camera position (0x00C7CF20) and its frame time in
the frame seconds (0x00C62510) the advect reads.
- The world stage's draw (0x006701E0 to 0x0068EFE0) after the models,
in the device's view with the world transform identity.
Both run while the camera is in a liquid and world flag 0x2000000 (the
waterParticulates setting) is set. The cloud's texture is closed with
0x0068E640 when the scene goes. The module is model/SceneWater.cpp.
The world transform stack id 8 moves from two local constants to
Offsets.h (GX_XFORM_WORLD).
A scene with terrain and no WMO now clears and draws its frame as the
world's does in the open, in place of a depth-only clear.
DrawWorldScene (0x0068123F) clears to black while the camera is dry and
to the fog colour (+0x70) under a liquid, and draws the sky only when
dry and the weather byte is set; outside every map object that byte
always stands set. The sky pass the WMO scenes already run (the dome
colours from the camera's heading, the map's light from BuildMapLight
with its sun disc and skybox slots, DrawSky, the world's objects put
back after) now takes the map, the camera's liquid and the portal
walk's skybox weight instead of the backdrop, so ClearAndDrawOpenSky
runs it for the terrain's map with no skybox from a walk. Under a
liquid the frame's fog is already the map's for it (LiquidFog), so the
clear takes that colour.
The scene's terrain liquid now answers the queries the world asks of
its own, so actors swim in it, GetLiquidAtPoint finds it, the camera
goes under it, and the splashes play.
Point query: GetLiquidStatus (0x0069B6D0) finds its chunk through the
global area table, so its terrain leg is transcribed over the scene's
chunks: u and v at 0.24 cells a yard (0x0081073C), stored and rounded
less 0.5; each slot's tile byte (+0x10) gives type & 3; the ocean is
height 0 under 0.01, else the MCLQ heights (+0x0C) bilinear at 53
bits, stored, the point under it plus 0.01. The scene asks the WMO
first and the terrain after, as GetLiquidStatus asks the map objects
first; GetLiquidAtPoint and the actor's swim step use it.
Swimmer's facets: GatherChunkFacets (0x006AADC0) under class 0x30000
adds an impassable chunk's walls (they ignore the class) and, for
each slot the class names, GatherLiquidFacets (0x006AB4E0): the facet
batches reset, GatherLiquidCellFacets (0x0068CEC0, called on the
scene's own slots) with the chunk-relative box and clipped cell
rectangle, and the batches emitted. The doodad leg takes no def for
this class. The box-to-cells step and the chunk window are shared
with the solid query; ResetFacetBatches and EmitFacetBatches leave
SceneWorld's anonymous namespace for it.
Camera: CameraEnvironment (0x006809C0) asks the claimed group's
liquid, else the liquid query (0x006723D0) at the probe point: the
eye lowered to the lowest near corner of the view. The probe drop
now comes from the scene's paint, so a scene without a WMO has one.
Under a liquid the world builds its light table with that liquid's
params and takes its fog from it (BuildSceneFog 0x006CEE30: end
+0x44 capped at the far clip, start +0x48 times it, colour band 7
+0x1C); LiquidFog swaps the frame's fog for that during the paint,
and a WMO's lava or slime without its own fog targets the same. A
terrain-only scene clears to the fog colour under a liquid, as
DrawWorldScene does (0x0068124D); the paint order and the water
colours follow the camera's liquid.
Sounds: CGUnit_C::UpdateSwimState (0x006030C0) plays the race splash
(ChrRaces +0x34, kept at +0xB18 by PostInit 0x005FB880) when the depth
past 0.4 of the height (0x0080C5C0) differs from the last test's
(+0x9A4), while not swimming; the mover's port now does, and the
actor keeps its unit's splash. The $FSD footstep is wet
(IsPointAtLiquidSurface 0x0060A740) when the unit, placed outdoors,
stands in a liquid (UpdateMapEntityEnvironment 0x0069E280) whose
LiquidType for the area (0x007CCF40; area from 0x0069B120) has flag
0x2, and the event point is from 0.01 above to 2/3 below the surface.
Constants already named under other roles are reused: 0x0081073C and
0x0086A750 become VAR_POINT_CELLS_PER_YARD and VAR_POINT_HALF_CELL,
and the nameplate lift 0x0080ABFC becomes VAR_TWO_THIRDS.
A scene's terrain now shows the rivers, lakes, ocean and lava of its
chunks, drawn by the world's own liquid code.
Load: after LoadNormals, each chunk runs CMapChunk::LoadLiquids
(0x006AF760), which reads one MCLQ block (0x324 bytes) per liquid flag
of the MCNK header into the four slots at chunk +0x118 and builds each
slot's vertices. The record check now bounds the MCLQ blocks too.
Buffers: the slots take a vertex and an index buffer from two static
pools the scene makes, as CreateLiquidBufferPool (0x0068CA50) makes the
world's (81 vertices of 40 bytes, 192 indices). The world rebuilds its
own pool, which would leave the scene's slots on freed buffers;
CChunkLiquid::Draw (0x0068DE40) re-acquires only for a usage-2 pool.
Each pair is marked unfilled with 0x0058A130, the call
AcquireLiquidBuffers (0x0068CC60) makes, so the first draw fills the
vertices and the strip.
Draw: the three passes are copied in the world's order. Magma
(DrawMagmaPass 0x006855A0: no culling, lighting or blending) after the
WMO, as DrawWorldScene has it; the ocean (0x00685010) then the river
(0x006851B0) before the WMO's liquids, as RenderLiquid (0x006816D0)
has it. Each pass binds its swatch, then the water program
(0xC81B7C) with the white specular and the shininess (0x8102E8) and
alpha blend when fancy water (0xC9A324) is on, else the add combine
on stage 1. A slot is culled by its box (0x0068DF40: the chunk's box
with the slot's heights as z), placed at its chunk less the eye, lit
as GatherSlotLights (0x0068DF70) lights it, and drawn with the pass.
The camera, fog end, frustum and world stack are staged as for the
chunks (one Staged helper now serves all three).
Colours: the river and ocean swatches refill from the light state
while their fresh flags (0xC8117C, 0xC81B70) are clear. World::MapWater
swaps the eight water fields of the live light record (+0x34..+0x40,
+0x68..+0x74) for the scene map's at the eye; the flags are cleared
before and after the draw, so the swatches take the scene's colours
and the world's next frame refills its own. The WMO's water uses the
same swap.
Actors do not swim in this water yet, and GetLiquidAtPoint does not
find it.
The world's chunk pass (0x006847C9) scatters a drawn chunk's detail
doodads once, with world flag 0x100000, when its view depth +0x78 is below
the horizon 0x00867958 (read live; 300.0 in this image):
CMapChunk::CreateDetailDoodads (0x006BFC10) from the layers' ground
effects, seeded by the chunk's grid position. The detail pass (0x006813D0)
then sets stage 0's mip bias (the immediate at 0x006813EA) and, for each
such chunk with a batch +0xC0, the chunk's transform and lighting record,
then DrawDetailDoodads (0x006C02D0) with the fade ramp. The scene runs both
over its own chunks.
The batches' slot buffers come from the world's detail pools, which
PrepareDetailDoodadBuffers (0x006B1D30) frees and rebuilds while 0x00CB0034
is set (a frillDensity change), after freeing only the world's listed
chunks' batches (0x006924F0); a slot's release (0x006B21D0) reads its
buffer's pool. So a scene batch gives its slot buffers back after each draw
(the world's idle release 0x006B28E0); the scene runs Prepare itself while
the flag is set (the world's frame never does at the glue screen), and
frees its batches (0x006B2030) to scatter them again when the rebuild
stamp 0x00CB003C, written only by Prepare, moves. CreateDetailDoodads reads
512 bytes at the MCSH pointer with or without a shadow map, so a chunk
whose range lies outside its file gets none.
The chunk pass and the detail pass each get their own render state push,
as DrawChunkPass and the detail pass do: the shader path's layer draw
(DrawLayersShader 0x006BF690) leaves its pixel program bound, and with one
push around both the grass drew through it as opaque quads.
A chunk's MCRF doodad references index the tile's MDDF placements, and the
world makes one CMapDoodadDef per unique id (+0x04, the hash 0x00CA7DDC).
PlaceDoodadDef (0x00694BC0) places it: position (origin - +0x10,
origin - +0x08, +0x0C), each a float sum; scale the word +0x20 x 1/1024
(0x008090D8); angles float(degrees x pi/180) with z + pi in one store;
the matrix TranslateMatrix, RotateMatrix about z, y and x (0x007BDD60),
ScaleMatrix (0x007BDD00); then CreateDoodadModel (0x00695100), the same
model setup as a WMO's doodads. LoadReferences (0x006B00F0) marks a chunk's
doodads outdoors (flag 4, intensity 1.0), and map lights only reach doodads
owned by WMO groups, so they take the sun and the zone fog: in the scene,
the frame's light and fog.
The WMO doodad code is now shared: a Doodad's WMO is optional and its sound
flag points to its owner's. World::Props holds a map's own doodads with the
same model setup, bounds, distance fade (0x00683F80), animate list, sounds
and collision facets (GatherDoodadFacets 0x006ABC40 rules, one stamp per
query). Terrain::QueryFacets adds each chunk's props after its triangles,
as GatherChunkFacets does (0x006AB496); a prop still loading fails the
query. SetPlayWorldModelSounds covers them.
AttachEntityToGround (0x006A8A20) traces the terrain (TraceTerrainSegment,
mask 0x100) and the map objects down 1000 yards from the probe point; with
neither hit it probes up past the bounds; the terrain wins when it is
nearer. A terrain win links the unit outdoors (the sun, no map object
lights), and its ground id is GetGroundEffect (0x0069B1C0) at the entity's
position: the cell from the map origin (0.24 at 0x0081073C, 0.5 at
0x0086A750; distances 0 to 34133.332), no hole, then the cell's dominant
layer from the chunk's MCNK +0x40 map (the row's word masked by
0x008104B4[column] and shifted by 0x008104C4[column]), that layer's effect
id (+0x0A, 0xFFFF none) into GroundEffectTexture (0x00C0DC60 / 0x00C0DC64),
whose +0x18 is the TerrainType id. GetGroundEffect finds the chunk through
the global area table, so Terrain::GroundEffect transcribes it over the
scene's chunks.
World::Place now runs both legs (the terrain through Terrain::Trace), and
World::Settle / Ground take the backdrop (nullable) and the terrain, so a
scene with terrain and no WMO places its actors too; the cached placement
keys on the terrain's serial as well. The Gurubashi pit's layer (ground
effect 587) is TerrainType 0, Dirt.
The world lays a decal through its box test TestWorldBox (0x006AD2C0): map
objects into the shared triangle batches (mask 0xF00F0), then the terrain
(0xF0F00, TestTerrainBox 0x006AD4E0). The terrain leg's cells are
fistp(float(0.24 x d) - 0.5) of the distances from the map origin (0.24 at
0x00810B44, 0.5 at 0x0086AAC4), the first from the unrounded origin - min.y;
rows run from the max-x cell >> 3 to the min-x cell >> 3, columns likewise.
TestChunkBox (0x006AD610) finds each chunk through the global area table,
takes the box less the chunk position, clips the cell rectangle to
{0, 0, 7, 7} (IntersectCellRects 0x006ADBF0) and, under 0x100, calls
TestChunkTriangles (0x006AD7E0, fastcall(chunk, rect, box, unused), ret 8),
which reads only the chunk and writes one batch: its kept cell triangles,
the chunk's translation as the matrix, its vertices at +0x83C.
Terrain::CollectDecalBatches is that leg over the scene's own chunks,
calling TestChunkTriangles on each. World::DrawDecal now takes the WMO and
the terrain (either null), collects them in the world's order and draws
the batches with the existing DrawDecalBatches states. The scene's decal
helpers take the paint's ground (WMO, terrain); the flat quad is left for
a scene with neither.
The scene's ground is now its WMO and its terrain, taken in the order the
world's queries take map objects and chunks:
- Box query (the mover's sweeps, SweepCollider): QueryGroundFacets gathers
the WMO's facets, then the terrain's, as 0x006AA8B0 does; a WMO still
loading fails it. The terrain leg is the gather's cell math (0x006AA92D:
fistp(float(0.24 x float(17066.666 - c)) - 0.5), bounds against 0 and
34133.332) and GatherChunkFacets' wall and triangle legs (0x006AAE2A..
0x006AB3F1): AddImpassableWalls' quads (0x006AB530, the 32000 immediate
at 0x006AB59C), the hole bits (0x008103F8), the five-vertex outcodes
against the box grown by 0.019444443 (0x00810468, 0x008101B8) and the
four cell triangles (0x00810438). Their planes are Contact::MakeFacet,
whose arithmetic is the chunk gather's inline SSE path operand for
operand. GatherChunkFacets itself finds its chunk through the global
area table (0x00C96318), so it cannot see the scene's chunks.
- Segment trace (TraceSegment, area-kit shards): TraceGround traces the WMO,
then lets the terrain lower the fraction, as 0x0069BFF0 does. The terrain
leg converts the ends to cells as 0x0069C320 does and calls the client's
four cell walks (0x0069C4E0, 0x0069C570, 0x0069C600, 0x0069C780), which
only fill its scratch list (0x00C962C8, count 0x00C89F40); then
TraceListedCells (0x0069C920) over the scene's chunks: the ray relative
to each chunk, its four cell triangles (0x0081047C) through
IntersectRayTriangle (0x007C2C40, slack immediate at 0x0069CB96), the
nearest t*k at or above 0. The normal is PlaneFromTriangle's (as the
terrain first hit 0x006AA600 takes it), turned toward the start.
The mover's facet context is the scene frame. World::Sweep moved to the
scene (SweepGround) so it sweeps against both. A cell outside the scene's
chunks has no ground; the world fails or ends there, but the scene's
terrain ends at its radius and a WMO may reach past it.
scene:SetWorldTerrain(mapID, x, y [, radius]) shows the map's ground around
a point, in map coordinates (radius default one tile, 533.33331 at
0x0080654C); scene:ClearWorldTerrain() removes it. The Gurubashi Arena's
pit is terrain under its WMO, so SetWorldModel at the arena's MODF plus
SetWorldTerrain shows it as the world does.
The chunks are the world's CMapChunk objects, owned by the scene and built
and drawn by the world's own functions (model/SceneTerrain.cpp):
- Load: the ADT is read whole and its MHDR read as ParseAreaFile
(0x006C2010) reads it. LoadAreaTextures (0x006C20E0) fills an area
stand-in's texture slots. Each chunk is constructed (0x006AF1D0), gets
CreateAreaChunk's fields (0x006C2880) and the self-contained steps of
CMapChunk::Load (0x006AF5F0) in order: FixupRecordPointers, header
fields, BuildVertices, LoadNormals, the impassable flag, AddLayer,
CreateShadowTexture, CreateCombinedTexture. LinkNeighbours (global area
table), LoadReferences (the world's doodad hashes), LinkLights and the
area's filing are world state and left out; the scene links its own
chunks by LinkNeighbours' rule. Each record is bounds-checked first.
- Buffers: the world's chunk pool is freed and rebuilt on a far clip change
or map unload (CreateChunkBufferPool 0x006AE640), and only the world's
listed chunks give theirs back; its empty-pool fallback is the device's
shared dynamic buffer. The scene makes its own static pools the same way
(CreatePool 0x0058A160, PoolBuffer 0x00589F80; freed by 0x0058A040 and
GxBufferRelease), which SelectMesh never trades away (it re-acquires only
for a usage 2 pool).
- Draw, before the WMO: DrawChunkPass (0x00684510) per chunk the scene's
volume shows (IsBoxCulled 0x00682F40): the chunk level and view depth
(0x006AFAD0), the world transform at the chunk less the eye, a lighting
record at the chunk's centre with the scene's lights there, the scene's
fog and, when the depth less twice the radius is inside the fog end, its
light as the sun (DrawMapEntity's slots 0x006C0CC0), then SelectMesh
(0x006BEB50). DrawLayers reads the camera (0x00C7CF20) and fog end
(0x00C62964) globals, which hold the scene's values while it draws. The
anisotropic mip bias is the pass's own immediate (0x00684528).
- Teardown: UnloadChunk (0x006A6B10) and ~CMapChunk, the scene's buffers,
then each tile's texture handles (HandleClose, as UnloadArea does).
- The chunks are built again when the terrain shader flags, shadowLevel or
map shadows change (glue screen to world).
Not yet: collision, the tile's doodads and grass, terrain liquid.
The client indexes a table's id map (store +0x08) by id and refuses only an
id above the largest id (+0x0C, signed, so negatives too) and an empty slot
(0x005F4010: test/jl, cmp/jg, null test). DBC::Record also refused id 0,
which is a real row in Map.dbc (Eastern Kingdoms), TerrainType and
ItemClass (Consumable); every other table leaves slot 0 empty, so it still
returns null there.
Effects: map 0 now has its directory, so scene:SetWorldTerrain(0, ...)
finds Eastern Kingdoms' tiles; the scene's death thud plays on TerrainType
0; ItemClass 0 resolves to "Consumable" in the item name lookups.
Instance::Info and the C_Map MapInfo reader had each copied the lookup
without the id 0 check to work around it; both now call DBC::Record.
# /O1 optimizes for size: the code is mostly short glue around engine calls,
# and /O1 made the DLL 280 KB smaller than /O2 /Ob2 (link-time code generation,
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.