Skip to content
Shapemetry

@huukhanhnguyen/geometry / catalog/types

catalog/types

Axis

Axis = { direction: Vector; origin: Point; }

The infinite line through origin along the UNIT direction, oriented: reversing direction gives a different axis while the locus stays the same — which is what makes an axis able to carry a revolve's orientation.

THE infinite-line type of the math core (CLAUDE.md type law, 2026-08-11): one carrier per dimension, no separate Line type. NOT a bounded segment — two endpoints are LineCurve (curve.ts). Plane twin: Axis2d (core2d.ts).

Structured form used at package boundaries. The wasm kernel wire is flat [px, py, pz, dx, dy, dz] (crates/geometry/src/types.rs Axis) — convert at the door (catalog/axis.ts).

direction

direction: Vector

origin

origin: Point


Axis2d

Axis2d = { direction: Vector2d; origin: Point2d; }

The plane twin of Axis (core.ts) (OCCT gp_Ax2d precedent). Wasm kernel wire is flat [px, py, dx, dy] (crates/geometry/src/types.rs Axis2d) — convert at the door (catalog/axis2d.ts).

direction

direction: Vector2d

origin

origin: Point2d


BoundingBox

BoundingBox = { max: Point; min: Point; }

Axis-aligned box in 3D (default space). Same stem as geometry BoundingBox ops namespace (type/value dual). Plane twin: BoundingBox2d (core2d.ts).

Structured form used at package boundaries (min/max points). The wasm kernel wire remains flat [minX,minY,minZ,maxX,maxY,maxZ] on op returns — convert at the door when needed.

max

max: Point

min

min: Point


BoundingBox2d

BoundingBox2d = { maxX: number; maxY: number; minX: number; minY: number; }

Axis-aligned box in 2D. Same stem as geometry BoundingBox2d ops namespace. Plane twin of bare BoundingBox (core.ts). Component fields match clip/window call sites and the flat wasm wire [minX,minY,maxX,maxY] field order.

maxX

maxX: number

maxY

maxY: number

minX

minX: number

minY

minY: number


BrepData

BrepData = { edges: BrepEdge[]; faces: BrepFace[]; loops: BrepLoop[]; vertices: BrepVertex[]; }

Shared topology tables of one body. The geometry may contain several topological shells' faces; the partition is derived (classifyShells). Same stem as the BrepData ops namespace.

edges

edges: BrepEdge[]

faces

faces: BrepFace[]

loops

loops: BrepLoop[]

vertices

vertices: BrepVertex[]


ClipEdgeTest

ClipEdgeTest = { inside: (p) => boolean; intersect: (a, b) => Point2d; }

