all_is_cubes_base/
math.rs

1//! Mathematical utilities and decisions.
2
3use euclid::Vector3D;
4use num_traits::identities::Zero;
5pub use ordered_float::{FloatIsNan, NotNan};
6
7/// Acts as polyfill for float methods
8#[cfg(not(feature = "std"))]
9#[allow(unused_imports)]
10use num_traits::float::FloatCore as _;
11
12use crate::util::ConciseDebug;
13
14mod aab;
15pub use aab::*;
16mod axis;
17pub use axis::*;
18#[macro_use]
19mod color;
20pub use color::*;
21mod coord;
22pub use coord::*;
23mod cube;
24pub use cube::Cube;
25mod face;
26pub use face::*;
27mod grid_aab;
28pub use grid_aab::*;
29mod grid_iter;
30pub use grid_iter::*;
31mod rigid;
32pub use rigid::*;
33mod restricted_number;
34pub use restricted_number::*;
35mod matrix;
36pub use matrix::*;
37mod octant;
38pub use octant::*;
39mod rotation;
40pub use rotation::*;
41#[cfg(feature = "serde")]
42mod serde_impls;
43mod vol;
44pub use vol::*;
45
46// We make an assumption in several places that `usize` is at least 32 bits.
47// It's likely that compilation would not succeed anyway, but let's make it explicit.
48#[cfg(target_pointer_width = "16")]
49compile_error!("all-is-cubes does not support platforms with less than 32-bit `usize`");
50
51/// Allows writing a [`NotNan`] value as a constant expression (which is not currently
52/// a feature provided by the [`ordered_float`] crate itself).
53///
54/// Note that if the expression does not need to be constant, this macro may not be
55/// needed; infallible construction can be written using `NotNan::from(an_integer)`,
56/// `NotNan::zero()`, and `NotNan::one()`.
57///
58/// # Examples
59///
60/// ```
61/// # extern crate all_is_cubes_base as all_is_cubes;
62/// use all_is_cubes::{notnan, math::NotNan};
63///
64/// const X: NotNan<f32> = notnan!(1.234);
65/// ```
66///
67/// If anything other than a floating-point literal is used, the code will not compile:
68///
69/// ```compile_fail
70/// # extern crate all_is_cubes_base as all_is_cubes;
71/// # use all_is_cubes::{notnan, math::NotNan};
72/// // Not a literal; will not compile
73/// const X: NotNan<f32> = notnan!(f32::NAN);
74/// ```
75///
76/// ```compile_fail
77/// # extern crate all_is_cubes_base as all_is_cubes;
78/// # use all_is_cubes::{notnan, math::NotNan};
79/// // Not a literal; will not compile
80/// const X: NotNan<f32> = notnan!(0.0 / 0.0);
81/// ```
82///
83/// ```compile_fail
84/// # extern crate all_is_cubes_base as all_is_cubes;
85/// # use all_is_cubes::{notnan, math::NotNan};
86/// const N0N: f32 = f32::NAN;
87/// // Not a literal; will not compile
88/// const X: NotNan<f32> = notnan!(N0N);
89/// ```
90///
91/// ```compile_fail
92/// # extern crate all_is_cubes_base as all_is_cubes;
93/// # use all_is_cubes::{notnan, math::NotNan};
94/// // Not a float; will not compile
95/// const X: NotNan<char> = notnan!('a');
96/// ```
97#[doc(hidden)]
98#[macro_export] // used by all-is-cubes-content
99macro_rules! notnan {
100    ($value:literal) => {
101        match $value {
102            value => {
103                // Safety: Only literal values are allowed, which will either be a non-NaN
104                // float or (as checked below) a type mismatch.
105                let result = unsafe { $crate::math::NotNan::new_unchecked(value) };
106
107                // Ensure that the type is one which could have resulted from a float literal,
108                // by requiring type unification with a literal. This prohibits char, &str, etc.
109                let _ = if false {
110                    // Safety: Statically never NaN, and is also never executed.
111                    unsafe { $crate::math::NotNan::new_unchecked(0.0) }
112                } else {
113                    result
114                };
115
116                result
117            }
118        }
119    };
120}
121
122#[cfg(not(any(feature = "std", test)))]
123#[allow(dead_code, reason = "unclear why this warns even though it is needed")]
124/// Identical to [`num_traits::Euclid`] except that its signatures are compatible with
125/// `std` versions.
126///
127/// Note: this code is duplicated between `all-is-cubes` and
128/// `all-is-cubes-base` so that it doesn't need to be public.
129pub(crate) trait Euclid {
130    fn div_euclid(self, rhs: Self) -> Self;
131    fn rem_euclid(self, rhs: Self) -> Self;
132}
133#[cfg(not(any(feature = "std", test)))]
134impl<T: num_traits::Euclid + Copy> Euclid for T {
135    fn div_euclid(self, rhs: Self) -> Self {
136        <T as num_traits::Euclid>::div_euclid(&self, &rhs)
137    }
138    fn rem_euclid(self, rhs: Self) -> Self {
139        <T as num_traits::Euclid>::rem_euclid(&self, &rhs)
140    }
141}
142
143/// Sort exactly two items; swap them if `a > b`.
144#[inline]
145#[doc(hidden)]
146pub fn sort_two<T: PartialOrd>(a: &mut T, b: &mut T) {
147    if *a > *b {
148        core::mem::swap(a, b);
149    }
150}
151
152/// Geometric objects that can be drawn as wireframes.
153pub trait Wireframe {
154    /// Represent this object as a line drawing, or wireframe.
155    ///
156    /// The generated points should be in pairs, each pair defining a line segment.
157    /// If there are an odd number of vertices, the caller should ignore the last.
158    ///
159    /// Design note: This method accepts a destination to write to, rather than returning an
160    /// iterator, because if it did return an iterator, it would be difficult to compose in
161    /// ways like allocating a temporary `Wireframe` and delegating to that, if it borrowed
162    /// its input, and would risk composing a very large yet unnecessary iterator struct
163    /// if it owned its input.
164    /// This way, composition is simply calling further functions.
165    fn wireframe_points<E>(&self, output: &mut E)
166    where
167        E: Extend<LineVertex>;
168}
169
170/// One end of a line to be drawn.
171///
172/// These are the output of [`Wireframe::wireframe_points()`].
173#[derive(Clone, Copy, Debug, PartialEq)]
174#[expect(clippy::exhaustive_structs)]
175pub struct LineVertex {
176    /// Position of the vertex.
177    pub position: FreePoint,
178
179    /// Color in which to draw the line.
180    ///
181    /// If [`None`], a color set by the context/parent should be used instead.
182    ///
183    /// If the ends of a line are different colors, color should be interpolated along
184    /// the line.
185    pub color: Option<Rgba>,
186}
187
188impl From<FreePoint> for LineVertex {
189    #[inline]
190    fn from(position: FreePoint) -> Self {
191        Self {
192            position,
193            color: None,
194        }
195    }
196}