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::{DMat4, DVec3},
11    traits::{FillColor, Interpolatable, Opacity, ShiftTransform, With},
12};
13
14use crate::mesh::MeshItem;
15
16use super::Surface;
17
18/// A sphere defined by center, radius, and resolution.
19///
20/// The sphere is parameterized as:
21/// - `u ∈ [0, TAU]`, `v ∈ [0, PI]`
22/// - `x = r * cos(u) * sin(v)`
23/// - `y = r * sin(u) * sin(v)`
24/// - `z = r * (-cos(v))`
25#[derive(Debug, Clone, PartialEq)]
26pub struct Sphere {
27    /// Center of the sphere.
28    pub center: DVec3,
29    /// Radius of the sphere.
30    pub radius: f64,
31    /// Grid resolution `(nu, nv)`.
32    pub resolution: (u32, u32),
33    /// Fill color (with alpha).
34    pub fill_rgba: AlphaColor<Srgb>,
35}
36
37impl Sphere {
38    /// Create a new sphere with the given radius, centered at the origin.
39    pub fn new(radius: f64) -> Self {
40        Self {
41            center: DVec3::ZERO,
42            radius,
43            resolution: (101, 51),
44            fill_rgba: color::palette::css::BLUE.with_alpha(1.0),
45        }
46    }
47
48    /// Create a unit sphere (radius = 1).
49    pub fn unit() -> Self {
50        Self::new(1.0)
51    }
52
53    /// Set the center. Returns `self` for chaining.
54    pub fn with_center(mut self, center: DVec3) -> Self {
55        self.center = center;
56        self
57    }
58
59    /// Set the resolution. Returns `self` for chaining.
60    pub fn with_resolution(mut self, resolution: (u32, u32)) -> Self {
61        self.resolution = resolution;
62        self
63    }
64
65    /// Set the fill color. Returns `self` for chaining.
66    pub fn with_fill_color(mut self, color: AlphaColor<Srgb>) -> Self {
67        self.fill_rgba = color;
68        self
69    }
70
71    /// Generate a point on the sphere using UV coordinates.
72    pub fn points_uv_func(u: f64, v: f64, r: f64) -> DVec3 {
73        Self::normals_uv_func(u, v) * r
74    }
75
76    /// Generate a normal on the sphere using UV coordinates.
77    pub fn normals_uv_func(u: f64, v: f64) -> DVec3 {
78        let x = u.cos() * v.sin();
79        let y = u.sin() * v.sin();
80        let z = -v.cos();
81        DVec3::new(x, y, z)
82    }
83}
84
85impl From<Sphere> for MeshItem {
86    fn from(value: Sphere) -> Self {
87        Surface::from(value).into()
88    }
89}
90
91impl From<Sphere> for Surface {
92    fn from(value: Sphere) -> Self {
93        Surface::from_uv_func(
94            |u, v| Sphere::points_uv_func(u, v, value.radius),
95            (0.0, TAU),
96            (0.0, PI),
97            value.resolution,
98        )
99        .with_transform(DMat4::from_translation(value.center))
100        .with(|x| {
101            x.set_fill_color(value.fill_rgba);
102        })
103    }
104}
105
106impl Interpolatable for Sphere {
107    fn lerp(&self, target: &Self, t: f64) -> Self {
108        Self {
109            center: Interpolatable::lerp(&self.center, &target.center, t),
110            radius: Interpolatable::lerp(&self.radius, &target.radius, t),
111            resolution: if t < 0.5 {
112                self.resolution
113            } else {
114                target.resolution
115            },
116            fill_rgba: Interpolatable::lerp(&self.fill_rgba, &target.fill_rgba, t),
117        }
118    }
119}
120
121impl FillColor for Sphere {
122    fn fill_color(&self) -> AlphaColor<Srgb> {
123        self.fill_rgba
124    }
125    fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
126        self.fill_rgba = color;
127        self
128    }
129    fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
130        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
131        self
132    }
133}
134
135impl Opacity for Sphere {
136    fn set_opacity(&mut self, opacity: f32) -> &mut Self {
137        self.fill_rgba = self.fill_rgba.with_alpha(opacity);
138        self
139    }
140}
141
142impl ShiftTransform for Sphere {
143    fn shift(&mut self, offset: DVec3) -> &mut Self {
144        self.center += offset;
145        self
146    }
147}
148
149impl Aabb for Sphere {
150    fn aabb(&self) -> [DVec3; 2] {
151        let r = DVec3::splat(self.radius);
152        [self.center - r, self.center + r]
153    }
154}
155
156impl Extract for Sphere {
157    type Target = CoreItem;
158    fn extract_into(&self, buf: &mut Vec<Self::Target>) {
159        Surface::from(self.clone()).extract_into(buf);
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use ranim_core::glam::dvec3;
167
168    #[test]
169    fn test_sphere_center_to_transform() {
170        let sphere = Sphere::new(1.0).with_center(dvec3(1.0, 2.0, 3.0));
171        let surface = Surface::from(sphere);
172        assert_eq!(
173            surface.transform,
174            DMat4::from_translation(dvec3(1.0, 2.0, 3.0))
175        );
176    }
177
178    #[test]
179    fn test_sphere_aabb() {
180        let sphere = Sphere::new(1.0).with_center(dvec3(1.0, 2.0, 3.0));
181        let [min, max] = sphere.aabb();
182        assert_eq!(min, dvec3(0.0, 1.0, 2.0));
183        assert_eq!(max, dvec3(2.0, 3.0, 4.0));
184    }
185
186    #[test]
187    fn test_sphere_shift() {
188        let mut sphere = Sphere::new(1.0);
189        sphere.shift(dvec3(1.0, 0.0, 0.0));
190        assert_eq!(sphere.center, dvec3(1.0, 0.0, 0.0));
191    }
192
193    #[test]
194    fn test_sphere_interpolation() {
195        let a = Sphere::new(1.0).with_center(dvec3(0.0, 0.0, 0.0));
196        let b = Sphere::new(3.0).with_center(dvec3(2.0, 0.0, 0.0));
197        let mid = a.lerp(&b, 0.5);
198        assert!((mid.radius - 2.0).abs() < 1e-10);
199        assert!((mid.center.x - 1.0).abs() < 1e-10);
200    }
201
202    #[test]
203    fn test_sphere_to_surface() {
204        let sphere = Sphere::new(1.0)
205            .with_center(dvec3(1.0, 0.0, 0.0))
206            .with_resolution((5, 5));
207        let surface = Surface::from(sphere);
208        assert_eq!(surface.vertices.len(), 25);
209        assert_eq!(surface.resolution, (5, 5));
210        assert_eq!(
211            surface.transform,
212            DMat4::from_translation(dvec3(1.0, 0.0, 0.0))
213        );
214    }
215}