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}