Rendering

One cube per non-air block, drawn via Easy3D's CubeMeshRenderer 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):
  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:
          add a 1×1×1 cube at (worldX, y, worldZ) to a CubeBatch,
          UV from tileAtlas.GetTileUv(block.type())

  staticMesh   = Easy3D::BuildCubeMesh(staticBatch)     // 24 verts + 36 indices per cube
  staticRenderer   = Easy3D::CubeMeshRenderer(device, staticMesh)
  animatedRenderer = Easy3D::CubeMeshRenderer(device, animatedMeshForCurrentPhase)

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

Wraps an Easy3D::TextureAtlas sized to object-m.png (1301×1431 px), registered as one grid: 64 px tiles, 20 columns, 65 px pitch (64 px icon + 1 px inter-tile gap). GetTileUv(blockType) returns an all-zero rect for Air (type ≤ 0), otherwise looks up "tile_<blockType>" in the atlas.

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.GetViewMatrix();
terrainEffect_->Projection = camera.GetProjectionMatrix();
terrainEffect_->World = Matrix::Identity;
staticRenderer->Draw(device, *terrainEffect_);
if (animatedRenderer) animatedRenderer->Draw(device, *terrainEffect_);

Known Rendering Limitations