pub struct Model<S: Scalar> {
pub vertices: HashMap<VertexId, Vertex<S>>,
pub edges: HashMap<EdgeId, Edge<S>>,
pub coedges: HashMap<CoedgeId, Coedge<S>>,
pub faces: HashMap<FaceId, Face<S>>,
pub shells: HashMap<ShellId, Shell>,
pub solids: HashMap<SolidId, Solid>,
/* private fields */
}Expand description
The top-level boundary-representation model.
Vertex, Edge, Coedge, Face, Shell and Solid are the entities
genuinely shared by reference (an edge by its two coedges via opposite,
a vertex by every incident edge, and so on) — these live here in arenas
and are referenced by stable typed IDs. Their geometry (a Curve3 per
Edge, a Curve2 pcurve per Coedge, a NurbSurface per Face) is
owned 1:1 by that entity directly rather than through another arena, since
nothing ever needs to reference it independently. A loop has no entity of
its own: each entry of Face::boundaries just anchors a CoedgeId whose
next/prev cycle traces the whole loop.
Hierarchy (each arrow means “references one or more”):
Solid ──▶ Shell(s) ──▶ Face(s) [+Surface] ──▶ Coedge(s) [+Curve2] ──▶ Edge [+Curve3] ──▶ VertexV - E + F - L = 2 * (S - G), E = 2C for a single-shell solid with genus G,
where V = #vertices, E = #edges, F = #faces, L = #hole loops (i.e.
Σ (face.boundaries.len() - 1), not counting each face’s mandatory outer loop),
S = #shells, and G = genus. Euler’s formula generalizes to multiple
shells and/or genus > 0.
Creation (insert_*), lookup (get_*), and iteration (iterate_*)
methods each live in their own private submodule (model::create,
model::get, model::iterate) — all still just plain inherent Model
methods from the outside.
Fields§
§vertices: HashMap<VertexId, Vertex<S>>§edges: HashMap<EdgeId, Edge<S>>§coedges: HashMap<CoedgeId, Coedge<S>>§faces: HashMap<FaceId, Face<S>>§shells: HashMap<ShellId, Shell>§solids: HashMap<SolidId, Solid>Implementations§
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
Sourcepub fn assemble_solid(
&mut self,
consumed: &[SolidId],
keep: &[FaceId],
) -> GeopResult<Option<SolidId>>
pub fn assemble_solid( &mut self, consumed: &[SolidId], keep: &[FaceId], ) -> GeopResult<Option<SolidId>>
Replace the solids consumed by one new solid of a single shell made
of keep, which must be faces of those solids. Every other face they
owned is deleted, and so is everything no face reaches any more.
What a boolean does last, once it has decided which faces survive.
Ok(None) (and no solid) when keep is empty: the result is empty,
which is an answer rather than a failure.
Deletion is by reachability rather than by tracking what was split or
re-homed on the way here, so no dangling id can be left behind — and
it only ever deletes from what consumed owned: faces of any other
solid in the model are none of this operation’s business.
Sourcepub fn merge_solids(&mut self, into: SolidId, from: SolidId) -> GeopResult<()>
pub fn merge_solids(&mut self, into: SolidId, from: SolidId) -> GeopResult<()>
Move every shell of from into into, deleting from: one solid of
both bodies. Only valid for bodies that don’t touch — nothing is
intersected, so two overlapping ones would make a solid whose shells
cross; combining those is a boolean union’s job.
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
Sourcepub fn merge_edge(
&mut self,
edge_into_id: EdgeId,
edge_deleted_id: EdgeId,
reversed: bool,
) -> GeopResult<()>
pub fn merge_edge( &mut self, edge_into_id: EdgeId, edge_deleted_id: EdgeId, reversed: bool, ) -> GeopResult<()>
Merges edge_deleted_id into edge_into_id: repoints every coedge
tracing the deleted edge to the surviving one, then drops the
now-unreferenced edge. reversed says whether the deleted edge ran
start<->end the other way round relative to the surviving one — if
so, every repointed coedge’s sense is flipped so it keeps tracing
the same physical direction it always did.
TODO: edge_into_id‘s own curve currently stays exactly as it was —
it should instead be widened (unioned) to certainly contain the
deleted edge’s curve too, so the kept edge’s geometry honestly
reflects both original edges’ combined tolerance instead of silently
favoring whichever one happened to survive.
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
Sourcepub fn merge_vertex(
&mut self,
vertex_into_id: VertexId,
vertex_deleted_id: VertexId,
) -> GeopResult<()>
pub fn merge_vertex( &mut self, vertex_into_id: VertexId, vertex_deleted_id: VertexId, ) -> GeopResult<()>
Merges vertex_deleted_id into vertex_into_id: unions their points
into vertex_into_id, repoints every edge/coedge/boundary reference
from vertex_deleted_id to vertex_into_id, then drops the now
-unreferenced vertex.
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
Sourcepub fn reverse_face(&mut self, face_id: FaceId) -> GeopResult<()>
pub fn reverse_face(&mut self, face_id: FaceId) -> GeopResult<()>
Turn face_id’s material side around, so its normal points the other
way.
Orientation in this kernel lives in the surface’s parametrization —
there is no flag on a face — so flipping it means mirroring the
surface’s u (see geop_core_geometry::nurb_surface::NurbSurface::reverse_u)
and applying the identical mirror to every pcurve drawn on it. The
domain is unchanged by the mirror, so the trim loops still describe
the same region of the same surface; only Su x Sv reverses.
Deliberately touches nothing but this face’s surface and its own pcurves. The 3-D curves, the edges, the vertices and every coedge on the other side of those edges are untouched, which is what lets a boolean flip one operand’s faces while they stay glued to the other’s along shared edges.
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
Sourcepub fn splice_edge_into_face(
&mut self,
edge_id: EdgeId,
face_id: FaceId,
max_nodes: usize,
min_subdivision_size: S,
) -> GeopResult<Option<FaceId>>
pub fn splice_edge_into_face( &mut self, edge_id: EdgeId, face_id: FaceId, max_nodes: usize, min_subdivision_size: S, ) -> GeopResult<Option<FaceId>>
Splice an already-existing edge_id into face_id’s boundary
structure, as a forward/reversed coedge pair whose pcurves it fits onto
the face’s own surface.
Unlike the mer/mekr Euler operators — which mint their own brand-new
edge, and so can’t be used for an edge that already exists because two
faces are meant to share it (the whole point of imprinting an
intersection curve) — this rewires loops around an edge it’s handed.
What it does depends on how much of the edge’s own topology the face already knows about, which is the only thing that determines what a correct result even looks like:
| edge’s start/end vertex on this face’s boundary | result |
|---|---|
| neither | a new self-contained ring floating inside the face — a hole |
| exactly one | a spur (out along the edge and back) inserted into that vertex’s own loop |
| both, same hole | that hole is divided into two holes |
| both, two different holes | those holes are merged into one |
| both, a hole and the outer loop | the hole is absorbed into the outer loop |
| both, the outer loop | the face is split into two faces |
Splitting the face, and dividing a hole (whose material side becomes
a face of its own), are the cases that create a face; its id is
returned, None otherwise. The face split is the only one that has to
reclassify the old face’s holes — each now lies inside exactly one of
the two halves (see [split_face]). The middle cases are the same
restructurings mer/mekr perform; a spur changes no connectivity at
all, being “wire” topology rather than a real trim boundary.
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
Sourcepub fn split_edge_at_vertex(
&mut self,
edge_id: EdgeId,
edge_t: S,
vertex_id: VertexId,
max_nodes: usize,
min_subdivision_size: S,
) -> GeopResult<EdgeId>
pub fn split_edge_at_vertex( &mut self, edge_id: EdgeId, edge_t: S, vertex_id: VertexId, max_nodes: usize, min_subdivision_size: S, ) -> GeopResult<EdgeId>
Splits edge_id at edge_t into two edges joined at the pre-existing
vertex_id (which must already coincide with edge.curve.evaluate(edge_t)),
and splits every coedge tracing edge_id to match. edge_id itself
keeps the first (start -> vertex) segment; the new edge holding the
second (vertex -> end) segment, in the same direction, is returned.
max_nodes/min_subdivision_size bound the BFS search used to locate
each coedge’s own pcurve parameter for the split (see
curve_could_contain).
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
Sourcepub fn add_vertex_coedge(
self: &mut Model<S>,
after: CoedgeId,
vertex: VertexId,
pcurve: NurbCurve2D<S>,
) -> GeopResult<CoedgeId>
pub fn add_vertex_coedge( self: &mut Model<S>, after: CoedgeId, vertex: VertexId, pcurve: NurbCurve2D<S>, ) -> GeopResult<CoedgeId>
Splice a single degenerate coedge — backed directly by vertex (see
CoedgeGeometry::Vertex), not a real edge — into after’s own
loop, right after it. pcurve must both start and end at vertex’s
own position, as mapped through after’s face’s surface (it’s
meant to sweep some parameter-space range while sitting at that one
3-D point the whole way, e.g. tracing a pole row’s full angular
span).
Unlike every other euler operator here, this isn’t one: it doesn’t
touch V, E, or F (vertex already exists, and no edge is
created), so there’s no Euler–Poincaré invariant for it to need to
preserve — it’s pure loop bookkeeping, letting a face’s own
boundary legitimately pass through an already-shared vertex without
requiring a real (and, for a single-face-only detour, otherwise
unpaired) edge to carry a pcurve.
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
Sourcepub fn kef(
self: &mut Model<S>,
edge: EdgeId,
killed_face: FaceId,
) -> GeopResult<()>
pub fn kef( self: &mut Model<S>, edge: EdgeId, killed_face: FaceId, ) -> GeopResult<()>
Kill the edge added by mef, merging killed_face back into the
face on the other side of edge: the absorbed face’s ring is
spliced back into the surviving face’s ring (undoing the split),
and killed_face is removed from the model and from its shell’s
face list. edge’s two coedges must currently lie on two
different faces, one of which is killed_face.
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
Sourcepub fn kill_vertex_coedge(
self: &mut Model<S>,
coedge: CoedgeId,
) -> GeopResult<()>
pub fn kill_vertex_coedge( self: &mut Model<S>, coedge: CoedgeId, ) -> GeopResult<()>
Undo Model::add_vertex_coedge: splice coedge back out of its
loop. coedge must be Vertex-backed (i.e. one add_vertex_coedge
itself returned) — nothing is removed from V, E, or F here
either, matching add_vertex_coedge’s own no-op effect on them.
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
pub fn mef( self: &mut Model<S>, coedge1: CoedgeId, coedge2: CoedgeId, curve: NurbCurve3D<S>, pcurve: NurbCurve2D<S>, pcurve_reversed: NurbCurve2D<S>, new_surface: NurbSurface3D<S>, ) -> GeopResult<(EdgeId, FaceId, CoedgeId, CoedgeId)>
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
pub fn mekr( self: &mut Model<S>, coedge1: CoedgeId, coedge2: CoedgeId, curve: NurbCurve3D<S>, pcurve: NurbCurve2D<S>, ) -> GeopResult<(EdgeId, CoedgeId, CoedgeId)>
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
pub fn mer( self: &mut Model<S>, coedge1: CoedgeId, coedge2: CoedgeId, curve: NurbCurve3D<S>, pcurve: NurbCurve2D<S>, pcurve_reversed: NurbCurve2D<S>, existing_face_id: FaceId, ) -> GeopResult<(EdgeId, CoedgeId, CoedgeId)>
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
pub fn mve( self: &mut Model<S>, coedge: CoedgeId, curve: NurbCurve3D<S>, pcurve: NurbCurve2D<S>, pcurve_reversed: NurbCurve2D<S>, p: Vector3<S>, ) -> GeopResult<(VertexId, CoedgeId, CoedgeId, EdgeId)>
pub fn mve_from_vertex( self: &mut Model<S>, face_id: FaceId, vertex: VertexId, curve: NurbCurve3D<S>, pcurve: NurbCurve2D<S>, pcurve_reversed: NurbCurve2D<S>, p: Vector3<S>, ) -> GeopResult<(VertexId, CoedgeId, CoedgeId, EdgeId)>
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
pub fn replace_face( self: &mut Model<S>, face_id: FaceId, surface: NurbSurface3D<S>, ) -> GeopResult<()>
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
pub fn replace_pcurve( self: &mut Model<S>, coedge_id: CoedgeId, pcurve: NurbCurve2D<S>, ) -> GeopResult<()>
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
pub fn insert_vertex(&mut self, v: Vertex<S>) -> VertexId
pub fn insert_edge(&mut self, e: Edge<S>) -> EdgeId
pub fn insert_coedge(&mut self, c: Coedge<S>) -> CoedgeId
pub fn insert_face(&mut self, f: Face<S>) -> FaceId
pub fn insert_shell(&mut self, s: Shell) -> ShellId
pub fn insert_solid(&mut self, s: Solid) -> SolidId
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
pub fn get_vertex(&self, id: VertexId) -> GeopResult<&Vertex<S>>
pub fn get_vertex_mut(&mut self, id: VertexId) -> GeopResult<&mut Vertex<S>>
pub fn get_edge(&self, id: EdgeId) -> GeopResult<&Edge<S>>
pub fn get_edge_mut(&mut self, id: EdgeId) -> GeopResult<&mut Edge<S>>
pub fn get_coedge(&self, id: CoedgeId) -> GeopResult<&Coedge<S>>
pub fn get_coedge_mut(&mut self, id: CoedgeId) -> GeopResult<&mut Coedge<S>>
pub fn get_face(&self, id: FaceId) -> GeopResult<&Face<S>>
pub fn get_face_mut(&mut self, id: FaceId) -> GeopResult<&mut Face<S>>
pub fn get_shell(&self, id: ShellId) -> GeopResult<&Shell>
pub fn get_shell_mut(&mut self, id: ShellId) -> GeopResult<&mut Shell>
pub fn get_solid(&self, id: SolidId) -> GeopResult<&Solid>
pub fn get_solid_mut(&mut self, id: SolidId) -> GeopResult<&mut Solid>
pub fn coedge_start_vertex_id(&self, coedge: CoedgeId) -> GeopResult<VertexId>
pub fn coedge_start_vertex(&self, coedge: CoedgeId) -> GeopResult<&Vertex<S>>
pub fn coedge_end_vertex_id(&self, coedge: CoedgeId) -> GeopResult<VertexId>
pub fn coedge_end_vertex(&self, coedge: CoedgeId) -> GeopResult<&Vertex<S>>
Source§impl<S: Scalar> Model<S>
impl<S: Scalar> Model<S>
Sourcepub fn coedges_of_edge(&self, edge: EdgeId) -> Vec<CoedgeId>
pub fn coedges_of_edge(&self, edge: EdgeId) -> Vec<CoedgeId>
The (unordered) coedges referencing a given edge. An edge shared by a single manifold face pair has exactly two.
Sourcepub fn iterate_loop_coedges(
&self,
anchor: CoedgeId,
) -> impl Iterator<Item = CoedgeId> + '_
pub fn iterate_loop_coedges( &self, anchor: CoedgeId, ) -> impl Iterator<Item = CoedgeId> + '_
Every coedge of the loop anchored at anchor, in traversal order.
Sourcepub fn iterate_face_coedges(
&self,
face_id: FaceId,
) -> impl Iterator<Item = CoedgeId> + '_
pub fn iterate_face_coedges( &self, face_id: FaceId, ) -> impl Iterator<Item = CoedgeId> + '_
Every coedge of every boundary loop of face_id (its outer loop and
any holes).
Sourcepub fn iter_solid_vertices(
&self,
solid_id: SolidId,
) -> GeopResult<impl Iterator<Item = VertexId> + '_>
pub fn iter_solid_vertices( &self, solid_id: SolidId, ) -> GeopResult<impl Iterator<Item = VertexId> + '_>
The distinct vertices referenced by solid_id’s faces, as an
iterator borrowing self — see [SolidVertices]’s own doc comment.
Sourcepub fn iter_solid_edges(
&self,
solid_id: SolidId,
) -> GeopResult<impl Iterator<Item = EdgeId> + '_>
pub fn iter_solid_edges( &self, solid_id: SolidId, ) -> GeopResult<impl Iterator<Item = EdgeId> + '_>
The distinct edges referenced by solid_id’s faces, as an iterator
borrowing self — see [SolidVertices]’s own doc comment.
Sourcepub fn find_boundary_containing(
&self,
face_id: FaceId,
coedge: CoedgeId,
) -> GeopResult<BoundaryIndex>
pub fn find_boundary_containing( &self, face_id: FaceId, coedge: CoedgeId, ) -> GeopResult<BoundaryIndex>
Which of face_id’s boundaries contains coedge — its outer loop or
one of its holes — found by traversing each Loop boundary’s .next
chain. A face’s boundaries are disjoint loops, so at most one can
contain any given coedge.
Returns BoundaryIndex rather than a bare position, because callers
invariably need to know whether they found the outer loop: joining two
holes, joining a hole to the outer loop, and joining two points of the
outer loop are three different restructurings (see
Model::splice_edge_into_face).
Sourcepub fn remove_boundary(
&mut self,
face_id: FaceId,
index: BoundaryIndex,
) -> GeopResult<()>
pub fn remove_boundary( &mut self, face_id: FaceId, index: BoundaryIndex, ) -> GeopResult<()>
Drop the hole index names from face_id.
Errors on BoundaryIndex::Outer: a face without an outer boundary
is not a face. An operation that genuinely consumes a face’s outer
loop is deleting the face, and must say so (see kef) rather than
leaving one behind with nothing bounding it.
Sourcepub fn solid_faces(&self, solid_id: SolidId) -> GeopResult<Vec<FaceId>>
pub fn solid_faces(&self, solid_id: SolidId) -> GeopResult<Vec<FaceId>>
Every FaceId across every shell of solid_id.