1use crate::{EPSILON, Vec3, acos, atan2, clamp, cos, sin};
2
3#[derive(Clone, Copy, Debug, Default, PartialEq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Spherical {
7 pub radius: f32,
9 pub phi: f32,
11 pub theta: f32,
13}
14
15impl Spherical {
16 #[inline]
18 pub const fn new(radius: f32, phi: f32, theta: f32) -> Self {
19 Self { radius, phi, theta }
20 }
21
22 pub fn from_vec3(value: Vec3) -> Self {
24 let radius = value.length();
25 if radius <= EPSILON {
26 return Self::default();
27 }
28 Self::new(
29 radius,
30 acos(clamp(value.y / radius, -1.0, 1.0)),
31 atan2(value.x, value.z),
32 )
33 }
34
35 pub fn to_vec3(self) -> Vec3 {
37 let sin_phi = sin(self.phi);
38 Vec3::new(
39 self.radius * sin_phi * sin(self.theta),
40 self.radius * cos(self.phi),
41 self.radius * sin_phi * cos(self.theta),
42 )
43 }
44
45 #[inline]
47 pub fn clamp_phi(mut self, min: f32, max: f32) -> Self {
48 self.phi = clamp(self.phi, min, max);
49 self
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use crate::assert_close;
57
58 #[test]
59 fn spherical_round_trips_vec3() {
60 let value = Vec3::new(2.0, 3.0, 4.0);
61 let out = Spherical::from_vec3(value).to_vec3();
62 assert_close(out.x, value.x);
63 assert_close(out.y, value.y);
64 assert_close(out.z, value.z);
65 }
66}