axiolid_arrangement/lib.rs
1// SPDX-License-Identifier: MPL-2.0
2#![forbid(unsafe_code)]
3#![warn(missing_docs)]
4
5//! Editable planar subdivision with persistent half-edge topology.
6//!
7//! # What this is for
8//!
9//! The `axiolid-overlay` crate answers "what is the union of these polygons" in one
10//! shot: polygons in, polygons out, no structure retained. That is the right
11//! shape for a query, and the wrong shape for editing. A caller who moves one
12//! vertex has to rebuild everything and then re-derive which output polygon
13//! corresponds to which input -- identity is lost on every call.
14//!
15//! This crate keeps the subdivision itself. Vertices, half-edges and faces
16//! have stable handles that survive edits, so "this face" means the same face
17//! before and after a vertex moves, and an edit touches only the affected
18//! neighbourhood instead of rebuilding the plane.
19//!
20//! # Deliberately neutral
21//!
22//! A planar arrangement is a general structure: it does not know about rooms,
23//! walls, storeys, or net floor area. Those are domain concepts and belong to
24//! the consumer that has the domain. This crate exposes faces, their
25//! boundaries, their areas, and their adjacencies; deciding that a particular
26//! face is a room is the caller's judgement, made with information this crate
27//! does not have.
28//!
29//! # Structure
30//!
31//! Standard doubly-connected edge list. Each edge is two opposite half-edges;
32//! each half-edge knows its origin vertex, its twin, and the next half-edge
33//! around its face. A face is identified by any half-edge on its boundary.
34//! Walking `next` traverses a face's boundary; walking `twin`/`next`
35//! traverses the edges around a vertex.
36//!
37//! The unbounded outer region is a real face ([`Arrangement::outer_face`]),
38//! not a `None`. Making it explicit removes a special case from every
39//! traversal: "the face across this edge" always has an answer.
40
41use axiolid_core::Point2;
42
43mod build;
44mod edit;
45mod entity;
46mod id;
47mod query;
48mod validate;
49
50pub use build::BuildError;
51pub use edit::EditError;
52pub use entity::{Face, HalfEdge, Vertex};
53pub use id::{FaceId, HalfEdgeId, VertexId};
54pub use validate::ArrangementHealth;
55
56/// A planar subdivision as a doubly-connected edge list.
57///
58/// Handles stay valid across edits unless the element they name is removed,
59/// which is what makes incremental editing possible at all.
60#[derive(Debug, Clone)]
61pub struct Arrangement {
62 pub(crate) vertices: Vec<Vertex>,
63 pub(crate) halfedges: Vec<HalfEdge>,
64 pub(crate) faces: Vec<Face>,
65}
66
67impl Arrangement {
68 /// An empty plane: one unbounded face, no vertices or edges.
69 #[must_use]
70 pub fn new() -> Self {
71 Self {
72 vertices: Vec::new(),
73 halfedges: Vec::new(),
74 // The unbounded face exists from the start, so `outer_face` is
75 // always a valid handle and callers never special-case an empty
76 // arrangement.
77 faces: vec![Face { boundary: None }],
78 }
79 }
80
81 /// The unbounded region surrounding every bounded face.
82 #[must_use]
83 pub const fn outer_face(&self) -> FaceId {
84 FaceId::OUTER
85 }
86
87 /// Number of vertices, including any left isolated by edits.
88 #[must_use]
89 pub fn vertex_count(&self) -> usize {
90 self.vertices.len()
91 }
92
93 /// Number of faces, including the unbounded one.
94 #[must_use]
95 pub fn face_count(&self) -> usize {
96 self.faces.len()
97 }
98
99 /// Number of half-edges; always twice the number of edges.
100 #[must_use]
101 pub fn halfedge_count(&self) -> usize {
102 self.halfedges.len()
103 }
104
105 /// Position of a vertex.
106 #[must_use]
107 pub fn position(&self, vertex: VertexId) -> Point2 {
108 self.vertices[vertex.index()].position
109 }
110}
111
112impl Default for Arrangement {
113 fn default() -> Self {
114 Self::new()
115 }
116}