Topology
The Topology module provides tools for analyzing connectivity and structure in meshes — adjacency structures, boundary detection, manifold analysis, connected components, and neighborhood queries.
Every function also has an async variant via tf.async for off-main-thread execution.
Adjacency Structures
Meshes lazily compute and cache adjacency structures on first access. These structures power the topology queries below.
import * as tf from "@polydera/trueform";
const mesh = tf.mesh(faces, points);
// Face membership — vertex → faces containing it
const fm = mesh.faceMembership; // OffsetBlockedBuffer
// Manifold edge link — face edge → adjacent face
const mel = mesh.manifoldEdgeLink; // NDArrayInt32 [F, 3]
// Face link — face → adjacent faces (sharing an edge)
const fl = mesh.faceLink; // OffsetBlockedBuffer
// Vertex link — vertex → connected vertices
const vl = mesh.vertexLink; // OffsetBlockedBuffer
| Property | Type | Description |
|---|---|---|
faceMembership | OffsetBlockedBuffer | Vertex → faces containing it |
manifoldEdgeLink | NDArrayInt32 [F, N] | Face edge → adjacent face index |
faceLink | OffsetBlockedBuffer | Face → adjacent faces |
vertexLink | OffsetBlockedBuffer | Vertex → connected vertices |
Manifold Edge Link Sentinel Values
| Value | Meaning |
|---|---|
>= 0 | Adjacent face index |
-1 | Boundary edge (no adjacent face) |
-2 | Non-manifold edge (3+ faces) |
-3 | Non-manifold representative |
Mesh Analysis
Closed / Open
Check if a mesh is closed (watertight) or open (has boundary edges).
tf.isClosed(mesh); // true if every edge shared by exactly 2 faces
tf.isOpen(mesh); // true if at least one boundary edge
A closed mesh is watertight — suitable for volume calculations and boolean operations.
tf.boundaryEdges(mesh) to get the actual boundary edges when isOpen returns true.Manifold / Non-Manifold
Check if a mesh is manifold (no edge shared by more than 2 faces).
tf.isManifold(mesh); // true if every edge shared by at most 2 faces
tf.isNonManifold(mesh); // true if any edge shared by 3+ faces
Non-manifold edges typically occur at T-junctions where three or more faces meet at a single edge.
tf.nonManifoldEdges(mesh) to get the actual non-manifold edges when isNonManifold returns true.Euler Characteristic
const chi = tf.eulerCharacteristic(mesh); // V - E + F
Boundary Detection
Boundary Edges
Extract boundary edges — edges belonging to only one face.
const edges = tf.boundaryEdges(mesh); // NDArrayInt32 [N, 2]
Boundary Paths
Organize boundary edges into connected loops of vertex indices.
const paths = tf.boundaryPaths(mesh); // OffsetBlockedBuffer
Non-Manifold Edges
Find edges shared by more than two faces.
const edges = tf.nonManifoldEdges(mesh); // NDArrayInt32 [N, 2]
Neighborhoods
k-Ring
Compute k-ring neighborhoods for all vertices using breadth-first traversal. The 1-ring is the immediate neighbors, the 2-ring includes neighbors of neighbors, etc.
// 2-ring neighborhoods for all vertices
const rings = tf.kRings(mesh, 2); // OffsetBlockedBuffer
// Include the seed vertex in each neighborhood
const ringsInc = tf.kRings(mesh, 2, true);
Radius-Based
Find all vertices reachable via mesh edges within a Euclidean distance.
// All vertices within radius 0.5
const neighs = tf.neighborhoods(mesh, 0.5); // OffsetBlockedBuffer
// Include seed vertex
const neighsInc = tf.neighborhoods(mesh, 0.5, true);
Connected Components
From Mesh
connectedComponents computes connected components of a mesh using a specified connectivity type.
const { labels, nComponents } = tf.connectedComponents(mesh, "edge");
console.log(`${nComponents} components`);
// labels: NDArrayInt32 [F] — component ID per face
| Type | Connectivity | Description |
|---|---|---|
"edge" | faceLink | Faces connected by any shared edge |
"manifoldEdge" | manifoldEdgeLink | Faces connected by manifold edges only |
"vertex" | vertexLink | Vertices connected by shared edges |
From Connectivity
labelConnectedComponents works on raw connectivity data — either an OffsetBlockedBuffer (variable-width) or an NDArrayInt32 (fixed-width, -1 for missing neighbors).
// Variable-width connectivity (e.g., faceLink)
const { labels, nComponents } = tf.labelConnectedComponents(mesh.faceLink);
// Fixed-width connectivity (e.g., manifoldEdgeLink)
const result = tf.labelConnectedComponents(mesh.manifoldEdgeLink);
Face Orientation
Make all faces in each connected region have consistent winding order.
const oriented = tf.consistentlyOriented(mesh);
The function uses flood-fill through manifold edges to propagate orientation. Non-manifold edges act as barriers between regions. The final orientation preserves the majority area within each region.
tf.positivelyOriented(mesh) from the Geometry module, which calls consistentlyOriented internally and then flips faces if needed.Path Finding
Connect edges into continuous vertex paths between branch points and endpoints.
const paths = tf.connectEdgesToPaths(edges); // OffsetBlockedBuffer
Edges are organized into maximal paths between endpoints and junctions (vertices with degree ≠ 2). Works on any edge collection — useful for organizing boundary segments, cut curves, and graph decomposition.
Async
All functions are available as tf.async.isClosed(), tf.async.boundaryEdges(), tf.async.connectedComponents(), etc. Signatures are identical — they return Promise<T> instead of T.
const closed = await tf.async.isClosed(mesh);
const edges = await tf.async.boundaryEdges(mesh);
const { labels, nComponents } = await tf.async.connectedComponents(mesh, "edge");
