Skip to content

@carrot/engine-meshes ​

ts

CPU-side mesh data and procedural geometry generators - primitives, subdivided planes, and path-to-mesh ribbon generation.

Installation ​

bash
npm install @carrot/engine-meshes

Architecture / How It Works ​

Mesh ​

The core Mesh class is pure CPU data - vertex attribute buffers and triangle indices with no WebGL dependency. The renderer uploads mesh data to the GPU via its own pipeline.

Attribute layout follows a convention matching standard shader locations:

  • 0: positions (vec3)
  • 1: normals (vec3)
  • 2: tangents (vec4)
  • 3-8: uv1-uv6 (vec2)
  • 9: colors (vec4)

All attributes are optional. Dirty tracking (isDirty) lets the renderer know when to re-upload.

VertexView ​

A zero-allocation view used by forEachVertex(). One VertexView instance is reused across all vertices - getters and setters read/write directly into the underlying FloatBuffers at stride offsets. This avoids per-vertex object allocation while providing a clean API for vertex manipulation.

Primitive generators ​

Five generators create common shapes: meshFromQuad, meshFromTriangle, meshFromCircle, meshFromPlane, meshFromCube. All return a Mesh with positions, normals, UV1, and indices populated.

Path-to-mesh generators ​

Five generators create ribbon/strip meshes from polylines and bezier curves:

  • meshFromStrip - open ribbon from a polyline (roads, trails, rivers)
  • meshFromLoop - closed ribbon from a polyline loop (borders, fences)
  • meshFromBezier - ribbon from a single bezier curve
  • meshFromBezierStrip - ribbon from a multi-segment bezier strip
  • meshFromBezierLoop - closed ribbon from a bezier loop

All path generators produce meshes in the XY plane (Z=0) with UV mapping: U across width (0 left, 1 right), V along length (normalised by cumulative distance).

Normal computation ​

computeNormals() calculates smooth normals from positions and indices - accumulates face normals at shared vertices and normalises. Works for any indexed triangle mesh.

Dependencies ​

PackageUsed For
@carrot/engine-buffersFloatBuffer, IndexBuffer for vertex/index data
@carrot/maths-geometryVector2Like, Bezier2, BezierStrip2, BezierLoop2 for geometry types
@carrot/colorsColorRgbLike for setColorsAll()

Build ​

bash
npm run build   # runs tsc

Usage Guide ​

CPU-side mesh data and procedural geometry generators - create primitives, subdivided planes, and path-following ribbons.

Import ​

ts
import {
  Mesh, VertexView,
  meshFromQuad, meshFromTriangle, meshFromCircle, meshFromPlane, meshFromCube,
  meshFromStrip, meshFromLoop, meshFromBezier, meshFromBezierStrip, meshFromBezierLoop,
} from '@carrot/engine-meshes';

Common Patterns ​

1. Creating a quad ​

ts
const quad = meshFromQuad(2, 1); // 2 units wide, 1 unit tall, centered at origin

2. Creating a triangle ​

ts
const tri = meshFromTriangle(
  { x: -1, y: 0 },
  { x: 1, y: 0 },
  { x: 0, y: 1.5 },
);

3. Creating a circle ​

ts
const circle = meshFromCircle(0.5, 64); // radius 0.5, 64 segments

4. Subdivided terrain plane ​

ts
const terrain = meshFromPlane(100, 100, 64, 64); // 100x100 units, 64x64 subdivisions

5. Cube ​

ts
const cube = meshFromCube(2); // 2-unit cube, hard edges, 24 verts

6. Road/trail from polyline ​

ts
const roadPoints = [
  { x: 0, y: 0 }, { x: 10, y: 5 }, { x: 20, y: 3 }, { x: 30, y: 8 },
];
const road = meshFromStrip(roadPoints, 2, 'Road'); // 2 units wide

7. Closed border from loop ​

ts
const borderPoints = [
  { x: 0, y: 0 }, { x: 10, y: 0 }, { x: 10, y: 10 }, { x: 0, y: 10 },
];
const border = meshFromLoop(borderPoints, 0.5, 'Border');

8. Bezier curve ribbon ​

ts
const ribbon = meshFromBezier(bezierCurve, 1.0, 64, 'CurveRibbon');

9. Per-vertex manipulation ​

ts
const plane = meshFromPlane(10, 10, 32, 32);
plane.forEachVertex((v, i) => {
  v.pz = noise(v.px, v.py);      // displace height
  v.u2 = v.px / 10;              // project into UV2
  v.v2 = v.py / 10;
});

10. Setting vertex colours ​

ts
const mesh = meshFromQuad(1);
mesh.setColorsAll({ r: 1, g: 0.5, b: 0, a: 1 }); // orange

11. Computing normals ​

ts
const mesh = meshFromPlane(10, 10, 32, 32);
// ... displace vertices ...
mesh.computeNormals(); // recalculate smooth normals from modified positions

12. Building a mesh manually ​

ts
import { FloatBuffer, IndexBuffer } from '@carrot/engine-buffers';

const mesh = new Mesh('Custom', {
  positions: new FloatBuffer([0, 0, 0, 1, 0, 0, 0.5, 1, 0]),
  normals: new FloatBuffer([0, 0, 1, 0, 0, 1, 0, 0, 1]),
  uv1: new FloatBuffer([0, 0, 1, 0, 0.5, 1]),
  indices: new IndexBuffer([0, 1, 2]),
});

13. Cloning ​

ts
const copy = mesh.clone('MyCopy');
// Independent copy - modifying copy doesn't affect original

14. Dirty tracking (renderer integration) ​

ts
if (mesh.isDirty) {
  uploadToGpu(mesh);
  mesh.clearDirty();
}

Carrot