ranim_items/mesh/
sphere.rs1use 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#[derive(Debug, Clone, PartialEq)]
19pub struct Sphere {
20 pub radius: f64,
22 pub resolution: (u32, u32),
24 pub fill_rgba: AlphaColor<Srgb>,
26}
27
28impl Sphere {
29 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 pub fn unit() -> Self {
39 Self::new(1.0)
40 }
41 pub fn with_resolution(mut self, resolution: (u32, u32)) -> Self {
43 self.resolution = resolution;
44 self
45 }
46 pub fn with_fill_color(mut self, color: AlphaColor<Srgb>) -> Self {
48 self.fill_rgba = color;
49 self
50 }
51 pub fn points_uv_func(u: f64, v: f64, r: f64) -> DVec3 {
53 Self::normals_uv_func(u, v) * r
54 }
55 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}