ultraviolet/interp.rs
1//! Interpolation on types for which it makes sense.
2use crate::*;
3
4/// Pure linear interpolation, i.e. `(1.0 - t) * self + (t) * end`.
5///
6/// For interpolating `Rotor`s with linear interpolation, you almost certainly
7/// want to normalize the returned `Rotor`. For example,
8/// ```rs
9/// let interpolated_rotor = rotor1.lerp(rotor2, 0.5).normalized();
10/// ```
11/// For most cases (especially where performance is the primary concern, like in
12/// animation interpolation for games, this 'normalized lerp' or 'nlerp' is probably
13/// what you want to use. However, there are situations in which you really want
14/// the interpolation between two `Rotor`s to be of constant angular velocity. In this
15/// case, check out `Slerp`.
16pub trait Lerp<T> {
17 fn lerp(&self, end: Self, t: T) -> Self;
18}
19
20macro_rules! impl_lerp {
21 ($($tt:ident => ($($vt:ident),+)),+) => {
22 $($(impl Lerp<$tt> for $vt {
23 /// Linearly interpolate between `self` and `end` by `t` between 0.0 and 1.0.
24 /// i.e. `(1.0 - t) * self + (t) * end`.
25 ///
26 /// For interpolating `Rotor`s with linear interpolation, you almost certainly
27 /// want to normalize the returned `Rotor`. For example,
28 /// ```rs
29 /// let interpolated_rotor = rotor1.lerp(rotor2, 0.5).normalized();
30 /// ```
31 /// For most cases (especially where performance is the primary concern, like in
32 /// animation interpolation for games, this 'normalized lerp' or 'nlerp' is probably
33 /// what you want to use. However, there are situations in which you really want
34 /// the interpolation between two `Rotor`s to be of constant angular velocity. In this
35 /// case, check out `Slerp`.
36 #[inline]
37 fn lerp(&self, end: Self, t: $tt) -> Self {
38 *self * ($tt::splat(1.0) - t) + end * t
39 }
40 })+)+
41 };
42}
43
44impl_lerp!(
45 f32 => (f32, Vec2, Vec3, Vec4, Bivec2, Bivec3, Rotor2, Rotor3),
46 f32x4 => (f32x4, Vec2x4, Vec3x4, Vec4x4, Bivec2x4, Bivec3x4, Rotor2x4, Rotor3x4),
47 f32x8 => (f32x8, Vec2x8, Vec3x8, Vec4x8, Bivec2x8, Bivec3x8, Rotor2x8, Rotor3x8)
48);
49
50#[cfg(feature = "f64")]
51impl_lerp!(
52 f64 => (f64, DVec2, DVec3, DVec4, DBivec2, DBivec3, DRotor2, DRotor3),
53 f64x2 => (f64x2, DVec2x2, DVec3x2, DVec4x2, DBivec2x2, DBivec3x2, DRotor2x2, DRotor3x2),
54 f64x4 => (f64x4, DVec2x4, DVec3x4, DVec4x4, DBivec2x4, DBivec3x4, DRotor2x4, DRotor3x4)
55);
56
57/// Spherical-linear interpolation.
58///
59/// Basically, interpolation that maintains a constant angular velocity
60/// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation
61/// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of
62/// 3d normal vectors.
63///
64/// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc!
65pub trait Slerp<T> {
66 fn slerp(&self, end: Self, t: T) -> Self;
67}
68
69macro_rules! impl_slerp_rotor3 {
70 ($($tt:ident => ($($vt:ident),+)),+) => {
71 $($(impl Slerp<$tt> for $vt {
72 /// Spherical-linear interpolation between `self` and `end` based on `t` from 0.0 to 1.0.
73 ///
74 /// `self` and `end` should both be normalized or something bad will happen!
75 ///
76 /// Basically, interpolation that maintains a constant angular velocity
77 /// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation
78 /// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of
79 /// 3d normal vectors.
80 ///
81 /// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc!
82 #[inline]
83 fn slerp(&self, mut end: Self, t: $tt) -> Self {
84 let mut dot = self.dot(end);
85
86 // make sure interpolation takes shortest path in case dot product is negative
87 if dot < 0.0 {
88 end *= -1.0;
89 dot = -dot;
90 }
91
92 if dot > 0.9995 {
93 return self.lerp(end, t);
94 }
95
96 let dot = dot.min(1.0).max(-1.0);
97
98 let theta_0 = dot.acos(); // angle between inputs
99 let theta = theta_0 * t; // amount of said angle to travel
100
101 let v2 = (end - (*self * dot)).normalized(); // create orthonormal basis between self and `v2`
102
103 let (s, c) = theta.sin_cos();
104
105 let mut n = *self;
106
107 n.s = (c * self.s) + (s * v2.s);
108 n.bv.xy = (c * self.bv.xy) + (s * v2.bv.xy);
109 n.bv.xz = (c * self.bv.xz) + (s * v2.bv.xz);
110 n.bv.yz = (c * self.bv.yz) + (s * v2.bv.yz);
111
112 n
113 }
114 })+)+
115 };
116}
117
118impl_slerp_rotor3!(
119 f32 => (Rotor3)
120);
121
122#[cfg(feature = "f64")]
123impl_slerp_rotor3!(
124 f64 => (DRotor3)
125);
126
127macro_rules! impl_slerp_rotor3_wide {
128 ($($tt:ident => ($($vt:ident),+)),+) => {
129 $($(impl Slerp<$tt> for $vt {
130 /// Spherical-linear interpolation between `self` and `end` based on `t` from 0.0 to 1.0.
131 ///
132 /// `self` and `end` should both be normalized or something bad will happen!
133 ///
134 /// The implementation for SIMD types also requires that the two things being interpolated between
135 /// are not exactly aligned, or else the result is undefined.
136 ///
137 /// Basically, interpolation that maintains a constant angular velocity
138 /// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation
139 /// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of
140 /// 3d normal vectors.
141 ///
142 /// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc!
143 #[inline]
144 fn slerp(&self, end: Self, t: $tt) -> Self {
145 let dot = self.dot(end);
146
147 let dot = dot.min($tt::splat(1.0)).max($tt::splat(-1.0));
148
149 let theta_0 = dot.acos(); // angle between inputs
150 let theta = theta_0 * t; // amount of said angle to travel
151
152 let v2 = (end - (*self * dot)).normalized(); // create orthonormal basis between self and `v2`
153
154 let (s, c) = theta.sin_cos();
155
156 let mut n = *self;
157
158 n.s = (c * self.s) + (s * v2.s);
159 n.bv.xy = (c * self.bv.xy) + (s * v2.bv.xy);
160 n.bv.xz = (c * self.bv.xz) + (s * v2.bv.xz);
161 n.bv.yz = (c * self.bv.yz) + (s * v2.bv.yz);
162
163 n
164 }
165 })+)+
166 };
167}
168
169impl_slerp_rotor3_wide!(
170 f32x4 => (Rotor3x4),
171 f32x8 => (Rotor3x8)
172);
173
174#[cfg(feature = "f64")]
175impl_slerp_rotor3_wide!(
176 f64x2 => (DRotor3x2),
177 f64x4 => (DRotor3x4)
178);
179
180macro_rules! impl_slerp_gen {
181 ($($tt:ident => ($($vt:ident),+)),+) => {
182 $($(impl Slerp<$tt> for $vt {
183 /// Spherical-linear interpolation between `self` and `end` based on `t` from 0.0 to 1.0.
184 ///
185 /// `self` and `end` should both be normalized or something bad will happen!
186 ///
187 /// The implementation for SIMD types also requires that the two things being interpolated between
188 /// are not exactly aligned, or else the result is undefined.
189 ///
190 /// Basically, interpolation that maintains a constant angular velocity
191 /// from one orientation on a unit hypersphere to another. This is sorta the "high quality" interpolation
192 /// for `Rotor`s, and it can also be used to interpolate other things, one example being interpolation of
193 /// 3d normal vectors.
194 ///
195 /// Note that you should often normalize the result returned by this operation, when working with `Rotor`s, etc!
196 #[inline]
197 fn slerp(&self, end: Self, t: $tt) -> Self {
198 let dot = self.dot(end);
199
200 let dot = dot.min($tt::splat(1.0)).max($tt::splat(-1.0));
201
202 let theta_0 = dot.acos(); // angle between inputs
203 let theta = theta_0 * t; // amount of said angle to travel
204
205 let v2 = (end - (*self * dot)).normalized(); // create orthonormal basis between self and `v2`
206
207 let (s, c) = theta.sin_cos();
208
209 *self * c + v2 * s
210 }
211 })+)+
212 };
213}
214
215impl_slerp_gen!(
216 f32 => (Vec2, Vec3, Vec4, Bivec2, Bivec3, Rotor2),
217 f32x4 => (Vec2x4, Vec3x4, Vec4x4, Bivec2x4, Bivec3x4, Rotor2x4),
218 f32x8 => (Vec2x8, Vec3x8, Vec4x8, Bivec2x8, Bivec3x8, Rotor2x8)
219);
220
221#[cfg(feature = "f64")]
222impl_slerp_gen!(
223 f64 => (DVec2, DVec3, DVec4, DBivec2, DBivec3, DRotor2),
224 f64x2 => (DVec2x2, DVec3x2, DVec4x2, DBivec2x2, DBivec3x2, DRotor2x2),
225 f64x4 => (DVec2x4, DVec3x4, DVec4x4, DBivec2x4, DBivec3x4, DRotor2x4)
226);