BillboardMeshRenderer requires CNA link

Easy3D / Data & Batching / BillboardMeshRenderer

Easy3D::BillboardMeshRenderer uploads a BillboardMesh's vertex/index arrays to GPU buffers and issues indexed TriangleList draw calls — it mirrors CubeMeshRenderer's role and shape exactly, field for field, method for method. The one real difference is lifecycle: see below.

#include <Easy3D/BillboardMeshRenderer.hpp>

namespace Easy3D { class BillboardMeshRenderer; }
<Easy3D/BillboardMeshRenderer.hpp> is not pulled in by the <Easy3D/Easy3D.hpp> umbrella header — include it directly, same as BillboardMesh.hpp.

Constructor — uploads once

BillboardMeshRenderer(GraphicsDevice& device,
                      const std::vector<BillboardVertex>& vertices,
                     const std::vector<std::uint32_t>& indices)
Uploads vertices/indices to new GPU buffers immediately. indices.size() must be a multiple of 3; otherwise throws std::invalid_argument.

Each BillboardVertex{Position, Uv} converts 1:1 to CNA's VertexPositionTexture{Position, TextureCoordinate}, and the index buffer is 32-bit — identical mechanics to CubeMeshRenderer.

Draw

void Draw(GraphicsDevice& device, BasicEffect& effect) const
Draws the uploaded mesh once per pass of effect's current technique. effect's World/View/Projection and, if texturing is wanted, TextureEnabled/Texture must already be set by the caller.
[[nodiscard]] int VertexCount() const noexcept
[[nodiscard]] int PrimitiveCount() const noexcept
The uploaded vertex count and triangle count.

Lifecycle — rebuild whenever the camera moves

This is the one place BillboardMeshRenderer differs from CubeMeshRenderer: a billboard's vertex positions are baked in camera-facing at build time (see BillboardMesh). Since the camera typically moves every frame, that means a fresh BuildBillboardMesh call and a fresh BillboardMeshRenderer instance, every frame the camera changes — there is no camera-independent cache to reuse the way there is for cube terrain.

// Each frame:
const Vector3 cameraRight = /* derive from the current camera orientation */;
const Vector3 cameraUp    = /* ... */;

std::vector<Easy3D::BillboardVertex> vertices;
std::vector<std::uint32_t>           indices;
Easy3D::BuildBillboardMesh(sprites, cameraRight, cameraUp, vertices, indices);

Easy3D::BillboardMeshRenderer spriteRenderer(graphicsDevice, vertices, indices);

effect.World          = Matrix::Identity;   // positions are already in world space
effect.View            = camera.GetViewMatrix();
effect.Projection      = camera.GetProjectionMatrix();
effect.TextureEnabled  = true;
effect.Texture          = spriteSheetTexture;

spriteRenderer.Draw(graphicsDevice, effect);
Rebuilding the vertex/index vectors and the GPU buffers every frame sounds expensive, but it matches the scale Easy3D targets (a handful to a few dozen sprites per frame for Galaxy Eggbert's first versions). If a real consumer needs more, that is a concrete, decidable optimization to raise later — not something to speculatively build now.

Verification

Same constraint as CubeMeshRenderer: the constructor needs a live GraphicsDevice&, which a plain test main() cannot produce. Easy3D's test suite only compile-checks this class (see Testing); real runtime verification happens against a windowed CNA binary.