sdfu/
util.rs

1//! Other random utilities that are helpful when using SDFs in computer graphics applications,
2//! such as estimating normals.
3use super::*;
4use std::ops::*;
5
6#[cfg(feature = "ultraviolet")]
7use ultraviolet::f32x4;
8
9pub type EstimateNormalDefault<T, V, S> =
10    EstimateNormal<T, V, S, CentralDifferenceEstimator<T, V, <V as Vec<T>>::Dimension>>;
11
12pub type EstimateNormalFast<T, V, S> = EstimateNormal<T, V, S, TetrahedralEstimator<T, V>>;
13
14/// Estimates the normal of an `sdf` using an `estimator`.
15///
16/// It is reasonable by default to choose a `CentralDifferenceEstimator`,
17/// which provides an estimator that works for both 2D and 3D SDFs. See the documentation
18/// of `NormalEstimator` for more information.
19pub struct EstimateNormal<T, V, S, E> {
20    pub sdf: S,
21    pub estimator: E,
22    _pd: std::marker::PhantomData<(T, V)>,
23}
24
25impl<T, V, S, E> EstimateNormal<T, V, S, E>
26where
27    E: NormalEstimator<T, V>,
28    S: SDF<T, V>,
29    V: Vec<T>,
30{
31    /// Creates a new `EstimateNormal` with an SDF and a provided estimator.
32    pub fn new(sdf: S, estimator: E) -> Self {
33        EstimateNormal {
34            sdf,
35            estimator,
36            _pd: std::marker::PhantomData,
37        }
38    }
39
40    /// Estimates the normal of the owned SDF at point p.
41    #[inline]
42    pub fn normal_at(&self, p: V) -> V {
43        self.estimator.estimate_normal(self.sdf, p)
44    }
45}
46
47/// `NormalEstimator`s provide a way to estimate the normal of the SDF `sdf` at point `p`.
48pub trait NormalEstimator<T, V: Vec<T>> {
49    fn estimate_normal<S: SDF<T, V>>(&self, sdf: S, p: V) -> V;
50}
51
52/// Estimates the normal of an SDF by estimating the gradient of the SDF.
53///
54/// The gradient is estimated by taking two samples of the SDF in each dimension,
55/// one slightly above (by `eps` distance) the point in question and one slightly below it and taking their
56/// difference, hence the 'central difference'. This estimation is relatively robust and accurate, and can
57/// work in both two and three dimensions, but is also relatively slow since it takes 6 samples of the SDF.
58/// See the `TetrahedralEstimator` for an estimator which is 3d only and slightly less robust/accurate but
59/// also slightly faster.
60///
61/// See [this article](http://iquilezles.org/www/articles/normalsSDF/normalsSDF.htm)
62/// for more.
63pub struct CentralDifferenceEstimator<T, V, D> {
64    pub eps: T,
65    _pdv: std::marker::PhantomData<V>,
66    _pdd: std::marker::PhantomData<D>,
67}
68
69impl<T, V: Vec<T>> CentralDifferenceEstimator<T, V, <V as Vec<T>>::Dimension> {
70    /// Creates a `CentralDifferenceEstimator` with a given epsilon value.
71    pub fn new(eps: T) -> Self {
72        CentralDifferenceEstimator {
73            eps,
74            _pdv: std::marker::PhantomData,
75            _pdd: std::marker::PhantomData,
76        }
77    }
78}
79
80impl<T, V> NormalEstimator<T, V> for CentralDifferenceEstimator<T, V, Dim3D>
81where
82    T: Add<T, Output = T> + Sub<T, Output = T> + Copy,
83    V: Vec3<T>,
84{
85    #[inline]
86    fn estimate_normal<S: SDF<T, V>>(&self, sdf: S, p: V) -> V {
87        let eps = self.eps;
88        V::new(
89            sdf.dist(V::new(p.x() + eps, p.y(), p.z()))
90                - sdf.dist(V::new(p.x() - eps, p.y(), p.z())),
91            sdf.dist(V::new(p.x(), p.y() + eps, p.z()))
92                - sdf.dist(V::new(p.x(), p.y() - eps, p.z())),
93            sdf.dist(V::new(p.x(), p.y(), p.z() + eps))
94                - sdf.dist(V::new(p.x(), p.y(), p.z() - eps)),
95        )
96        .normalized()
97    }
98}
99
100impl<T, V> NormalEstimator<T, V> for CentralDifferenceEstimator<T, V, Dim2D>
101where
102    T: Add<T, Output = T> + Sub<T, Output = T> + Copy,
103    V: Vec2<T>,
104{
105    #[inline]
106    fn estimate_normal<S: SDF<T, V>>(&self, sdf: S, p: V) -> V {
107        let eps = self.eps;
108        V::new(
109            sdf.dist(V::new(p.x() + eps, p.y())) - sdf.dist(V::new(p.x() - eps, p.y())),
110            sdf.dist(V::new(p.x(), p.y() + eps)) - sdf.dist(V::new(p.x(), p.y() - eps)),
111        )
112        .normalized()
113    }
114}
115
116#[cfg(feature = "ultraviolet")]
117impl<V: Vec<f32x4>> Default for CentralDifferenceEstimator<f32x4, V, <V as Vec<f32x4>>::Dimension> {
118    fn default() -> Self {
119        Self::new(f32x4::from(0.000))
120    }
121}
122
123impl<V: Vec<f32>> Default for CentralDifferenceEstimator<f32, V, <V as Vec<f32>>::Dimension> {
124    fn default() -> Self {
125        Self::new(0.001)
126    }
127}
128
129impl<V: Vec<f64>> Default for CentralDifferenceEstimator<f64, V, <V as Vec<f64>>::Dimension> {
130    fn default() -> Self {
131        Self::new(0.001)
132    }
133}
134
135/// Estimates the normal of an SDF by estimating the gradient of the SDF.
136///
137/// The gradient is estimated by taking four samples of the SDF in a tetrahedron around the
138/// point of interest. By doing so, it only needs to take four instead of 6 samples of the SDF,
139/// like the CentralDifferenceEstimator does, so it is slightly faster. However, it only works
140/// for 3d SDFs and it is slightly less robust than the traditional way.
141///
142/// See [this article](http://iquilezles.org/www/articles/normalsSDF/normalsSDF.htm)
143/// for more.
144pub struct TetrahedralEstimator<T, V> {
145    pub eps: T,
146    _pdv: std::marker::PhantomData<V>,
147}
148
149impl<T, V: Vec<T>> TetrahedralEstimator<T, V> {
150    /// Creates a `TetrahedralEstimator` with a given epsilon value.
151    pub fn new(eps: T) -> Self {
152        TetrahedralEstimator {
153            eps,
154            _pdv: std::marker::PhantomData,
155        }
156    }
157}
158
159impl<T, V> NormalEstimator<T, V> for TetrahedralEstimator<T, V>
160where
161    T: Add<T, Output = T> + Sub<T, Output = T> + Neg<Output = T> + One + Copy + std::fmt::Display,
162    V: Vec3<T>,
163{
164    #[inline]
165    fn estimate_normal<S: SDF<T, V>>(&self, sdf: S, p: V) -> V {
166        let xyy = V::new(T::one(), -T::one(), -T::one());
167        let yyx = V::new(-T::one(), -T::one(), T::one());
168        let yxy = V::new(-T::one(), T::one(), -T::one());
169        let xxx = V::one();
170
171        let d1 = sdf.dist(p + xyy * self.eps);
172        let d2 = sdf.dist(p + yyx * self.eps);
173        let d3 = sdf.dist(p + yxy * self.eps);
174        let d4 = sdf.dist(p + xxx * self.eps);
175
176        (xyy * d1 + yyx * d2 + yxy * d3 + xxx * d4).normalized()
177    }
178}
179
180#[cfg(feature = "ultraviolet")]
181impl<V: Vec<f32x4>> Default for TetrahedralEstimator<f32x4, V> {
182    fn default() -> Self {
183        Self::new(f32x4::from(0.001))
184    }
185}
186
187impl<V: Vec<f32>> Default for TetrahedralEstimator<f32, V> {
188    fn default() -> Self {
189        Self::new(0.001)
190    }
191}
192
193impl<V: Vec<f64>> Default for TetrahedralEstimator<f64, V> {
194    fn default() -> Self {
195        Self::new(0.001)
196    }
197}