@huukhanhnguyen/geometry / catalog/types
catalog/types
Axis
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
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
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
Returns
boolean
intersect
intersect: (
a,b) =>Point2d
Parameters
a
b
Returns
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?
optionaltransformation?: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?
optionaltransformation?: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
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?
optionaledges?: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?
optionalmorphs?: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?
optionalskin?: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?
optionalweightsTrack?: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?
optionalmaterial?: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
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 + tyIdentity 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?
optionaledges?:MeshEdge[]
groups?
optionalgroups?:MeshGroup[]
indices
indices:
A
normals?
optionalnormals?:A
positions
positions:
A
uvs?
optionaluvs?: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?
optionaledges?: {edgeIndex?:number|null;positions?:number[];type?:string|null; }[]
edgeIndex?
optionaledgeIndex?:number|null
positions?
optionalpositions?:number[]
type?
optionaltype?:string|null
faces?
optionalfaces?: {count?:number;faceIndex?:number|null;start?:number; }[]
count?
optionalcount?:number
faceIndex?
optionalfaceIndex?:number|null
start?
optionalstart?:number
indices?
optionalindices?:number[]
normals?
optionalnormals?:number[]
positions?
optionalpositions?:number[]
uvs?
optionaluvs?: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?
optionaldeviation?: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?
optionaleulerCharacteristic?: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]; }