Build meshes in code from paths, profiles, tubes and merged parts.
Builders return Geometry: a Float32Array of 11 floats per vertex (position, normal, RGB color, UV) and a Uint32Array of triangle indices. createShape(geometry, options) registers it as a Model for any entity's mesh, and equal geometry and options reuse the same model.
Mesh ids are app-wide and never freed (about 4,080), so build each mesh once after warmUpEngine (useWorld's setup runs after it), keep it at module scope and spawn many entities from it. shapeGeometry(name, { color }) copies a built-in shape (cube, sphere, plane, cylinder, cone, capsule, torus, roundedBox) for merging.
| ShapeOptions | Meaning |
|---|---|
| center | Centre the bounding box on the origin. Default true. |
| fit | Scale so the largest side is this long. Default: keep the size. |
| shine | Gloss 0..1; 0.6 waxy, 0.85 glass. Default 0. |
| optimize | true or OptimizeOptions: clean, weld and optionally simplify. Default false. |
Build once, spawn many
React and React Native · shared engine API
import { createShape, GameView, shapeGeometry, useWorld, type Model } from "@nayan-ui/engine";let token: Model | undefined; // one mesh for the whole appconst tokenMesh = () => (token ??= createShape(shapeGeometry("roundedBox"), { fit: 1, shine: 0.25 }));export function Tokens() {const world = useWorld(50, (w) => {for (let i = 0; i < 5; i++) w.spawn({ mesh: tokenMesh(), position: [i * 1.2 - 2.4, 0.5, 0], color: "#FFB020" });});return world && <GameView source={world} camera={{ eye: [0, 4, 8], target: [0, 0, 0], fov: 1 }} style={{ flex: 1 }} />;}
Path builds 2D outlines like SVG (x right, y down, world units) with chaining methods, and Path.fromSVG parses path data (M L H V C S Q T A Z, absolute and relative). extrude(path, options) fills it into a solid facing +Z, flipped so it stands as drawn.
depth defaults to 0.2, and 0 gives a one-sided flat face. fillRule defaults to nonzero; evenodd makes every inner contour a hole.
| API | Meaning |
|---|---|
| moveTo / lineTo / close | Start a contour, add a line, close it. |
| quadraticTo / cubicTo | Bézier curves through control points to (x, y). |
| arcTo(rx, ry, rotation, largeArc, sweep, x, y) | SVG elliptical arc. |
| addPolygon(points, close = true) | Polygon through [x, y] points. |
| addRect / addRoundedRect | (x, y, width, height), plus radius or [rx, ry]. |
| addOval / addCircle | Ellipse filling a box; circle (cx, cy, radius). |
| addStar(cx, cy, outer, inner, points = 5) | Star with its first tip up. |
| addRegularPolygon(cx, cy, radius, sides) | Regular polygon with its first corner up. |
| Path.fromSVG(d) | Parses path data, not whole SVG markup. |
| transform(sx, sy = sx, dx = 0, dy = 0) | A scaled, moved copy. |
| toContours() / bounds() | Flattened points; [minX, minY, maxX, maxY] or undefined. |
| ExtrudeOptions | depth (0.2), fillRule ("nonzero" | "evenodd"), color tuple. |
Extrude a ring and an SVG arrow
React and React Native · shared engine API
import { createShape, extrude, Path } from "@nayan-ui/engine";// Call once after warmUpEngine() and keep the models.export function badgeMeshes() {const ring = new Path().addCircle(0, 0, 1).addCircle(0, 0, 0.35); // inner circle becomes a holeconst arrow = Path.fromSVG("M0 4 L6 4 L6 1 L10 6 L6 11 L6 8 L0 8 Z");return {ring: createShape(extrude(ring, { depth: 0.15, fillRule: "evenodd", color: [1, 0.75, 0.2] }), { shine: 0.5 }),arrow: createShape(extrude(arrow, { depth: 1 }), { fit: 1.5 }),};}
lathe(profile, options) spins a profile around the Y axis, like a potter's wheel: a Path (x is the radius, y down) or [radius, height] points. Open ends away from the axis are capped, and a sweep below 2π closes its cut faces too.
Make displace equal at u = 0 and 1 to avoid a seam. Color callbacks paint vertices, so more segments, capRings or cutSteps give finer patterns at a vertex cost.
| LatheOptions | Meaning |
|---|---|
| segments | Steps around the axis. Default 32. |
| sweep | Radians around; Math.PI for a half. Default 2π. |
| color | Tuple, or ({ u, v, position, normal }) => tuple. Default white. |
| displace(u, v) | Offset along the normal: dimples, ribs, ridges. |
| capColor / capRings | End-cap color or (r, angle) => tuple; rings, default 1 (12 for a function). |
| cutColor / cutSteps | Cut-face color or (r, y) => tuple; steps, default 12 for a function. |
A painted half fruit
React and React Native · shared engine API
import { createShape, lathe } from "@nayan-ui/engine";export function halfFruitMesh() {return createShape(lathe([[0, -1], [0.7, -0.7], [1, 0], [0.7, 0.7], [0, 1]], {segments: 40,sweep: Math.PI,color: ({ v }) => [1, 0.35 + v * 0.2, 0.05],displace: (u, v) => 0.015 * Math.cos(u * Math.PI * 16) * Math.sin(v * Math.PI),cutColor: (r, y) => [1, 0.6 + r * 0.3, 0.2 + Math.abs(y) * 0.1],}), { shine: 0.2 });}
tube(points, options) sweeps a cross-section along a smooth curve through 3D points: stems, horns, ropes, pipes, snakes. radius tapers when it's a function of t, 0 at the first point and 1 at the last.
| TubeOptions | Meaning |
|---|---|
| radius | Number or (t) => number. Default 0.1. |
| shape(around) | Radius multiplier around the section, for ridges or stars. Default round. |
| sides | Steps around. Default 12. |
| smoothness | Steps between each pair of points. Default 8. |
| caps | Cap the ends where the radius isn't 0. Default true. |
| closed | Join the last point to the first, without caps. Default false. |
| color / capColor | Surface color (v = t) and end-cap color. Default white. |
A tapered horn
React and React Native · shared engine API
import { createShape, tube } from "@nayan-ui/engine";export function hornMesh() {return createShape(tube([[0, 0, 0], [0.2, 0.6, 0], [0.5, 1, 0.1], [0.8, 1.1, 0.2]], {radius: (t) => 0.25 * (1 - t), // 0 at the tipcolor: [0.8, 0.75, 0.6],}));}
merge(parts) bakes parts, each with an optional position, rotation quaternion, scale and color multiplier, into one Geometry that draws in one call and moves as one entity. recolor(geometry, fn) repaints vertices in place from their SurfacePoint { u, v, position, normal }.
smoothNormals(geometry) recomputes normals in place and welds seams; run it on a sculpted surface before merging hard-edged caps. Geometry has no per-vertex alpha, so use the entity's color for transparency.
A stem and a berry in one mesh
React and React Native · shared engine API
import { createShape, merge, recolor, shapeGeometry, tube } from "@nayan-ui/engine";export function berryMesh() {const stem = tube([[0, 0, 0], [0, 0.8, 0], [0.2, 1.2, 0]], { radius: 0.08, sides: 10 });recolor(stem, ({ position }) => (position[1] > 0.6 ? [0.3, 0.8, 0.2] : [0.15, 0.4, 0.1]));return createShape(merge([{ geometry: stem },{ geometry: shapeGeometry("sphere"), position: [0.2, 1.3, 0], scale: 0.4, color: [1, 0.3, 0.2] },]));}