1use crate::Interpolate;
4use crate::math::{acos, cos, sin, sqrt};
5
6const PI: f32 = core::f32::consts::PI;
7
8#[derive(Clone, Copy, Debug, PartialEq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct Angle(pub f32);
15
16impl Angle {
17 #[inline]
19 pub fn from_degrees(degrees: f32) -> Self {
20 Self(degrees)
21 }
22
23 #[inline]
25 pub fn from_radians(radians: f32) -> Self {
26 Self(radians * 180.0 / PI)
27 }
28
29 #[inline]
31 pub fn degrees(self) -> f32 {
32 self.0
33 }
34
35 #[inline]
37 pub fn radians(self) -> f32 {
38 self.0 * PI / 180.0
39 }
40
41 pub fn normalized(self) -> Self {
43 let mut degrees = self.0 % 360.0;
44 if degrees < 0.0 {
45 degrees += 360.0;
46 }
47 Self(degrees)
48 }
49}
50
51impl Interpolate for Angle {
52 fn lerp(&self, other: &Self, t: f32) -> Self {
53 let delta = ((other.0 - self.0 + 540.0) % 360.0) - 180.0;
54 Self(self.0 + delta * t)
55 }
56}
57
58#[derive(Clone, Copy, Debug, PartialEq)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64pub struct Quaternion {
65 pub x: f32,
67 pub y: f32,
69 pub z: f32,
71 pub w: f32,
73}
74
75impl Quaternion {
76 pub const IDENTITY: Self = Self {
78 x: 0.0,
79 y: 0.0,
80 z: 0.0,
81 w: 1.0,
82 };
83
84 pub fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
86 Self { x, y, z, w }.normalized()
87 }
88
89 pub fn from_axis_angle(axis: [f32; 3], angle: Angle) -> Self {
91 let len = sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
92 if len <= f32::EPSILON {
93 return Self::IDENTITY;
94 }
95 let half = angle.radians() * 0.5;
96 let s = sin(half) / len;
97 Self::new(axis[0] * s, axis[1] * s, axis[2] * s, cos(half))
98 }
99
100 #[inline]
102 pub fn dot(self, other: Self) -> f32 {
103 self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w
104 }
105
106 #[inline]
108 pub fn length(self) -> f32 {
109 sqrt(self.dot(self))
110 }
111
112 pub fn normalized(self) -> Self {
114 let len = self.length();
115 if !len.is_finite() || len <= f32::EPSILON {
116 return Self::IDENTITY;
117 }
118 Self {
119 x: self.x / len,
120 y: self.y / len,
121 z: self.z / len,
122 w: self.w / len,
123 }
124 }
125
126 #[inline]
128 pub fn negated(self) -> Self {
129 Self {
130 x: -self.x,
131 y: -self.y,
132 z: -self.z,
133 w: -self.w,
134 }
135 }
136
137 pub fn slerp(self, other: Self, t: f32) -> Self {
139 let a = self.normalized();
140 let mut b = other.normalized();
141 let mut dot = a.dot(b);
142 if dot < 0.0 {
143 b = b.negated();
144 dot = -dot;
145 }
146
147 if dot > 0.9995 {
148 return Self {
149 x: a.x + (b.x - a.x) * t,
150 y: a.y + (b.y - a.y) * t,
151 z: a.z + (b.z - a.z) * t,
152 w: a.w + (b.w - a.w) * t,
153 }
154 .normalized();
155 }
156
157 let theta_0 = acos(dot.clamp(-1.0, 1.0));
158 let theta = theta_0 * t;
159 let sin_theta = sin(theta);
160 let sin_theta_0 = sin(theta_0);
161 if sin_theta_0.abs() <= f32::EPSILON {
162 return a;
163 }
164 let s0 = cos(theta) - dot * sin_theta / sin_theta_0;
165 let s1 = sin_theta / sin_theta_0;
166 Self {
167 x: a.x * s0 + b.x * s1,
168 y: a.y * s0 + b.y * s1,
169 z: a.z * s0 + b.z * s1,
170 w: a.w * s0 + b.w * s1,
171 }
172 .normalized()
173 }
174
175 pub fn to_mat3(self) -> [f32; 9] {
177 let q = self.normalized();
178 let xx = q.x * q.x;
179 let yy = q.y * q.y;
180 let zz = q.z * q.z;
181 let xy = q.x * q.y;
182 let xz = q.x * q.z;
183 let yz = q.y * q.z;
184 let wx = q.w * q.x;
185 let wy = q.w * q.y;
186 let wz = q.w * q.z;
187
188 [
189 1.0 - 2.0 * (yy + zz),
190 2.0 * (xy + wz),
191 2.0 * (xz - wy),
192 2.0 * (xy - wz),
193 1.0 - 2.0 * (xx + zz),
194 2.0 * (yz + wx),
195 2.0 * (xz + wy),
196 2.0 * (yz - wx),
197 1.0 - 2.0 * (xx + yy),
198 ]
199 }
200}
201
202impl Interpolate for Quaternion {
203 fn lerp(&self, other: &Self, t: f32) -> Self {
204 self.slerp(*other, t)
205 }
206}
207
208#[derive(Clone, Copy, Debug, PartialEq)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
214pub struct Mat4(pub [f32; 16]);
215
216impl Mat4 {
217 pub const IDENTITY: Self = Self([
219 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
223 ]);
224
225 pub fn from_translation_rotation_scale(
227 translation: [f32; 3],
228 rotation: Quaternion,
229 scale: [f32; 3],
230 ) -> Self {
231 let r = rotation.to_mat3();
232 Self([
233 r[0] * scale[0],
234 r[1] * scale[0],
235 r[2] * scale[0],
236 0.0,
237 r[3] * scale[1],
238 r[4] * scale[1],
239 r[5] * scale[1],
240 0.0,
241 r[6] * scale[2],
242 r[7] * scale[2],
243 r[8] * scale[2],
244 0.0,
245 translation[0],
246 translation[1],
247 translation[2],
248 1.0,
249 ])
250 }
251
252 #[inline]
254 pub fn translation(self) -> [f32; 3] {
255 [self.0[12], self.0[13], self.0[14]]
256 }
257
258 pub fn scale(self) -> [f32; 3] {
260 [
261 column_len(&self.0, 0),
262 column_len(&self.0, 1),
263 column_len(&self.0, 2),
264 ]
265 }
266
267 pub fn decompose(self) -> ([f32; 3], Quaternion, [f32; 3]) {
269 let translation = self.translation();
270 let scale = self.scale();
271 let rotation = rotation_from_scaled_mat4(self.0, scale);
272 (translation, rotation, scale)
273 }
274}
275
276impl Interpolate for Mat4 {
277 fn lerp(&self, other: &Self, t: f32) -> Self {
278 let (ta, ra, sa) = self.decompose();
279 let (tb, rb, sb) = other.decompose();
280 Self::from_translation_rotation_scale(ta.lerp(&tb, t), ra.slerp(rb, t), sa.lerp(&sb, t))
281 }
282}
283
284#[derive(Clone, Copy, Debug, PartialEq)]
288#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
289pub struct Color(pub [f32; 4]);
290
291impl Color {
292 #[inline]
294 pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
295 Self([r, g, b, a])
296 }
297
298 #[inline]
300 pub fn r(self) -> f32 {
301 self.0[0]
302 }
303
304 #[inline]
306 pub fn g(self) -> f32 {
307 self.0[1]
308 }
309
310 #[inline]
312 pub fn b(self) -> f32 {
313 self.0[2]
314 }
315
316 #[inline]
318 pub fn a(self) -> f32 {
319 self.0[3]
320 }
321}
322
323impl Interpolate for Color {
324 fn lerp(&self, other: &Self, t: f32) -> Self {
325 Self(self.0.lerp(&other.0, t))
326 }
327}
328
329fn column_len(m: &[f32; 16], col: usize) -> f32 {
330 let i = col * 4;
331 sqrt(m[i] * m[i] + m[i + 1] * m[i + 1] + m[i + 2] * m[i + 2])
332}
333
334fn rotation_from_scaled_mat4(m: [f32; 16], scale: [f32; 3]) -> Quaternion {
335 let sx = if scale[0].abs() <= f32::EPSILON {
336 1.0
337 } else {
338 scale[0]
339 };
340 let sy = if scale[1].abs() <= f32::EPSILON {
341 1.0
342 } else {
343 scale[1]
344 };
345 let sz = if scale[2].abs() <= f32::EPSILON {
346 1.0
347 } else {
348 scale[2]
349 };
350
351 let r00 = m[0] / sx;
352 let r01 = m[4] / sy;
353 let r02 = m[8] / sz;
354 let r10 = m[1] / sx;
355 let r11 = m[5] / sy;
356 let r12 = m[9] / sz;
357 let r20 = m[2] / sx;
358 let r21 = m[6] / sy;
359 let r22 = m[10] / sz;
360 let trace = r00 + r11 + r22;
361
362 if trace > 0.0 {
363 let s = sqrt(trace + 1.0) * 2.0;
364 return Quaternion::new((r21 - r12) / s, (r02 - r20) / s, (r10 - r01) / s, 0.25 * s);
365 }
366 if r00 > r11 && r00 > r22 {
367 let s = sqrt(1.0 + r00 - r11 - r22) * 2.0;
368 return Quaternion::new(0.25 * s, (r01 + r10) / s, (r02 + r20) / s, (r21 - r12) / s);
369 }
370 if r11 > r22 {
371 let s = sqrt(1.0 + r11 - r00 - r22) * 2.0;
372 return Quaternion::new((r01 + r10) / s, 0.25 * s, (r12 + r21) / s, (r02 - r20) / s);
373 }
374 let s = sqrt(1.0 + r22 - r00 - r11) * 2.0;
375 Quaternion::new((r02 + r20) / s, (r12 + r21) / s, 0.25 * s, (r10 - r01) / s)
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
383 fn angle_uses_shortest_path() {
384 let a = Angle::from_degrees(359.0);
385 let b = Angle::from_degrees(1.0);
386 assert_eq!(a.lerp(&b, 0.5).normalized().degrees(), 0.0);
387 }
388
389 #[test]
390 fn quaternion_slerp_midpoint_is_normalized() {
391 let a = Quaternion::IDENTITY;
392 let b = Quaternion::from_axis_angle([0.0, 0.0, 1.0], Angle::from_degrees(180.0));
393 let mid = a.lerp(&b, 0.5);
394 assert!((mid.length() - 1.0).abs() < 0.0001);
395 assert!((mid.w.abs() - 0.70710677).abs() < 0.0002);
396 }
397
398 #[test]
399 fn mat4_interpolates_translation_scale_and_rotation() {
400 let a = Mat4::IDENTITY;
401 let b = Mat4::from_translation_rotation_scale(
402 [10.0, 20.0, 30.0],
403 Quaternion::from_axis_angle([0.0, 0.0, 1.0], Angle::from_degrees(90.0)),
404 [2.0, 4.0, 6.0],
405 );
406 let mid = a.lerp(&b, 0.5);
407 let (translation, rotation, scale) = mid.decompose();
408 assert_eq!(translation, [5.0, 10.0, 15.0]);
409 assert!((rotation.length() - 1.0).abs() < 0.0001);
410 assert!((scale[0] - 1.5).abs() < 0.0001);
411 assert!((scale[1] - 2.5).abs() < 0.0001);
412 assert!((scale[2] - 3.5).abs() < 0.0001);
413 }
414
415 #[test]
416 fn color_lerps_components() {
417 let a = Color::rgba(0.0, 0.25, 0.5, 1.0);
418 let b = Color::rgba(1.0, 0.75, 0.0, 0.5);
419 assert_eq!(a.lerp(&b, 0.5), Color::rgba(0.5, 0.5, 0.25, 0.75));
420 }
421}