Skip to main content

ranim_items/mesh/
sphere.rs

1//! Sphere — a sphere mesh item.
2
3use std::f64::consts::{PI, TAU};
4
5use ranim_core::{
6    Extract,
7    anchor::Aabb,
8    color::{self, AlphaColor, Srgb},
9    core_item::CoreItem,
10    glam::DVec3,
11    traits::{FillColor, Interpolatable, Opacity, With},
12};
13
14use super::Surface;
15use crate::mesh::MeshItem;
16
17/// A sphere primitive centered at the origin.
18#[derive(Debug, Clone, PartialEq)]
19pub struct Sphere {
20    /// Sphere radius.
21    pub radius: f64,
22    /// UV mesh resolution `(u, v)`.
23    pub resolution: (u32, u32),
24    /// Sphere fill color.
25    pub fill_rgba: AlphaColor<Srgb>,
26}
27
28impl Sphere {
29    /// Creates a sphere centered at the origin.
30    pub fn new(radius: f64) -> Self {
31        Self {
32            radius,
33            resolution: (101, 51),
34            fill_rgba: color::palette::css::BLUE.with_alpha(1.0),
35        }
36    }
37    /// Creates a unit sphere.
38    pub fn unit() -> Self {
39        Self::new(1.0)
40    }
41    /// Sets the UV mesh resolution.
42    pub fn with_resolution(mut self, resolution: (u32, u32)) -> Self {
43        self.resolution = resolution;
44        self
45    }
46    /// Sets the fill color.
47    pub fn with_fill_color(mut self, color: AlphaColor<Srgb>) -> Self {
48        self.fill_rgba = color;
49        self
50    }
51    /// Returns a point at spherical UV coordinates and radius `r`.
52    pub fn points_uv_func(u: f64, v: f64, r: f64) -> DVec3 {
53        Self::normals_uv_func(u, v) * r
54    }
55    /// Returns the unit normal at spherical UV coordinates.
56    pub fn normals_uv_func(u: f64, v: f64) -> DVec3 {
57        DVec3::new(u.cos() * v.sin(), u.sin() * v.sin(), -v.cos())
58    }
59}
60impl From<Sphere> for MeshItem {
61    fn from(value: Sphere) -> Self {
62        Surface::from(value).into()
63    }
64}
65impl From<Sphere> for Surface {
66    fn from(value: Sphere) -> Self {
67        Surface::from_uv_func(
68            |u, v| Sphere::points_uv_func(u, v, value.radius),
69            (0.0, TAU),
70            (0.0, PI),
71            value.resolution,
72        )
73        .with(|x| {
74            x.set_fill_color(value.fill_rgba);
75        })
76    }
77}
78impl Interpolatable for Sphere {
79    fn lerp(&self, target: &Self, t: f64) -> Self {
80        Self {
81            radius: self.radius.lerp(&target.radius, t),
82            resolution: if t < 0.5 {
83                self.resolution
84            } else {
85                target.resolution
86            },
87            fill_rgba: Interpolatable::lerp(&self.fill_rgba, &target.fill_rgba, t),
88        }
89    }
90}
91impl FillColor for Sphere {
92    fn fill_color(&self) -> AlphaColor<Srgb> {
93        self.fill_rgba
94    }
95    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
96        self.fill_rgba = color;
97        self
98    }
99    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
100        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
101        self
102    }
103}
104impl Opacity for Sphere {
105    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
106        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
107        self
108    }
109}
110impl Aabb for Sphere {
111    fn aabb(&self) -> [DVec3; 2] {
112        let r = DVec3::splat(self.radius);
113        [-r, r]
114    }
115}
116impl Extract for Sphere {
117    type Target = CoreItem;
118    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
119        Surface::from(self.clone()).extract_into(buf);
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use ranim_core::{glam::dvec3, prelude::TransformedExt, traits::Translation};
127    #[test]
128    fn sphere_surface_uses_canonical_local_vertices() {
129        let surface = Surface::from(Sphere::new(1.0).with_resolution((5, 5)));
130        assert_eq!(surface.vertices.len(), 25);
131        assert_eq!(surface.resolution, (5, 5));
132        assert!(surface.vertices[0].abs_diff_eq(Sphere::points_uv_func(0.0, 0.0, 1.0), 1e-10));
133    }
134    #[test]
135    fn sphere_aabb_tracks_placement() {
136        let [min, max] = Sphere::new(1.0).aabb();
137        assert_eq!(min, dvec3(-1.0, -1.0, -1.0));
138        assert_eq!(max, dvec3(1.0, 1.0, 1.0));
139
140        let sphere = Sphere::new(1.0).transformed(Translation(dvec3(1.0, 2.0, 3.0)));
141        let [min, max] = sphere.aabb();
142        assert_eq!(min, dvec3(0.0, 1.0, 2.0));
143        assert_eq!(max, dvec3(2.0, 3.0, 4.0));
144    }
145    #[test]
146    fn test_sphere_interpolation() {
147        let mid = Sphere::new(1.0).lerp(&Sphere::new(3.0), 0.5);
148        assert!((mid.radius - 2.0).abs() < 1e-10);
149    }
150}