Files
dusk/.claude/display-mesh.md
T
2026-06-16 12:29:36 -05:00

5.8 KiB

Display -- Mesh

Source: src/dusk/display/mesh/

See also: .claude/display-spritebatch.md, .claude/display-shader.md


Overview

The mesh system wraps platform-specific GPU geometry buffers behind a common mesh_t type. Geometry is described as an array of meshvertex_t values and a primitive type. The platform layer (meshplatform.h) provides the concrete buffer and draw implementation (VBO on OpenGL, display list or immediate-mode on Dolphin GX).


Vertex format (meshvertex.h)

typedef struct {
  #if MESH_ENABLE_COLOR
    color_t color;     // optional per-vertex colour (disabled by default)
  #endif
  float_t uv[2];      // texture coordinates (U, V)
  float_t pos[3];     // position (X, Y, Z)
} meshvertex_t;

MESH_ENABLE_COLOR is a compile-time flag (default 0). Enable it with -DMESH_ENABLE_COLOR=1 at CMake configure time if per-vertex colouring is needed; be aware this changes the struct size and breaks binary compatibility with pre-built mesh data.


Core API (mesh.h)

// Platform alias -- do not use meshplatform_t directly.
typedef meshplatform_t mesh_t;
typedef meshprimitivetypeplatform_t meshprimitivetype_t;

// Upload vertices to the GPU. Must be called from the main thread.
errorret_t meshInit(
  mesh_t *mesh,
  const meshprimitivetype_t primitiveType,
  const int32_t vertexCount,
  const meshvertex_t *vertices
);

// Flush a range of updated vertices to the GPU (modern targets only).
// vertexCount == -1 flushes all vertices.
errorret_t meshFlush(
  mesh_t *mesh,
  const int32_t vertexOffset,
  const int32_t vertexCount
);

// Draw the mesh. vertexCount == -1 draws all vertices.
errorret_t meshDraw(
  const mesh_t *mesh,
  const int32_t vertexOffset,
  const int32_t vertexCount
);

// Compute the axis-aligned bounding box.
void meshGetBounds(const mesh_t *mesh, vec3 outMin, vec3 outMax);

int32_t meshGetVertexCount(const mesh_t *mesh);

errorret_t meshDispose(mesh_t *mesh);

On constrained targets (GameCube/Wii) meshFlush is a no-op -- the hardware reads vertices from main memory directly. Always call it on desktop/mobile targets after modifying vertex data.


Primitive generators

Each generator provides:

  • A *Buffer(vertices, ...) function that writes into a caller-supplied meshvertex_t array (no allocation).
  • A global pre-built *_MESH_SIMPLE singleton + *_MESH_SIMPLE_VERTICES array initialised at engine startup (for common one-off uses).

All generators use CCW winding and MESH_PRIMITIVE_TYPE_TRIANGLES.

Quad (quad.h)

#define QUAD_VERTEX_COUNT 6  // two triangles

// 2D quad in XY plane:
void quadBuffer(
  meshvertex_t *vertices,
  const float_t minX, const float_t minY,
  const float_t maxX, const float_t maxY,
  const float_t u0, const float_t v0,
  const float_t u1, const float_t v1
);

// 3D quad using full vec3 min/max:
void quadBuffer3D(
  meshvertex_t *vertices,
  const vec3 min, const vec3 max,
  const vec2 uvMin, const vec2 uvMax
);

extern mesh_t QUAD_MESH_SIMPLE;

The SpriteBatch is built on quadBuffer3D internally.

Cube (cube.h)

#define CUBE_VERTEX_COUNT 36  // 6 faces x 6 vertices

// Axis-aligned box from min to max:
void cubeBuffer(
  meshvertex_t *vertices,
  const vec3 min, const vec3 max
);

extern mesh_t CUBE_MESH_SIMPLE;  // unit cube (0,0,0) to (1,1,1)

Plane (plane.h)

#define PLANE_VERTEX_COUNT 6

typedef enum {
  PLANE_AXIS_XY,  // flat in XY, normal along +Z (billboard / wall face)
  PLANE_AXIS_XZ,  // flat in XZ, normal along +Y (ground / floor)
  PLANE_AXIS_YZ,  // flat in YZ, normal along +X (side wall)
} planeaxis_t;

void planeBuffer(
  meshvertex_t *vertices,
  const planeaxis_t axis,
  const vec3 min, const vec3 max,
  const vec2 uvMin, const vec2 uvMax
);

extern mesh_t PLANE_MESH_SIMPLE;  // unit XZ plane (0,0,0) to (1,0,1)

Sphere (sphere.h)

#define SPHERE_STACKS  8
#define SPHERE_SECTORS 16
#define SPHERE_VERTEX_COUNT (SPHERE_STACKS * SPHERE_SECTORS * 6)

void sphereBuffer(
  meshvertex_t *vertices,
  const vec3 center,
  const float_t radius,
  const int32_t stacks,
  const int32_t sectors
);

extern mesh_t SPHERE_MESH_SIMPLE;  // unit sphere centered at (0,0,0), r=0.5

Capsule (capsule.h)

#define CAPSULE_CAP_RINGS 4
#define CAPSULE_SECTORS   16
// Total vertex count = (2 * capRings + 1) * sectors * 6

void capsuleBuffer(
  meshvertex_t *vertices,
  const vec3 center,
  const float_t radius,
  const float_t halfHeight,  // half-height of the cylindrical section only
  const int32_t capRings,
  const int32_t sectors
);

extern mesh_t CAPSULE_MESH_SIMPLE;  // r=0.5, halfHeight=0.5 (total h=2.0)

The long axis is always Y. This mirrors the physics capsule body (see .claude/physics.md).

Triangular prism (triprism.h)

#define TRIPRISM_VERTEX_COUNT 24

// Cross-section triangle defined by three 2D points in XY;
// extruded along Z from minZ to maxZ.
void triPrismBuffer(
  meshvertex_t *vertices,
  const float_t x0, const float_t y0,
  const float_t x1, const float_t y1,
  const float_t x2, const float_t y2,
  const float_t minZ, const float_t maxZ
);

extern mesh_t TRIPRISM_MESH_SIMPLE;
// Unit prism: triangle (0,0),(1,0),(0.5,1) extruded z=0 to z=1.

Custom dynamic mesh

If you need to update geometry each frame (e.g. a procedural mesh):

static meshvertex_t myVerts[MY_VERT_COUNT];
static mesh_t myMesh;

// On init:
// Fill myVerts, then:
errorChain(meshInit(&myMesh, MESH_PRIMITIVE_TYPE_TRIANGLES,
  MY_VERT_COUNT, myVerts));

// Each frame (after modifying myVerts):
errorChain(meshFlush(&myMesh, 0, -1));
errorChain(meshDraw(&myMesh, 0, -1));

Notes

  • meshInit must be called on the main thread (GPU upload).
  • meshFlush is required on OpenGL targets when vertices change after init. It is a no-op on Dolphin.
  • All _MESH_SIMPLE globals are initialised during engine startup -- do not call meshInit on them manually.