rscad_csg_bsp/primitives/
sphere.rs1use crate::{Csg, Number, Polygon};
2use itertools::*;
3use nalgebra::Point3;
4use num_traits::real::Real;
5
6pub fn sphere<T: Number>(radius: T, segments: usize, stacks: usize) -> Csg<T> {
14 let segments = segments.max(3);
15 let stacks = stacks.max(2);
16
17 let pi = T::PI();
18 let two = T::one() + T::one();
19 let two_pi = two * pi;
20
21 let stacks_t = T::from(stacks).expect("BUG: failed to convert stacks to T");
22 let segments_t = T::from(segments).expect("BUG: failed to convert segments to T");
23
24 let point_on_surface = |frac_seg: T, frac_stack: T| {
25 let lat = pi * frac_stack;
26 let stack_radius = radius * Real::sin(lat);
27 let x = stack_radius * Real::sin(two_pi * frac_seg);
28 let y = radius * Real::cos(lat);
29 let z = stack_radius * Real::cos(two_pi * frac_seg);
30 Point3::new(x, y, z)
31 };
32
33 let seg_fracs: Vec<T> = (0..segments)
34 .flat_map(|v| T::from(v))
35 .map(|v| v / segments_t)
36 .collect();
37
38 let stack_pairs: Vec<(T, T)> = (1..stacks)
39 .flat_map(|v| T::from(v))
40 .map(|v| v / stacks_t)
41 .tuple_windows()
42 .collect();
43
44 let first_ring = T::one() / stacks_t;
45 let last_ring = T::from(stacks - 1).expect("BUG: stacks > 1") / stacks_t;
46
47 let top_cap = Polygon::from_points(seg_fracs.iter().map(|&s| point_on_surface(s, first_ring)));
48
49 let bottom_cap = Polygon::from_points(
50 seg_fracs
51 .iter()
52 .rev()
53 .map(|&s| point_on_surface(s, last_ring)),
54 );
55
56 let body = seg_fracs
57 .iter()
58 .copied()
59 .circular_tuple_windows()
60 .flat_map(|(left, right)| {
61 stack_pairs.iter().flat_map(move |&(top, bottom)| {
62 Polygon::from_points([
63 point_on_surface(left, top),
64 point_on_surface(left, bottom),
65 point_on_surface(right, bottom),
66 point_on_surface(right, top),
67 ])
68 })
69 });
70
71 let polygons = body.chain([top_cap, bottom_cap].into_iter().flatten());
72 Csg::from_polygons(polygons)
73}