TerrainRenderer

Turns a loaded world into terrain meshes, keeps the animated ones current, builds reliefs near the camera and draws them in the right passes.

Headersrc/GalaxyEggbert/Game/TerrainRenderer.hpp
Sourcesrc/GalaxyEggbert/Game/TerrainRenderer.cpp
NamespaceGalaxyEggbert::Game
Former nameGETerrainRenderer (the GE prefixes were dropped from game components)

Purpose

One TerrainRenderer is built per loaded world (and rebuilt when the world changes in a way it cannot patch). The constructor walks every cell of the World and sorts it into a mesh by what its block definition and its tile's texels call for: the static opaque mesh, the cut-out mesh (alpha-tested sprites), the static blended mesh (faint tiles), the grass-top overlay, the animated and water cells (rebuilt when the animation phase changes), and the lava, lightning and wind cells (each frame of their cycle built once and cached). Cells drawn as 3D models are left out and listed by ModelCells(). The meshes are uploaded as GpuMeshParts, split when one buffer would be too big (MeshParts.hpp).

The class holds GPU resources and needs a GraphicsDevice, so it is not unit-tested itself; the pure parts it relies on are (see Tests).

Collaborators

Public API

MemberWhat it does
TerrainRenderer(device, world, tileAtlas, tileCoverage = {}, drawSecret = false, modelDrawnIcons = {}, reliefs = nullptr, ambientOcclusion = false, globalIllumination = nullptr)Builds every mesh. drawSecret draws the flight barrier 214 (ShowSecret); modelDrawnIcons are still icons the caller draws as models; reliefs must outlive the renderer; the last two bake ambient occlusion or Ultra's GI into the vertex colour.
AmbientOcclusion(), GlobalIllumination()Whether the meshes were built with those bakes.
Update(device, animPhase, blupiFocused = true)Rebuilds the animated and water meshes and picks the lava, lightning and wind frame for the phase; a no-op when the phase is unchanged.
UpdateRelief(device, focus, radius, maxBuilds = 2)Builds reliefs for the chunks within radius of focus (nearest first, on worker threads) and releases far ones.
AdoptReliefs(previous)Takes over a previous renderer's reliefs for chunks whose cells did not change.
SetReliefOverhangs(keep)Keep reliefs' overhangs into the air (for comparison).
ReliefChunkCount(), ReliefChunksShown(), ReliefPrimitiveCount()Relief statistics.
kReliefChunkCells = 8, kReliefRelease = 4.0, kReliefBuildsInFlight = 2Chunk edge in cells, release distance beyond the radius, worker builds at a time.
DrawOpaque(device, effect, shading = nullptr)Lava, the static mesh, reliefs or far cubes, the cut-outs; writes depth.
DrawTransparent(device, effect, shading = nullptr)Blended static tiles, animated tiles, lightning, wind and water; depth read only. Must come after everything opaque.
Draw(device, effect)Both halves in a row, for a caller with nothing to draw in between.
DrawGrass(device, grassEffect)The icon-107 grass tops with their own texture and effect.
DrawShadowCasters(device, solid, cutout), DrawGeometryPass(device, colored, plain, cutout), DrawWater(device, effect)The effects' shadow map, Ultra's geometry pass and its water mirror.
LavaCells(), BlitzCells(), BlitzShown()Where lava and lightning are, and whether the lightning strikes now (point lights, the zap sound).
SetModelDrawnBases(bases), SetSawModelBases(bases)Animated tiles the caller draws as models (fans, saws); the next Update() leaves them out.
ModelCells(), PatchModelCells(world, changes, icons, drawSecret, coverage)The cells drawn as models; apply journal changes that only touch them (false: rebuild instead).
BlockCount(), RaggedBrickCount(), AnimatedBlockCount(), WaterBlockCount(), VertexCount(), PrimitiveCount()Statistics printed at load.
CentroidX(), CentroidY(), CentroidZ()Centre of all non-air blocks, a fallback camera target.

TerrainShading (same header) carries the effects' replacement shaders: occluded, lit, cutout, lava, water and Ultra's reliefGi callback; a null member keeps the stock effect.

How it is used

Built at load (src/GalaxyEggbertCNA/GalaxyEggbertGameLoad.cpp):

terrainRenderer_ = std::make_unique<TerrainRenderer>(device, worldRuntime_.GetWorld(), terrainTileAtlas_, tileCoverage_,
                                                     terrainDrawSecret_, StillModelIcons(), TerrainReliefs(),
                                                     TerrainOcclusionWanted(), TerrainGiWanted());
...
terrainRevision_ = worldRuntime_.GetWorld().blockRevision();

After a tick changed blocks, the cheap path is tried first:

const auto changes = world.blockChangesSince(terrainRevision_);
if (TerrainDrawSecret() == terrainDrawSecret_ &&
    terrainRenderer_->PatchModelCells(world, changes, StillModelIcons(), terrainDrawSecret_, tileCoverage_))
{
    terrainRevision_ = world.blockRevision();
    return;
}
... // otherwise build a new TerrainRenderer and call rebuilt->AdoptReliefs(*terrainRenderer_)

Tests

Related pages