Sutherland–Hodgman edge test for rectangle window clipping (catalog/projection.ts's clipPolygonToWindow).

inside

inside: (p) => boolean

Parameters

p

Point2d

Returns

boolean

intersect

intersect: (a, b) => Point2d

Parameters

a

Point2d

b

Point2d

Returns

Point2d


Curve

Curve = { curves: PolyCurve[]; transformation?: Transformation; }

3D curve document — collection of ordered space chains.

Same stem as the geometry Curve ops namespace (type/value dual). Each entry of curves is one PolyCurve (closed ring or open stroke). Optional transformation is a 4×4 placement; omitted when identity.

curves

curves: PolyCurve[]

transformation?

optional transformation?: Transformation

Optional 4×4 placement; omitted when identity. Scene objects put placement on instances instead — do not set this on CurveObject.data.


Curve2d

Curve2d = { curves: PolyCurve2d[]; transformation?: Transformation; }

2D curve document — fill/stroke collection of ordered plane chains.

Same stem as the geometry Curve2d ops namespace (type/value dual). Each entry of curves is one PolyCurve2d (closed ring or open stroke). Optional transformation is a length-16 4×4 placing the planar document into world space (plane-in-world embed — not Transformation2d (core2d.ts), which is a 6-number in-plane affine). Omitted when identity.

Wire write uses tagged Segment2d segments. The Curve2d.addShape wasm door additionally accepts the UNTAGGED segment bag the text kernel's glyph outliner emits, and tags it on the way in; that bag is not a public TypeScript type.

curves

curves: PolyCurve2d[]

transformation?

optional transformation?: Transformation


Hsl

Hsl = { h: number; l: number; s: number; }

HSL channels (h in degrees, s/l 0..1) — internal to the Color.* math.

h

h: number

l

l: number

s

s: number


LineCurve2d

LineCurve2d = { end: Point2d; start: Point2d; }

The plane twin of LineCurve (curve.ts): a bounded straight segment in uv / xy.

Untagged on the contour wire, where a segment is discriminated by field presence — this is the fallback, the one with neither control, control1 nor sweepAngle.

end

end: Point2d

start

start: Point2d


LineFit

LineFit = { intercept: number; r2: number; slope: number; }

Ordinary least-squares linear regression result.

intercept

intercept: number

r2

r2: number

slope

slope: number


MeshData

MeshData = { edges?: EdgeMark[]; faces: MeshFace[]; morphs?: MorphTarget[]; skin?: SkinBinding; vertices: MeshVertex[]; weightsTrack?: string; }

Opaque n-gon polygon mesh — design topology stored as faces, triangles only at the door (render tessellation). Per-vertex claims live on each MeshVertex (NOTE the difference from shell tables: here vertex i's uv is vertices[i].uv, its normal vertices[i].normal, its skin influences vertices[i].skin). The object's kind is explicit in the evaluated value (MeshObject.type); no reader sniffs a geometry field to recognise it. Closed BY TYPE (the boundary law): only the mesh→mesh op family consumes it; boolean/BOM/tableSource/nesting/watertight filter it out by type.

Kept DISTINCT from the tessellation TriangleMesh wire type (see triangleMesh.ts) on purpose: the stream shape carries per-vertex MeshVertex.skin (indices/weights) while the tessellation expands it to skinIndices/skinWeights arrays (+ skeleton), and the tessellation adds the groups bake-time field a stream document never has. Tessellation always carries triangle indices.

edges?

optional edges?: EdgeMark[]

Sparse marked edges for interaction/shading — one entry per marked edge, array-of-structs — the same shape CurveData.edges keeps: an array of CurveEdge entries instead of a parallel curveAttributes. Runtime builds the FULL edge table from faces; this array only ever lists edges that carry a mark.

faces

faces: MeshFace[]

N-gon faces, array-of-structs — see MeshFace.

morphs?

optional morphs?: MorphTarget[]

Morph targets — see MorphTarget (offsets are per-VERTEX position deltas, flat and parallel to vertices by index — already the tessellation layout, no remap at render).

skin?

optional skin?: SkinBinding

This mesh's skeleton binding — see SkinBinding. Absent === not skinned. The per-vertex influences live on MeshVertex.skin; this holds only what is true of the whole document (which skeleton, and the bind pose it was attached at), which is why a writer reads the name and the inverse-bind matrices here and the weights per vertex.

vertices

vertices: MeshVertex[]

Vertices — see MeshVertex.

weightsTrack?

optional weightsTrack?: string

Track.key reference (2026-08-14) — this mesh's OWN weights channel. One track covers every entry in morphs at once: its values[i].length must equal morphs.length, in the SAME order — glTF packs one node's whole weights array into a single channel (confirmed against the spec: node.weights count MUST match the referenced mesh's morph target count), so one track already covers it — no per-target track to re-merge at export. Consumer-holds-the- reference, same as BoneEntity's *Track fields. Absent = this mesh's morph weights never animate (still usable as a static pose via each target's own authored weight elsewhere, out of scope here).


MeshEdge

MeshEdge = { positions: number[]; type: "smooth" | "boundary" | "unset"; }

One display edge: a polyline along a topology edge of the mesh.

positions

positions: number[]

Flat [x, y, z, ...] polyline along the edge.

type

type: "smooth" | "boundary" | "unset"


MeshGroup

MeshGroup = { count: number; material?: string; start: number; }

One material range in the mesh (contiguous indices slice).

count

count: number

material?

optional material?: string

start

start: number


OffsetEndType

OffsetEndType = "round" | "square" | "butt"

Offset line cap — how the open ends of an offset chain are closed.


OffsetJoinType

OffsetJoinType = "round" | "miter" | "square"

Offset join — how the joins between offset segments are resolved.


Plane

Plane = { normal: Vector; point: Point; }

An unbounded plane through point with the given normal.

normal

normal: Vector

point

point: Point


Point

Point = [number, number, number]

3D point (default space). Same stem as geometry Point namespace.


Point2d

Point2d = [number, number]

2D point. Plane twin of 3D bare Point (core.ts).


PolyCurve

PolyCurve = { segments: Segment[]; }

Ordered chain of 3D curve segments (endpoint-welded).

An ordered chain only — segments meet end to start, never branch. Closest OCCT idea: CompositeCurve / a polyline of heterogeneous carriers. Wire: { segments: Segment[] }.

This is the geometry CurveData carries. Anything the user draws (one line, one arc, a polyline, a heterogeneous chain) is a PolyCurve — a single segment is a valid one-element chain. Bare NurbsCurve / bare LineCurve are kind carriers inside segments, not document payloads.

segments

segments: Segment[]


PolyCurve2d

PolyCurve2d = { segments: Segment2d[]; }

Ordered chain of 2D curve segments (endpoint-welded).

Plane twin of PolyCurve (curve.ts). Wire: { segments: Segment2d[] }. One ring / open stroke is one polycurve; multi-ring fill is Curve2d.

segments

segments: Segment2d[]


PrincipalView

PrincipalView = "top" | "front" | "right" | "left" | "back" | "bottom"

The six axis-aligned standard views.


Quaternion

Quaternion = [number, number, number, number]

Unit rotation [x, y, z, w].


Rgb

Rgb = { b: number; g: number; r: number; }

RGB channels 0..255 — internal to the Color.* math, never on a wire.

b

b: number

g

g: number

r

r: number


Surface

Surface = { type: "plane"; } & PlaneSurface | { type: "revolve"; } & RevolveSurface | { formAbsent?: boolean; type: "nurbs"; uPeriodic?: boolean; vPeriodic?: boolean; } & NurbsSurface

The document's surface carrier: the three kinds of the PlaneSurface / RevolveSurface / NurbsSurface union (geometry/surface.ts) wearing their type tags, plus carrier-only fields on the nurbs arm that are document concerns rather than geometry.


Transformation

Transformation = number[]

A flat 4×4 affine matrix, 16 numbers.

Major order depends on the USE and the two are NOT interchangeable, so every field carrying this type states its own convention: placement transforms are COLUMN-major (Three.Matrix4 / SketchUp Geom::Transformation, translation at 12,13,14), while a face's uvMatrix is ROW-major (plane→world frame, origin at 3,7,11).


Transformation2d

Transformation2d = [number, number, number, number, number, number]

A flat 2D affine transform, 6 numbers [a, b, c, d, tx, ty] — byte-for-byte the kernel's Matrix3x2 = [f64; 6] (crates/geometry/src/types.rs), so this type crosses the wasm boundary with NO conversion. It is also the CSS/SVG matrix(a, b, c, d, tx, ty) convention.

Layout is column pairs: the linear part's columns are (a, b) and (c, d), and (tx, ty) is the translation. Points are COLUMN vectors:

x′ = a·x + c·y + tx
y′ = b·x + d·y + ty

Identity is [1, 0, 0, 1, 0, 0]. Composition follows column-vector order: the kernel's transform2d_multiply(A, B) applies B first, then A (the result maps p → A·(B·p)). The linear part's determinant is a·d − b·c; |det| = 0 is degenerate and has no inverse.

Unlike Transformation (core.ts, whose major order is stated per use), the layout here is FIXED — there is exactly one 2D convention in the repo because the kernel's wire encoding fixes it. A hatch's lattice basis is NOT this object — it is a pair of spanning vectors ([Vector2d, Vector2d], a Bravais-lattice generator); the placement transform is DERIVED from it as translate(o) · scale(s) · rotate(θ) · B.


TriangleMesh

TriangleMesh<A> = { edges?: MeshEdge[]; groups?: MeshGroup[]; indices: A; normals?: A; positions: A; uvs?: A; }

The canonical triangle-mesh payload and the ONE definition of an indexed triangulation (law: one mathematical object, one definition) — what BrepData.tessellate / BrepData.tessellate return (owned JS arrays), what every mesh-producing door speaks, and what projection/section clipping, scan import and the weld passes' shared-vertex intermediate carry.

positions + indices are the mesh; every other channel is optional because the producer decides which it has. BrepData.tessellate ALWAYS fills normals, uvs and edges (empty arrays at worst); a clipped or welded triangulation carries positions and indices alone.

Tessellation is a ONE-WAY arrow: the mesh keeps no pointer back to the topology that produced it.

The Group is parameterized because it is instantiated two ways: the default plain number[] is the JSON/display form; TriangleMesh<Float64Array> is the packed-wire form whose channels are zero-copy subarray slices of the kernel's Float64Array wire (a number[] fold there would copy a 500k-triangle scan).

Type Parameters

A

A extends Float64Array | number[] = number[]

edges?

optional edges?: MeshEdge[]

groups?

optional groups?: MeshGroup[]

indices

indices: A

normals?

optional normals?: A

positions

positions: A

uvs?

optional uvs?: A


TriangleMeshDocument

TriangleMeshDocument = { edges?: { edgeIndex?: number | null; positions?: number[]; type?: string | null; }[]; faces?: { count?: number; faceIndex?: number | null; start?: number; }[]; indices?: number[]; normals?: number[]; positions?: number[]; uvs?: number[]; }

Wire shape of the one-shot tess_solid / tess_shell / tess_face / mesh_tessellate_json JSON document (Rust TriangleMesh serde, camelCase). Optional fields mirror serde defaults — catalog/mesh.ts's meshFromJson parses then normalizes into TriangleMesh; that is the only decode point.

edges?

optional edges?: { edgeIndex?: number | null; positions?: number[]; type?: string | null; }[]

edgeIndex?

optional edgeIndex?: number | null

positions?

optional positions?: number[]

type?

optional type?: string | null

faces?

optional faces?: { count?: number; faceIndex?: number | null; start?: number; }[]

count?

optional count?: number

faceIndex?

optional faceIndex?: number | null

start?

optional start?: number

indices?

optional indices?: number[]

normals?

optional normals?: number[]

positions?

optional positions?: number[]

uvs?

optional uvs?: number[]


ValidationIssue

ValidationIssue = { checkId: string; description: string; deviation?: number; entity: { index: number; type: string; }; severity: "error" | "warning" | "info"; }

The one-shot BrepData.validate report (Rust brep_validate JSON — the authority is crates/geometry/src/brep/heal_check/wasm.rs, whose doc spells this exact shape).

checkId

checkId: string

description

description: string

deviation?

optional deviation?: number

entity

entity: { index: number; type: string; }

index

index: number

type

type: string

severity

severity: "error" | "warning" | "info"


ValidationReport

ValidationReport = { eulerCharacteristic?: number; issues: ValidationIssue[]; isValid: boolean; }

eulerCharacteristic?

optional eulerCharacteristic?: number

V − E + F — present only when the validated document is a solid.

issues

issues: ValidationIssue[]

isValid

isValid: boolean


Vector

Vector = [number, number, number]

Free 3D vector (direction / axis). Same layout as Point; field role differs.


Vector2d

Vector2d = [number, number]

Free 2D vector (direction). Same layout as Point2d; field role differs.


ViewTriangle

ViewTriangle = { projected: [Point2d, Point2d, Point2d]; world: [Point, Point, Point]; }

projected

projected: [Point2d, Point2d, Point2d]

world

world: [Point, Point, Point]

Last updated: 📖 14 min readEdit on GitHub