Skip to content

Creating Shapes

This guide covers building Polygon[] arrays by hand, using the built-in geometry helpers from @glyphcss/core, and generating procedural meshes from math functions. All examples are self-contained — copy any block and it will run.

A tetrahedron has 4 vertices and 4 triangular faces. The simplest approach: write the vertices as a Vec3[] and the face indices by hand, then map to polygons.

import { createGlyphCamera, createGlyphScene } from "glyphcss";
import type { Polygon, Vec3 } from "@glyphcss/core";
// 4 vertices of a regular tetrahedron (circumradius ≈ 1).
const s = 1 / Math.sqrt(3);
const verts: Vec3[] = [
[ s, s, s], // 0
[-s, -s, s], // 1
[-s, s, -s], // 2
[ s, -s, -s], // 3
];
// 4 CCW-from-outside triangular faces.
const faceIndices: [number, number, number][] = [
[0, 2, 1],
[0, 1, 3],
[0, 3, 2],
[1, 2, 3],
];
const polygons: Polygon[] = faceIndices.map(([a, b, c]) => ({
vertices: [verts[a], verts[b], verts[c]],
color: "#aaffcc",
}));
// Render it.
const host = document.querySelector<HTMLElement>("#scene")!;
const camera = createGlyphCamera({ rotX: 25 });
const scene = createGlyphScene(host, { camera, mode: "solid", cols: 80, rows: 24 });
scene.add(polygons);

The resulting Polygon[] is a flat array — no scene graph, no hierarchy. Each element has vertices: Vec3[] and an optional color hex string. The rasterizer fan-triangulates N-gons internally.

@glyphcss/core ships geometry generators for all common shapes. Each returns Polygon[] — pass directly to scene.add() or to the GlyphMesh polygons prop.

4 triangular faces. size is the circumradius.

import { tetrahedronPolygons } from "@glyphcss/core";
const polys = tetrahedronPolygons({
center: [0, 0, 0],
size: 1,
color: "#ff6644",
});
// Returns Polygon[] — 4 triangular polygons.

6 square faces. size is the edge length.

import { cubePolygons } from "@glyphcss/core";
const polys = cubePolygons({
center: [0, 0, 0],
size: 1,
color: "#4488ff",
});
// Returns Polygon[] — 6 quad polygons.

8 triangular faces. size is the half-extent (distance from center to each pole).

import { octahedronPolygons } from "@glyphcss/core";
const polys = octahedronPolygons({
center: [0, 0, 0],
size: 1,
color: "#ffcc44",
});
// Returns Polygon[] — 8 triangular polygons.

12 pentagonal faces. size is the circumradius. Vertices follow the golden-ratio form; winding is CCW from outside, same as three.js DodecahedronGeometry.

import { dodecahedronPolygons } from "@glyphcss/core";
const polys = dodecahedronPolygons({
center: [0, 0, 0],
size: 1,
color: "#cc44ff",
});
// Returns Polygon[] — 12 pentagonal polygons.

20 triangular faces. size is the circumradius.

import { icosahedronPolygons } from "@glyphcss/core";
const polys = icosahedronPolygons({
center: [0, 0, 0],
size: 1,
color: "#44ffcc",
});
// Returns Polygon[] — 20 triangular polygons.

A single axis-aligned quad. axis picks the perpendicular direction (0=YZ, 1=XZ, 2=XY); size is the half-extent of the quad.

import { planePolygons } from "@glyphcss/core";
const polys = planePolygons({
axis: 1, // quad lies in the XZ plane
size: 0.4,
color: "#ffffff",
});
// Returns Polygon[] — 1 quad polygon.

A flat annulus (ring) perpendicular to a chosen axis. Made of segments quads around the circle.

import { ringPolygons } from "@glyphcss/core";
const polys = ringPolygons({
axis: 1, // ring perpendicular to Y axis
radius: 1.2,
halfThickness: 0.05,
segments: 32,
color: "#ff4488",
});
// Returns Polygon[] — 32 quad segments forming the annulus.

Three thin colored cuboids along world X (red), Y (green), Z (blue). Mirrors the three.js AxesHelper gizmo.

import { axesHelperPolygons } from "@glyphcss/core";
const polys = axesHelperPolygons({
size: 2,
thickness: 0.02,
negative: false, // only positive halves
xColor: "#ff3a3a",
yColor: "#3aff3a",
zColor: "#3a8aff",
});
// Returns Polygon[] — 18 quads (6 per axis bar × 3 axes).

Stack multiple meshes in one scene by calling scene.add() for each, or by composing multiple framework components.

import {
GlyphCamera,
GlyphScene,
GlyphMesh,
GlyphOrbitControls,
} from "@glyphcss/react";
import { cubePolygons, octahedronPolygons, axesHelperPolygons } from "@glyphcss/core";
// Build each shape once (outside render).
const cube = cubePolygons({ center: [-1.5, 0, 0], size: 0.8, color: "#4488ff" });
const octa = octahedronPolygons({ center: [0, 0, 0], size: 0.8, color: "#ffcc44" });
const axes = axesHelperPolygons({ size: 1.5 });
export function App() {
return (
<GlyphCamera rotX={25}>
<GlyphScene mode="solid" cols={120} rows={36}>
<GlyphOrbitControls />
<GlyphMesh polygons={cube} />
<GlyphMesh polygons={octa} />
<GlyphMesh polygons={axes} />
</GlyphScene>
</GlyphCamera>
);
}

Generate arbitrary surfaces with a for loop. This example builds a wavy plane by pushing two triangles per grid cell. You can also push quads (4 vertices) — the rasterizer fan-triangulates them automatically.

import { createGlyphCamera, createGlyphScene } from "glyphcss";
import type { Polygon, Vec3 } from "@glyphcss/core";
const COLS = 20;
const ROWS = 20;
const SIZE = 2;
function waveY(x: number, z: number, t = 0): number {
return Math.sin(x * 3 + t) * 0.15 + Math.cos(z * 2.5 + t) * 0.1;
}
function makeGrid(): Polygon[] {
const polygons: Polygon[] = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const x0 = (c / COLS - 0.5) * SIZE;
const x1 = ((c + 1) / COLS - 0.5) * SIZE;
const z0 = (r / ROWS - 0.5) * SIZE;
const z1 = ((r + 1) / ROWS - 0.5) * SIZE;
const v00: Vec3 = [x0, waveY(x0, z0), z0];
const v10: Vec3 = [x1, waveY(x1, z0), z0];
const v01: Vec3 = [x0, waveY(x0, z1), z1];
const v11: Vec3 = [x1, waveY(x1, z1), z1];
polygons.push({ vertices: [v00, v10, v11], color: "#33aaff" });
polygons.push({ vertices: [v00, v11, v01], color: "#2299ee" });
}
}
return polygons;
}
const host = document.querySelector<HTMLElement>("#scene")!;
const camera = createGlyphCamera({ rotX: 25 });
const scene = createGlyphScene(host, { camera, mode: "solid", cols: 100, rows: 30 });
scene.add(makeGrid());

Each cell produces two triangular polygons sharing the quad diagonal v00→v11. To animate the wave, call handle.dispose() and scene.add(makeGrid(newTime)) after each camera-end event — do not call it every frame.