Skip to content
Shapemetry

@huukhanhnguyen/types / geometry/surface

geometry/surface

NurbsSurface

NurbsSurface = { controlPoints: Point[][]; degreeU: number; degreeV: number; knotsU: number[]; knotsV: number[]; weights: number[][]; }

controlPoints

controlPoints: Point[][]

degreeU

degreeU: number

degreeV

degreeV: number

knotsU

knotsU: number[]

knotsV

knotsV: number[]

weights

weights: number[][]


PlaneSurface

PlaneSurface = { d: number; normal: Vector; origin?: Point; uvMax: Point2d; uvMin: Point2d; xAxis?: Vector; yAxis?: Vector; }

A plane, given as its implicit equation plus the finite window it is used over.

d

d: number

The plane's implicit constant: the surface is normal · p = d.

normal

normal: Vector

origin?

optional origin?: Point

Form origin of the UV frame. Absent on older documents: the loader then uses the foot of the world origin, which is the wrong UV origin for a translated disk.

uvMax

uvMax: Point2d

Upper-right corner of the finite rectangle in surface UV.

uvMin

uvMin: Point2d

Lower-left corner of the finite rectangle in surface UV.

xAxis?

optional xAxis?: Vector

In-plane U axis. Paired with origin / yAxis.

yAxis?

optional yAxis?: Vector

In-plane V axis. Paired with origin / xAxis.


RevolveSurface

RevolveSurface = { angle: [number, number]; axis: Axis; profile: Segment; }

A profile curve swept about an axis.

u is ALWAYS the ANGLE and v is ALWAYS the profile. The profile owns its own parameter domain.

This is the OPPOSITE of Rhino's ON_RevSurface, which puts the profile on u. Do not "fix" it back after reading that reference — the order here was verified against this kernel rather than imported: a cylinder(2, 5) reports uMin 0, uMax 2π, vMin 0, vMax 5, and a sphere(2) reports uMin 0, uMax 2π, vMin -π/2, vMax π/2. u sweeps, v runs along the profile. Every analytic carrier agrees, and operations/revolve.rs transposes the general construction to match. A face's uv curves live in that exact uv, so swapping the order would silently invalidate every uv curve on every revolve face.

Revolution earns a kind of its own where extrusion does not, and the reason is the parametrization rather than the shape: a linear extrusion's sweep parameter is a distance, which NURBS preserves exactly, so an extrusion is already exactly a NURBS surface and needs no kind. Revolution's sweep parameter is an ANGLE, and NURBS turns an angle into a Möbius reparametrization — so a NURBS carrier holds the same POINTS but no longer the same meaning, and the angle has to be stated to survive.

An arbitrary-profile revolve is EXPRESSIBLE here but only the four NAMED forms are produced — do not read this type as a general capability. Measured 2026-08-09 after the emission pass: surface_to_json writes a revolve entry for any form-tagged cylinder/cone/sphere/torus whose NURBS base is the canonical form layout (the gate is the same agreement check form recovery uses), spelled exactly as the revolveFrom* builders below spell it so the loader's revolve_form folds it back into the same named form — brep_cylinder(2,5) → {"revolve":1,"nurbs":2} (the nurbs entries are the caps' bilinear plane forms), brep_sphere(2) → {"revolve":1}, brep_box(4,4,4) → {"nurbs":6}, box − cylinder → revolve walls plus plane-form nurbs. Two cases keep the NURBS entry: a reparametrized base (an extruded-circle wall), because the rebuilt canonical carrier would move its uv curves' uv, and a form whose (xAxis, yAxis) frame is LEFT-handed about the axis (an opposite-wound hole wall), because the sign carries the winding and a revolve entry cannot state it — the loader always rebuilds yAxis = axis × xAxis. A GENERAL profile (a non-analytic sweep) has no named form to emit from and also crosses as NURBS.

A general-profile emission would need profile recognition at the writer or a general revolve variant on NurbsSurfaceForm, which has only the five NAMED forms — the 182 match arms this fold deliberately left alone.

What the fold DID buy: vRange and xAxis/yAxis stopped being separately stated fields that can disagree with the parametrization — the profile's own extent is the v range and its position is the seam, so that invalid state is now unrepresentable.

angle

angle: [number, number]

Sweep interval in radians. [0, 2 * Math.PI] is a full turn.

axis

axis: Axis

The axis swept about — the oriented infinite line of Axis. Reversing direction gives a different axis and therefore the opposite sweep sense, which is exactly what makes an axis able to carry orientation.

profile

profile: Segment

Last updated: 📖 4 min readEdit on GitHub