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.
Reverse the winding order of all faces (flips normals):
const flipped = tf.reverseWinding(mesh);
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");
