Paths, extrusion, lathe and tube geometry

Build meshes in code from paths, profiles, tubes and merged parts.

Geometry and createShape

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.

Geometry and createShape reference
ShapeOptionsMeaning
centerCentre the bounding box on the origin. Default true.
fitScale so the largest side is this long. Default: keep the size.
shineGloss 0..1; 0.6 waxy, 0.85 glass. Default 0.
optimizetrue or OptimizeOptions: clean, weld and optionally simplify. Default false.

Build once, spawn many

React and React Native · shared engine API

Build once, spawn many
import { createShape, GameView, shapeGeometry, useWorld, type Model } from "@nayan-ui/engine";
let token: Model | undefined; // one mesh for the whole app
const 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 }} />;
}

Paths and extrusion

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.

Paths and extrusion reference
APIMeaning
moveTo / lineTo / closeStart a contour, add a line, close it.
quadraticTo / cubicToBé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 / addCircleEllipse 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.
ExtrudeOptionsdepth (0.2), fillRule ("nonzero" | "evenodd"), color tuple.

Extrude a ring and an SVG arrow

React and React Native · shared engine API

Extrude a ring and an SVG arrow
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 hole
const 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 profiles

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.

Lathe profiles reference
LatheOptionsMeaning
segmentsSteps around the axis. Default 32.
sweepRadians around; Math.PI for a half. Default 2π.
colorTuple, or ({ u, v, position, normal }) => tuple. Default white.
displace(u, v)Offset along the normal: dimples, ribs, ridges.
capColor / capRingsEnd-cap color or (r, angle) => tuple; rings, default 1 (12 for a function).
cutColor / cutStepsCut-face color or (r, y) => tuple; steps, default 12 for a function.

A painted half fruit

React and React Native · shared engine API

A painted half fruit
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 });
}

Tubes

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.

Tubes reference
TubeOptionsMeaning
radiusNumber or (t) => number. Default 0.1.
shape(around)Radius multiplier around the section, for ridges or stars. Default round.
sidesSteps around. Default 12.
smoothnessSteps between each pair of points. Default 8.
capsCap the ends where the radius isn't 0. Default true.
closedJoin the last point to the first, without caps. Default false.
color / capColorSurface color (v = t) and end-cap color. Default white.

A tapered horn

React and React Native · shared engine API

A tapered horn
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 tip
color: [0.8, 0.75, 0.6],
}));
}

Merge parts, recolor and smooth

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

A stem and a berry in one mesh
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] },
]));
}