Skip to main content

lox/
lib.rs

1//! A polygon mesh library with different data structures and traits to abstract
2//! over those.
3//!
4//! `lox` can be used to create, generate, process, and analyze polygon meshes.
5//! This is part of the field "geometry processing", relevant for developing
6//! real-time 3D applications, simulations, 3D-printing, and much more.
7//!
8//! **Main features**:
9//!
10//! - Multiple optimized and well tested mesh data structures, including *half
11//!   edge mesh* and *directed edge mesh*.
12//! - Ability to abstract over different data structures without overhead.
13//! - *BlAzInGlY fAsT*. Ok no actually, it's [pretty fast](#speed) and I have
14//!    benchmarks to prove it.
15//! - *Prop maps* as flexible solution for storing and managing additional mesh
16//!   properties (e.g. vertex positions, face colors, ...).
17//! - Built-in algorithms (only very few right now).
18//! - **Notably missing**: IO. [Explanation](#background-and-missing-features).
19//!
20//!
21//!
22//! # Quick start
23//!
24//! Important facts:
25//!
26//! - `lox` offers [multiple data structures][core#available-data-structures] to
27//!   store mesh connectivity information. It has [traits][core#mesh-traits] to
28//!   abstract over those. You can chose the one that best fits your needs.
29//!
30//! - Data associated with mesh elements (e.g. vertex positions, face
31//!   colors, ...) are called "**props**". Props are stored separately from the
32//!   connectivity information, in [**prop maps**][map]. Yes, even the vertex
33//!   positions.
34//!
35//! - To refer to mesh elements, use [`VertexHandle`], [`FaceHandle`] and
36//!   [`EdgeHandle`].
37//!
38//! - You can find some provided algorithms in [`algo`].
39//!
40//! - You likely want to `use lox::prelude::*;` to import all important traits.
41//!
42//!
43//! # Examples
44//!
45//! Basic creation and usage of some mesh methods:
46//!
47//! ```
48//! use lox::{
49//!     core::DirectedEdgeMesh,
50//!     prelude::*,
51//! };
52//!
53//! let mut mesh = <DirectedEdgeMesh>::empty();
54//!
55//! let v0 = mesh.add_vertex();
56//! let v1 = mesh.add_vertex();
57//! let v2 = mesh.add_vertex();
58//! let v3 = mesh.add_vertex();
59//!
60//! let f0 = mesh.add_triangle([v0, v1, v2]);
61//! let f1 = mesh.add_triangle([v0, v2, v3]);
62//!
63//! let v_center = mesh.split_face(f0);
64//!
65//! for neighbor in mesh.vertices_around_vertex(v_center) {
66//!     assert!(neighbor == v0 || neighbor == v1 || neighbor == v2);
67//! }
68//!
69//! assert_eq!(mesh.num_faces(), 4);
70//! ```
71//!
72//! Creation of a mesh with vertex positions, then subdividing it, finally
73//! printing the positions of all boundary vertices.
74//!
75//! ```
76//! use lox::{
77//!     core::{HalfEdgeMesh, half_edge::TriConfig},
78//!     algo,
79//!     prelude::*,
80//! };
81//!
82//! let (mut mesh, mut positions) = lox::mesh! {
83//!     type: HalfEdgeMesh<TriConfig>,
84//!     vertices: [
85//!         v0: [0.0, 0.0, 0.0],
86//!         v1: [0.0, 1.0, 0.0],
87//!         v2: [1.0, 0.0, 0.0],
88//!         v3: [1.0, 1.0, 0.5],
89//!     ],
90//!     faces: [
91//!         [v0, v2, v1],
92//!         [v3, v1, v2],
93//!     ],
94//! };
95//!
96//! algo::subdivision::sqrt3(&mut mesh, &mut positions, 2);
97//!
98//! for v in mesh.vertices().filter(|v| v.is_boundary()) {
99//!     println!("{:?}", positions[v.handle()]);
100//! }
101//! ```
102//!
103//! Working with a non-triangular mesh:
104//!
105//! ```
106//! use lox::{
107//!     core::{HalfEdgeMesh, half_edge::PolyConfig},
108//!     prelude::*,
109//! };
110//!
111//! let mesh = lox::mesh! {
112//!     type: HalfEdgeMesh<PolyConfig>,
113//!     vertices: [v0, v1, v2, v3, v4, v5, v6],
114//!     faces: [
115//!         [v0, v3, v2, v1],
116//!         [v0, v5, v3, v3],
117//!         [v0, v1, v6, v5],
118//!         [v1, v2, v3, v4, v5, v6],
119//!     ],
120//! };
121//!
122//! for face in mesh.faces() {
123//!     for vertex in face.adjacent_vertices() {
124//!         println!("{:?} is a vertex of {:?}", vertex.handle(), face.handle());
125//!     }
126//! }
127//! ```
128//!
129//!
130//! # Speed
131//!
132//! The library was specifically designed with performance in mind, trying to
133//! beat or at least meet the performance of existing C++ libraries like
134//! [OpenMesh]. This was evaluated constantly with benchmarks as part of my
135//! [master's thesis][thesis]. See chapter 5.1 in the rendered PDF for details.
136//!
137//! The benchmarks are in need of updating (see the next section) and the
138//! benchmark results are 4 years old. But I am pretty confident that not much
139//! has changed since then.
140//!
141//!
142//! [OpenMesh]: https://www.graphics.rwth-aachen.de/software/openmesh/
143//!
144//!
145//! # Background and missing features
146//!
147//! This library started as part of [my master's thesis][thesis] which I
148//! finished in summer 2019. Thesis title: "*Designing and Implementing a
149//! Polygon Mesh Library: Can Rust Improve the Status Quo in the Domain of
150//! Geometry Processing?*" The chapter "Background: Geometry processing" might
151//! be useful to read, if you are not very familiar with the field. It also
152//! explains the data structures implemented in `lox` in some detail.
153//!
154//! Anyway, the plan was to just polish the library and then release it.
155//! Naturally, that didn't happen. I started multiple attempts over the years
156//! to finish the work and get it out the door, but until 2023 I failed. To be
157//! fair, only with Rust's stabilization of GATs, was I able to create a
158//! somewhat nice API.
159//!
160//! In 2023 I looked at the crate again and made the decision to cut down its
161//! scope for the initial release, to finally get it released at all. So I
162//! threw out all IO-related code as it added a whole lot of complexity. That's
163//! why IO is completely missing from `lox` right now.
164//!
165//! Of course, I plan on adding all that functionality back. I already did lots
166//! of API design and implementation work. Well, it was working already. But
167//! polishing the API and making it maintainable is a different beast. So let's
168//! see what the future brings. If you have interest in this, feel free to
169//! reach out, I'm happy to hear from you!
170//!
171//!
172//! [thesis]: https://github.com/LukasKalbertodt/masters-thesis
173//!
174//!
175//! # Crate features
176//!
177//! - `large-handle`: makes the crate us 64 bit integers instead of 32 bit
178//!   integers for handles.
179//!
180
181
182#![deny(missing_debug_implementations)]
183#![deny(rustdoc::broken_intra_doc_links)]
184
185
186// Reexport crates which are publicly used in this crate.
187pub extern crate lina;
188pub extern crate leer;
189pub extern crate typebool;
190
191// This is done for proc macros from `lox-macros`. These use paths starting
192// with `lox`. This makes sense for all crates using `lox` as dependency. But
193// we also want to use proc macros in this library. So we alias `crate` with
194// `lox`.
195extern crate self as lox;
196
197
198#[cfg(test)]
199#[macro_use]
200mod test_utils;
201
202pub mod algo;
203pub mod cast;
204pub mod core;
205pub mod map;
206pub mod prelude;
207pub mod util;
208
209mod refs;
210
211use std::fmt;
212
213pub use lox_macros::mesh;
214
215pub use refs::{ElementRef, EdgeRef, FaceRef, VertexRef};
216
217
218// ===========================================================================
219// ===== `Sealed` trait
220// ===========================================================================
221pub(crate) mod sealed {
222    /// A trait that cannot be implemented outside of this crate.
223    ///
224    /// This is helpful for all "real" traits in this library that only
225    /// abstract over a closed set of types. Thus, users shouldn't be able to
226    /// implement those traits for their types. Adding `Sealed` as supertrait
227    /// solves this problem.
228    pub trait Sealed {}
229}
230
231
232// ===========================================================================
233// ===== Handles and `hsize`
234// ===========================================================================
235
236/// The integer used in all [handle types][Handle].
237///
238/// The type is either `u32` or `u64` depending on whether the Cargo feature
239/// `large-handle` is enabled.
240///
241///
242/// # `hsize` and Arena Allocators
243///
244/// A handle type is just a wrapper around a simple integer, namely `hsize`.
245/// This integer is usually used as an index to an array-like thing (like a
246/// `Vec`) -- that way, we can refer to data. It is called `hsize` because it
247/// is very similar to `usize` in many regards. The *h* is for *handle*.
248///
249/// Note that handles share some properties with pointers (they refer to a
250/// thing and are actually just an integer), but have some important
251/// differences:
252///
253/// - `hsize` (and thus handles) are always 32 bit (or 64 bit with the
254///   `large-handle` feature enabled) wide, unlike pointers which might vary
255///   with target platform.
256/// - While all pointers exist in one "universe" (they all refer to the global
257///   memory view of the running process), handles often refer to many
258///   different universes. For example, it's perfectly fine to have two
259///   `FaceHandle`s with the value 0 that refer to different faces: one handle
260///   belongs to the "universe" of one mesh and the other to a different mesh.
261///   The user has to remember which universe (mesh) a handle belongs to.
262/// - Pointers are usually handled as references in Rust. With that, the borrow
263///   checker makes sure we don't do bad things with those. With handles, all
264///   those bad things can still happen, e.g. referring to an already deleted
265///   thing.
266///
267/// When looking closer at it, this system can look a lot like an "arena
268/// allocator": it's a type that reserves a big chunk of memory at its creation
269/// and serves as a memory allocator with the advantage that all its memory can
270/// be freed at once. In many cases, simple integers are used to refer to data
271/// within such an arena. There are some more "high level" allocators for this,
272/// e.g. `slab`. The easiest allocator that works like that would be `Vec`: you
273/// can add elements and can refer to them via `usize`.
274///
275/// It's a common criticism that this way of allocating things is used to
276/// sidestep the borrow checker and all the nice guarantees Rust gives us. And
277/// as such, that this pattern should be discouraged. However, there are some
278/// situations where it cannot be avoided or where this has very large
279/// advantages.
280///
281/// For example, in mesh processing, meshes with more than 2<sup>32</sup>
282/// elements are extremely rare and most of the memory in a mesh data structure
283/// is taken by references to other elements. So we can drastically reduce the
284/// overall memory consumption (and due to caching: the execution time). It
285/// would also be very hard to use this library if every element reference
286/// would be a real reference. So it does make sense for this library to use
287/// such a system.
288///
289/// Of course, we would like to avoid annoying bugs due to errors like "use
290/// after free". The crate `slotmap` has really great ideas regarding this.
291/// `lox will try out some ideas to avoid some common mistakes in the future.
292///
293/// # The size of `hsize`
294///
295/// Since `lox` is currently not generic over the integer type, we have to
296/// choose a good default. `u32` is fitting for most use cases.
297///
298/// Since the ID is always used to refer to some data, exhausting `u32` means
299/// that we have more than 2<sup>32</sup> instances of that data. If one
300/// instance is only 1 byte big, this results in 4GiB memory usage. However, in
301/// practice 1 byte is not enough to store anything useful for meshes. Usually,
302/// you at least store some kind of position (e.g. `[f32; 3]` = 12 bytes) per
303/// vertex plus the connectivity information, which is at something like 3
304/// handles per face. So the useful minimum of stored information is:
305///
306/// - 12 bytes per vertex
307/// - 12 bytes per face
308///
309/// From [here][1] we can see that in a typical triangular mesh, there are
310/// around twice as many faces as vertices. The effective size per face is thus
311/// around 18 bytes. To have more than 2<sup>32</sup> faces, the mesh would
312/// occupy around 2<sup>32</sup> ยท 18 bytes = 72 GiB of memory. In other data
313/// structures which store more connectivity information, this would be even
314/// more. There do exist rare situations (mostly in research) where one has to
315/// deal with huge meshes of that size. But again, it's rather rare.
316///
317/// On the other side are use cases where a smaller ID type, like `u16` would
318/// be sufficient. Here, one could save memory by using a smaller ID type.
319/// Making `u16` the default ID type is not OK though: 2<sup>16</sup> = 65536
320/// is not a huge number and there are many situations in which meshes have way
321/// more than 65536 elements.
322///
323/// This crate offers the Cargo feature `large-handle` which makes `hsize` 64
324/// bit large. This can be easily enabled if you actually need to deal with
325/// such huge meshes.
326///
327/// [1]: https://math.stackexchange.com/q/425968/340615
328#[allow(non_camel_case_types)]
329pub type hsize = HsizeImpl;
330
331#[cfg(not(feature = "large-handle"))]
332type HsizeImpl = u32;
333
334#[cfg(feature = "large-handle")]
335type HsizeImpl = u64;
336
337
338/// Types that can be used to refer to some data.
339///
340/// A handle is some kind of identifier which allows you to retrieve the data
341/// associated with that handle. It's a bit like the key in a hashmap. There
342/// are different kinds of handles to refer to different data. For example, a
343/// [`FaceHandle`] can be used to refer to a face.
344///
345/// Different kinds of handles (e.g. [`FaceHandle`] and [`VertexHandle`]) are
346/// identical on machine level and only serve to catch programming errors via
347/// strong typing.
348pub trait Handle: 'static + Copy + fmt::Debug + Eq + Ord {
349    /// Create a handle from the given index. The index must not be
350    /// `hsize::max_value()` as this value is reserved!
351    fn new(idx: hsize) -> Self;
352
353    /// Return the index of the current handle.
354    fn idx(&self) -> hsize;
355
356    /// Helper method to create a handle directly from an `usize`.
357    ///
358    /// If `raw` cannot be represented by `hsize`, this function either panics
359    /// or returns a nonsensical ID. In debug mode, this function is guaranteed
360    /// to panic in this case.
361    #[inline(always)]
362    fn from_usize(raw: usize) -> Self {
363        // If `usize` is bigger than `hsize`, we assert that the value is fine.
364        #[cfg(all(target_pointer_width = "64", not(feature = "large-handle")))]
365        debug_assert!(raw <= hsize::max_value() as usize);
366
367        Self::new(raw as hsize)
368    }
369
370    /// Helper method to get the ID as a usize directly from an handle.
371    ///
372    /// If the index cannot be represented by `usize`, this function either
373    /// panics or returns a nonsensical value. In debug mode, this function is
374    /// guaranteed to panic in this case. Note however, that this usually won't
375    /// happen, because `hsize` is in almost all cases smaller than or equal to
376    /// `usize`.
377    #[inline(always)]
378    fn to_usize(&self) -> usize {
379        // If `usize` is smaller than `hsize`, we assert that the value is fine.
380        #[cfg(any(
381            all(target_pointer_width = "32", feature = "large-handle"),
382            target_pointer_width = "16",
383            target_pointer_width = "8",
384        ))]
385        debug_assert!(self.idx() <= usize::max_value() as hsize);
386
387        self.idx() as usize
388    }
389}
390
391macro_rules! make_handle_type {
392    ($(#[$attr:meta])* $name:ident = $short:expr;) => {
393        $(#[$attr])*
394        #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
395        pub struct $name(hsize);
396
397        impl Handle for $name {
398            #[inline(always)]
399            fn new(id: hsize) -> Self {
400                $name(id)
401            }
402
403            #[inline(always)]
404            fn idx(&self) -> hsize {
405                self.0
406            }
407        }
408
409        impl fmt::Debug for $name {
410            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
411                write!(f, "{}", $short)?;
412                self.idx().fmt(f)
413            }
414        }
415    }
416}
417
418make_handle_type!{
419    /// A [handle][Handle] referring to a face.
420    FaceHandle = "F";
421}
422make_handle_type!{
423    /// A [handle][Handle] referring to an edge.
424    EdgeHandle = "E";
425}
426make_handle_type!{
427    /// A [handle][Handle] referring to a vertex.
428    VertexHandle = "V";
429}