geop-core-topology
This page is a reference to the crate; the B-rep is taught from Lecture 17, its checks in Lecture 20.
B-rep (boundary representation) data structures, building, editing and validation for solids built from geop-core-geometry primitives.
The model
A Model<S> holds every entity in its own arena (HashMap), referenced by
typed ids (VertexId, EdgeId, CoedgeId, FaceId, ShellId,
SolidId):
Solid ──▶ Shell(s) ──▶ Face(s) [+Surface] ──▶ Coedge(s) [+Curve2] ──▶ Edge [+Curve3] ──▶ Vertex
| Entity | Holds |
|---|---|
Vertex | a 3-D point |
Edge | a Curve3 trimmed so its domain is exactly the edge, and its start and end vertex |
Coedge | one side of an edge on one face: sense, a Curve2 pcurve on the face’s surface, next/prev in its loop |
Face | a NurbSurface3D, one outer boundary and a list of holes |
Shell | a connected set of faces, and the solid it bounds — none for a sheet of faces standing on their own |
Solid | its shells, outer shell first |
A Body is either: a solid, or a sheet. A sheet bounds nothing, so its
border edges are used by one face only; what bodies may not do is overlap
within themselves — separate bodies may touch or overlap, and validation
only checks each body’s own entities against each other.
Geometry is owned by the entity it belongs to, not shared through another
arena, because nothing references it on its own. A loop is not an entity
either: a face’s boundary is a BoundaryType, either a Loop anchored at
one coedge whose next/prev cycle traces it, or a bare Vertex.
Some choices here encode invariants in the types:
- A face has exactly one outer boundary, so it is a field rather than
“index 0 of a list”.
BoundaryIndex(OuterorHole(i)) says which kind of boundary a lookup found. That distinction decides what joining two boundaries means: two points of one hole split it into two holes, two holes merge into one, a hole bridged to the outer loop stops being a hole, and only two points of the outer loop cut the face in two. - “No boundary” is not a state. A face without edges is bounded by a bare vertex.
- A coedge is backed either by an
Edge(shared with exactly one opposite coedge on the neighbouring face) or by aVertex. The latter is a degenerate segment that lets a loop run along a surface row collapsed to a point, such as a sphere’s pole. - Orientation has no flag. It lives in the surface’s parametrization, so
reverse_facemirrors the surface inuand applies the same mirror to the face’s pcurves.
Building a body
build_body builds a whole body at once from a BodySpec: vertices, edges,
and faces bounded by loops of coedges, all referring to each other by
index. It inserts nothing until BodySpec::check finds the description
consistent: every loop closes, every face is in exactly one shell, every
edge is used at most twice and then in opposite senses (exactly twice for a
solid). A description without faces is a wire. body_spec describes faces
of a model the same way, as a copy, and says where each entity came from.
Sweeps, the basic shapes and the operations that copy or rebuild faces build this way: a sweep comes in many shapes (one span or several, closed around an axis or not, with poles, holes or caps), and each is far simpler said whole than grown one entity at a time.
Edit operations
edit restructures what already exists, and is what the boolean operations
are built from:
split_edge_at_vertexsplits an edge, and every coedge tracing it, at an existing vertex. The split parameter is refined with Newton until the split point really is the vertex, rather than splitting at an arbitrary point of a search box.splice_edge_into_faceimprints an existing edge into a face. Depending on which of its end vertices already lie on the face’s boundaries, it creates a hole, a spur, divides a hole, merges two holes, absorbs a hole into the outer loop, or splits the face.merge_vertex,merge_edgemerge duplicates (vertex points are combined withunion).reverse_faceflips a face’s material side without touching the neighbouring faces.assemble_solidbuilds the result of a boolean: one new solid made of the faces to keep, deleting everything no longer reachable from them.
Containment
contains::face::face_contains classifies a (u, v) point against a face’s
trim as OnVertex, OnCoedge, Inside or Outside, by casting a ray in
parameter space. contains::shell does the same in 3-D for a point against
a shell, and solid_contains against a solid: inside an odd number of its
shells — its outer one, and no void. Both first check for coincidence with
the boundary. They then pick
ray directions from a seeded PRNG and retry until a ray crosses the boundary
only at clean interior points (a graze through a vertex or edge cannot be
counted reliably), so the parity of the crossing count gives the answer.
Validation
validation::validate runs every whole-model check and returns all
violations at once rather than stopping at the first:
numerical_accuracyruns first, since an entity that carries more uncertainty than the searches assume explains failures anywhere else;- pointer validity and two-way references (
next/prev, back-pointers); - vertices lie on their curves and surfaces, and pcurves agree with their 3-D curves;
- pcurve loops are continuous, holes lie inside the outer loop, loops wind the right way and normals point outward;
- vertices and edges are disjoint, edges and faces are consistent, and no two faces intersect away from their shared edges.
validate_fast runs only the structural checks and the single-pass geometric
ones, skipping the pairwise intersection searches, for use after every step
of a long sequence. validate_manifold checks that a shell encloses a
volume consistently. The budgets for all of this are in
ValidationParameters.