Rendering

One cube per non-air block, built with cna-engine's MeshBuilder and drawn as a GpuMesh through CNA.

Overview

GalaxyEggbertCNA renders terrain only — there is no Blupi sprite, no object rendering, no HUD, no sky dome. Terrain rendering works end-to-end and is verified: a real run on worlds3d/world001.vwr uploaded 2749 blocks (pre-bugfix count) as 65976 vertices / 32988 triangles, and a 5×5 on-screen pixel sample found 21 of 25 sampled points showing real terrain color across 10 distinct colors — confirming genuine texture sampling, not a placeholder.

Pipeline

GETerrainRenderer(world, tileAtlas):
  staticMesh = MakeTexturedMeshBuilder()                // positions + one texture coordinate
  for each (x, y, z) in world.blocksPerAxis()³:
      block = world.getBlock(x, y, z)
      if block.isAir(): continue
      worldX = x - kWorldCenterX;  worldZ = z - kWorldCenterZ   // Y unshifted
      if BlockTypes::tileAnimBase(block.type()) is animated:
          store in m_animBlocks (rebuilt on anim-phase change)
      else:
          AddCenteredBox(staticMesh, (worldX, y, worldZ), size 1×1×1, faces)  // MeshBuilder::AddBox
          // faces: one BoxFaceStyle{visible, uv} per BoxFace,
          //        uv = tileAtlas.GetTileUv(block.type())

  staticRenderer   = UploadTexturedMesh(device, staticMesh.Mesh())   // unique_ptr<GpuMesh>
  animatedRenderer = UploadTexturedMesh(device, animatedMeshForCurrentPhase)

MakeTexturedMeshBuilder, AddCenteredBox and UploadTexturedMesh are the game's small helpers in src/GalaxyEggbert/Game/TexturedMesh.hpp over cna-engine's MeshBuilder and GpuMesh. A full cube is 24 vertices + 36 indices — the same vertices and indices easy-3d's BuildCubeMesh produced until 2026-10-20, bit for bit; the GPU meshes replaced easy-3d's CubeMeshRenderer.

No face culling or greedy meshing is performed — every non-air block gets a full cube, even where faces are fully occluded by neighbors. This is a known, flagged limitation (see Known Issues) that will matter once denser hand-authored worlds exist.

Texture Mapping — GETileAtlas

GetTileUv(blockType) returns a CnaEngine::Graphics::UvRect (u0, v0, u1, v1) into object-m.png (1301×1431 px; 64 px tiles, 20 columns, 65 px pitch: 64 px icon + 1 px inter-tile gap), computed with the game's own BlockTypes::tileUV(). It returns an all-zero rect for Air (type ≤ 0) and for types outside the sheet's grid.

Animated Tiles

Ported 1:1 from mobile-eggbert's Tables.cpp (via GalaxyEggbertSimple3D):

TileFrames
Lava8
Spike16
Crusher10
Saw6
Water1 / Water26 each
Temp20 (including 2 invisible/blank frames)
Fan ×4 directions3 each
Marine11

Animated blocks are split into a second mesh, rebuilt only when the anim phase actually changes (not every frame). If the current phase yields zero visible animated cubes (e.g. Temp's blank frames), the animated renderer is reset to null rather than uploading empty data.

Draw Call

terrainEffect_->View = camera_.View();             // CnaEngine::Graphics::Camera
terrainEffect_->Projection = camera_.Projection();
terrainEffect_->World = Matrix::Identity;
staticRenderer->Draw(device, *terrainEffect_);       // GpuMesh::Draw: each pass of the effect
if (animatedRenderer) animatedRenderer->Draw(device, *terrainEffect_);

Known Rendering Limitations