Skip to main content

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}