Skip to main content

rscad_csg_bsp/primitives/
sphere.rs

1use crate::{Csg, Number, Polygon};
2use itertools::*;
3use nalgebra::Point3;
4use num_traits::real::Real;
5
6/// Create a UV sphere centered at the origin.
7///
8/// - `radius`: sphere radius
9/// - `segments`: tessellation resolution (longitude = segments)
10/// - `stacks`: tessellation resolution (latitude = stacks)
11///
12/// Minimum useful value for `segments` is 3.
13pub 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}