subsphere/
lib.rs

1#![cfg_attr(not(doctest), doc = include_str!("../README.md"))]
2#![deny(clippy::undocumented_unsafe_blocks)]
3#![deny(missing_debug_implementations)]
4#![deny(missing_docs)]
5mod math;
6
7pub mod basetri;
8pub mod hex;
9pub mod proj;
10pub mod tri;
11pub mod util;
12
13pub use basetri::BaseTriSphere;
14pub use hex::HexSphere;
15pub use tri::TriSphere;
16
17use std::num::NonZero;
18
19/// Constructs a tessellation of the unit sphere by projecting an icosahedron onto it.
20///
21/// The tessellation can be refined by calling methods such as [`TriSphere::subdivide_edge`] or
22/// [`TriSphere::truncate`].
23pub fn icosphere() -> TriSphere {
24    TriSphere::new(
25        BaseTriSphere::Icosa,
26        Default::default(),
27        NonZero::new(1).unwrap(),
28        0,
29    )
30}
31
32/// Constructs a tessellation of the unit sphere by projecting an octahedron onto it.
33///
34/// The tessellation can be refined by calling methods such as [`TriSphere::subdivide_edge`] or
35/// [`TriSphere::truncate`].
36pub fn octasphere() -> TriSphere {
37    TriSphere::new(
38        BaseTriSphere::Octa,
39        Default::default(),
40        NonZero::new(1).unwrap(),
41        0,
42    )
43}
44
45/// Bundles the traits required to ergonomically work with tessellated spheres.
46///
47/// It is recommended to always import this module when using this crate, like so:
48///
49/// ```
50/// use subsphere::prelude::*;
51/// ```
52///
53/// The names of the bundled traits are hidden to prevent name collisions. If you need to actually
54/// refer to the traits, you must do so explicitly, e.g. [`subsphere::Sphere`](crate::Sphere).
55pub mod prelude {
56    pub use crate::Face as _;
57    pub use crate::HalfEdge as _;
58    pub use crate::Sphere as _;
59    pub use crate::Vertex as _;
60}
61
62/// Partitions the surface of the unit sphere into a set of spherical polygons ([`Face`]s).
63///
64/// There are numerous requirements for a valid [`Sphere`] implementation. Custom implementations
65/// may use [`util::validate`] to check that these requirements are met.
66pub trait Sphere {
67    /// The type of [`Face`] on this sphere.
68    type Face: Face<Vertex = Self::Vertex, HalfEdge = Self::HalfEdge>;
69
70    /// The type of [`Vertex`] on this sphere.
71    type Vertex: Vertex<Face = Self::Face, HalfEdge = Self::HalfEdge>;
72
73    /// The type of [`HalfEdge`] on this sphere.
74    type HalfEdge: HalfEdge<Face = Self::Face, Vertex = Self::Vertex>;
75
76    /// The number of faces on this sphere.
77    fn num_faces(&self) -> usize;
78
79    /// Gets the [`Face`] with the given [`index`](Face::index).
80    fn face(&self, index: usize) -> Self::Face;
81
82    /// Iterates over the faces of this sphere.
83    fn faces(&self) -> impl Iterator<Item = Self::Face>;
84
85    /// Determines which face contains the given point on the unit sphere.
86    ///
87    /// If the point is between faces (i.e. on a vertex or edge), one of the faces that contains
88    /// it inclusively will be returned.
89    fn face_at(&self, point: [f64; 3]) -> Self::Face;
90
91    /// The number of vertices on this sphere.
92    fn num_vertices(&self) -> usize;
93
94    /// Gets the [`Vertex`] with the given [`index`](Vertex::index).
95    fn vertex(&self, index: usize) -> Self::Vertex;
96
97    /// Iterates over the vertices of this sphere.
98    fn vertices(&self) -> impl Iterator<Item = Self::Vertex>;
99}
100
101/// Represents a "face" on a [`Sphere`] of a certain type.
102///
103/// These are spherical polygons bounded by [`HalfEdge`]s.
104pub trait Face: Clone + Eq {
105    /// The type of [`Vertex`] on the sphere.
106    type Vertex: Vertex<Face = Self, HalfEdge = Self::HalfEdge>;
107
108    /// The type of [`HalfEdge`] on the sphere.
109    type HalfEdge: HalfEdge<Face = Self, Vertex = Self::Vertex>;
110
111    /// The index of this vertex within the [`Sphere::faces`] list.
112    fn index(&self) -> usize;
113
114    /// The area of this face.
115    ///
116    /// This is also known as the [solid angle](https://en.wikipedia.org/wiki/Solid_angle)
117    /// subtended by the face. The sum of the areas of all faces on a sphere is `4 π`.
118    ///
119    /// For faces whose edges are all geodesics, this is equivalent to the [`util::poly_area`]
120    /// of the vertices of the face.
121    fn area(&self) -> f64;
122
123    /// The number of sides (vertices or edges) that this face has.
124    fn num_sides(&self) -> usize;
125
126    /// Gets a [`Vertex`] of this face given its index within [`Face::vertices`].
127    ///
128    /// This should be equivalent to `side(index).start()`.
129    fn vertex(&self, index: usize) -> Self::Vertex {
130        self.side(index).start()
131    }
132
133    /// Iterates over the [`Vertex`]s of this face in counter-clockwise order.
134    ///
135    /// Vertices will be returned in an order consistent with [`sides`](Face::sides) and
136    /// [`start`](HalfEdge::start). This will iterate over [`num_sides`](Face::num_sides) vertices.
137    fn vertices(&self) -> impl Iterator<Item = Self::Vertex> {
138        self.sides().map(|h| h.start())
139    }
140
141    /// Gets the [`HalfEdge`] which has the given [`index`](HalfEdge::side_index) and this face as
142    /// its [`inside`](HalfEdge::inside).
143    fn side(&self, index: usize) -> Self::HalfEdge;
144
145    /// Iterates over the [`HalfEdge`]s which have this face as their [`inside`](HalfEdge::inside).
146    ///
147    /// Edges will be returned in counter-clockwise order around the face, consistent with
148    /// [`HalfEdge::side_index`]. The iterator will return [`num_sides`](Face::num_sides) edges.
149    fn sides(&self) -> impl Iterator<Item = Self::HalfEdge> {
150        (0..self.num_sides()).map(|i| self.side(i))
151    }
152}
153
154/// Represents a vertex on a [`Sphere`].
155pub trait Vertex: Clone + Eq {
156    /// The type of [`Face`] on the sphere.
157    type Face: Face<Vertex = Self, HalfEdge = Self::HalfEdge>;
158
159    /// The type of [`HalfEdge`] on the sphere.
160    type HalfEdge: HalfEdge<Face = Self::Face, Vertex = Self>;
161
162    /// The index of this vertex within the [`Sphere::vertices`] list.
163    fn index(&self) -> usize;
164
165    /// The position of this vertex.
166    ///
167    /// This is guaranteed to be a unit-length vector.
168    fn pos(&self) -> [f64; 3];
169
170    /// The number of edges (or equivalently, [`Face`]s) that are connected to this vertex.
171    fn degree(&self) -> usize;
172
173    /// Gets a connected [`Face`] based on its index within the [`Vertex::faces`] list.
174    fn face(&self, index: usize) -> Self::Face {
175        self.outgoing(index).inside()
176    }
177
178    /// Iterates over the [`Face`]s which are connected to this vertex.
179    fn faces(&self) -> impl Iterator<Item = Self::Face> {
180        self.outgoings().map(|h| h.inside())
181    }
182
183    /// Gets an outgoing [`HalfEdge`] based on its index within the [`Vertex::outgoings`] list.
184    fn outgoing(&self, index: usize) -> Self::HalfEdge;
185
186    /// Iterates over the outgoing [`HalfEdge`]s which have this vertex as their
187    /// [`start`](HalfEdge::start).
188    ///
189    /// Edges will be returned in counter-clockwise order around the vertex.
190    fn outgoings(&self) -> impl Iterator<Item = Self::HalfEdge> {
191        (0..self.degree()).map(|i| self.outgoing(i))
192    }
193}
194
195/// Represents one "side" or direction of an edge on a [`Sphere`].
196///
197/// [Half-edge data structures](https://jerryyin.info/geometry-processing-algorithms/half-edge/)
198/// are used to conveniently represent the topology of a sphere and traverse between different
199/// elements.
200pub trait HalfEdge: Clone + Eq {
201    /// The type of [`Face`] on the sphere.
202    type Face: Face<Vertex = Self::Vertex, HalfEdge = Self>;
203
204    /// The type of [`Vertex`] on the sphere.
205    type Vertex: Vertex<Face = Self::Face, HalfEdge = Self>;
206
207    /// The index of this half-edge within the [`Face::sides`] list of its
208    /// [`inside`](HalfEdge::inside).
209    fn side_index(&self) -> usize {
210        self.inside().sides().position(|h| h == *self).unwrap()
211    }
212
213    /// The index of this half-edge within the [`Vertex::outgoings`] list of its
214    /// [`start`](HalfEdge::start).
215    fn outgoing_index(&self) -> usize {
216        self.start().outgoings().position(|h| h == *self).unwrap()
217    }
218
219    /// The length of the edge.
220    ///
221    /// For edges that are geodesics, this is equivalent to the [`util::dist`] between its
222    /// endpoints.
223    fn length(&self) -> f64;
224
225    /// The interior angle between the [previous half-edge](HalfEdge::prev) and this half-edge.
226    ///
227    /// The sum of the angles of all [outgoing](Vertex::outgoings) half-edges at a vertex should
228    /// be `2 π`.
229    ///
230    /// If both this edge, and the previous edge, are geodesics, the angle is equivalent to
231    /// [`util::angle`] where the `b` vertex is given by `self.start()`.
232    fn angle(&self) -> f64;
233
234    /// Gets the [`Face`] whose interior boundary contains this half-edge.
235    fn inside(&self) -> Self::Face;
236
237    /// Gets the [`Vertex`] at the "start" of this half-edge.
238    fn start(&self) -> Self::Vertex;
239
240    /// Gets the complementary half-edge on the opposite side of the edge.
241    ///
242    /// The returned half-edge will go in the opposite direction along the same edge. It will have
243    /// the opposite [`start`](HalfEdge::start) and [`inside`](HalfEdge::inside). This method is
244    /// its own inverse, i.e. for all half-edges `e`, `e.twin().twin() == e`.
245    fn twin(&self) -> Self;
246
247    /// Gets the half-edge which shares the [`inside`](HalfEdge::inside) face of this half-edge and
248    /// precedes it in counter-clockwise order around the face.
249    fn prev(&self) -> Self;
250
251    /// Gets the half-edge which shares the [`inside`](HalfEdge::inside) face of this half-edge and
252    /// follows it in counter-clockwise order around the face.
253    fn next(&self) -> Self;
254}