CubeMesh requires CNA link
Easy3D / Data & Batching / CubeMesh
The CubeMesh functions turn queued CubeBatch items — and a handful of related standalone shapes — into plain CPU-side vertex/index arrays. No GPU work: no GraphicsDevice, no vertex/index buffers, no shaders — just std::vector data a caller can later upload. This was Easy3D's first Roadmap Phase 3 vertex builder, and CubeMeshRenderer is the Phase 4 adapter that actually draws its output.
#include <Easy3D/CubeMesh.hpp>
namespace Easy3D
{
struct CubeVertex;
void AppendCubeMesh(const CubeItem& item, vertices, indices);
void BuildCubeMesh(const CubeBatch& batch, vertices, indices);
enum class CubeFace : int { PosZ, NegZ, PosX, NegX, PosY, NegY };
struct DirectionalCubeFace;
struct DirectionalCubeItem;
void AppendDirectionalCubeMesh(const DirectionalCubeItem& item, vertices, indices);
enum class PlateAxis { Z, X, Y };
struct PlateItem;
void AppendPlateMesh(const PlateItem& item, vertices, indices);
struct TripleCrossItem;
void AppendTripleCrossMesh(const TripleCrossItem& item, vertices, indices);
struct PyramidTipItem;
void AppendPyramidTipMesh(const PyramidTipItem& item, vertices, indices);
}
(Signatures abbreviated above — every Append*/Build* function takes std::vector<CubeVertex>& vertices, std::vector<std::uint32_t>& indices as its trailing two parameters, exactly like AppendCubeMesh.)
CubeVertex
One mesh vertex — shared by every shape on this page:
| Field | Type | Meaning |
|---|---|---|
Position | Microsoft::Xna::Framework::Vector3 | World position. |
Uv | Microsoft::Xna::Framework::Vector2 | Normalized texture coordinate. |
Winding: the XNA convention, not the OpenGL one
Every shape below is wound so its first triangle's cross(v1-v0, v2-v0) points into the solid, not out of it — the opposite of the OpenGL-textbook "counter-clockwise from outside" rule. This matches CNA's real, verified culling behavior: under the default RasterizerState::CullCounterClockwise, the triangles that survive are the ones that appear visually clockwise on screen — the same convention FNA's SpriteBatch quads and XNA's canonical tutorial triangle use.
AppendCubeMesh / BuildCubeMesh — the basic block
vertices, so results from repeated calls concatenate correctly into one combined mesh.batch, in CubeBatch::Items() order. Equivalent to calling AppendCubeMesh for each item in turn against the same output arrays.Properties of the generated geometry:
- Per-cube counts: 24 vertices and 36 indices (12 triangles). Vertices are not shared between faces so each face keeps its own UV corners.
- Face order: +Z, −Z, +X, −X, +Y, −Y — the same order
CubeFacebelow enumerates. - UVs: every face uses the item's single
Uvregion on all four of its corners, laid out as(U0,V0)-(U1,V0)-(U1,V1)-(U0,V1)—CubeItemstores one atlas region per cube, not one per face. For per-face UVs or partial faces, seeDirectionalCubeItembelow. - Index type:
std::uint32_t, so large combined meshes (> 65k vertices) are fine.
All functions on this page append — they never clear the output vectors. Clear them yourself when rebuilding from scratch, or exploit the appending to accumulate several shapes into one mesh (e.g. a cube plus a PyramidTipItem hanging beneath it).
DirectionalCube — per-face textures and holes
Identified by the mobile-eggbert-reference tile-identification work as the "DirectionalCube" render mode: a cube where each of the 6 faces independently chooses its own UV region and whether it is emitted at all. A Visible == false face is a genuine geometric hole (e.g. an open grate you can see and shoot through) — not just an untextured face.
enum class CubeFace : int { PosZ = 0, NegZ = 1, PosX = 2, NegX = 3, PosY = 4, NegY = 5 };
struct DirectionalCubeFace
{
bool Visible = true;
UvRect Uv{0.0f, 0.0f, 1.0f, 1.0f};
};
struct DirectionalCubeItem
{
Vector3 Center;
Vector3 Size;
DirectionalCubeFace Faces[6]; // indexed by CubeFace
};
void AppendDirectionalCubeMesh(const DirectionalCubeItem& item, vertices, indices);
AppendDirectionalCubeMesh appends only the item's visible faces, using CubeFace indexing into Faces[6] — faces with Visible == false contribute no vertices or indices at all, so a fully-invisible item produces an empty mesh and a 4-side, 2-top/bottom-open item produces exactly 16 vertices / 24 indices (4 faces, not 6). Each face's own Uv is independent, unlike CubeItem's single shared region.
Uv sub-rect of the existing atlas texture (a corner swatch) rather than Easy3D growing a second, color-only vertex/shader path.Plate — a flat double-sided panel
Identified as the "InnerFlatPlate" render mode: a single flat, genuinely double-sided plate centered inside an otherwise fully transparent block — a signpost, a thin post, a screen. The block's outer 6 faces are simply never drawn by the caller; this only builds the plate itself. The horizontal axis (PlateAxis::Y) is also used standalone for surface-only effects, like a grass top sitting above an otherwise ordinary block.
enum class PlateAxis { Z, X, Y };
struct PlateItem
{
Vector3 Center;
float Width = 1.0f; // X for Z/Y axis, Z for X axis
float Height = 1.0f; // Y for Z/X axis, Z for Y axis
UvRect Uv{0.0f, 0.0f, 1.0f, 1.0f};
PlateAxis Axis = PlateAxis::Z;
};
void AppendPlateMesh(const PlateItem& item, vertices, indices);
Axis | Plane | Reading |
|---|---|---|
Z | spans X (Width) × Y (Height), facing ±Z | a "north-south wall" |
X | spans Z (Width) × Y (Height), facing ±X | an "east-west wall" |
Y | spans X (Width) × Z (Height), facing ±Y | a horizontal "floor/ceiling/tabletop" |
AppendPlateMesh emits 8 vertices, 12 indices: two coincident quads with opposite winding, genuinely visible from both sides regardless of the renderer's cull state — unlike a single quad, which under standard culling only shows from one side.
TripleCross — a 3-plane cross billboard
Identified as the "TripleCrossBillboard" render mode: the same texture drawn on 3 vertical, double-sided planes through the block's center, each 60° apart around Y — a 3-plane generalization of the classic 2-plane "cross" billboard used for plants and foliage. Rotationally symmetric by construction, so unlike PlateItem/DirectionalCubeItem there is no per-icon facing decision to make.
struct TripleCrossItem
{
Vector3 Center;
float Width = 1.0f;
float Height = 1.0f;
UvRect Uv{0.0f, 0.0f, 1.0f, 1.0f};
};
void AppendTripleCrossMesh(const TripleCrossItem& item, vertices, indices);
AppendTripleCrossMesh emits 24 vertices, 36 indices — 3 PlateItem-style double-sided planes (8 vertices/12 indices each), the first lying in the Z=Center.Z plane exactly like a PlateAxis::Z plate, the other two rotated 60° and 120° around the Y axis through Center.
PyramidTip — a hanging spike
An inverted square pyramid: a flat square face at Center's Y (typically flush with a block's own bottom face), tapering down to a single point Height below it — a "hanging spike / stalactite" attachment, e.g. the cone tip beneath a teleporter pillar. This is not one of the confirmed terrain render modes from the tile-identification work — it's a small, distinct "extra geometry" attachment a caller layers on top of an existing cube/DirectionalCube render, not a replacement for one.
struct PyramidTipItem
{
Vector3 Center; // center of the square top face
float BaseSize = 1.0f; // width/depth of the square top
float Height = 0.5f; // vertical drop from the top face to the apex
UvRect Uv{0.0f, 0.0f, 1.0f, 1.0f}; // shared by the top square and all 4 side triangles
};
void AppendPyramidTipMesh(const PyramidTipItem& item, vertices, indices);
AppendPyramidTipMesh emits 16 vertices, 18 indices: 1 square top face (visible from below, matching a cube's own −Y face winding — 4 fresh vertices) + 4 triangular side faces tapering to one shared apex point (3 fresh vertices each; none shared with each other or the square, since each face needs its own UVs). Each triangular face maps Uv with its two base corners at (U0,V0)/(U1,V0) and the apex at the horizontally-centered ((U0+U1)/2, V1) — a tapering-to-a-point mapping matching the tapering geometry.
Example — level terrain to one mesh
#include <Easy3D/CubeBatch.hpp>
#include <Easy3D/CubeMesh.hpp>
Easy3D::CubeBatch terrain;
terrain.Begin();
// ... Add() one cube per solid tile (see the CubeBatch page) ...
terrain.End();
std::vector<Easy3D::CubeVertex> vertices;
std::vector<std::uint32_t> indices;
Easy3D::BuildCubeMesh(terrain, vertices, indices);
// vertices.size() == terrain.Count() * 24
// indices.size() == terrain.Count() * 36
// A grate tile (open top/bottom): append it into the same combined mesh.
Easy3D::DirectionalCubeItem grate;
grate.Center = Vector3(3.5f, 0.5f, 2.5f);
grate.Size = Vector3(1.0f, 1.0f, 1.0f);
for (int f = 0; f < 6; ++f) {
const bool topOrBottom = f == static_cast<int>(Easy3D::CubeFace::PosY)
|| f == static_cast<int>(Easy3D::CubeFace::NegY);
grate.Faces[f].Visible = !topOrBottom;
grate.Faces[f].Uv = tiles.GetUv("grate_side");
}
Easy3D::AppendDirectionalCubeMesh(grate, vertices, indices);
// The game now owns plain geometry data. Upload it with CubeMeshRenderer and draw.
Scope and status
CubeMesh was Easy3D's first Phase 3 vertex builder; BillboardMesh followed it. A debug line/box vertex builder is the one remaining Phase 3 gap. For issuing real CNA draw calls from this data, see CubeMeshRenderer (Phase 4, done). See the Roadmap.