1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use crate::{Geometry, Rad, Vertex3D};

#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
pub struct Sphere {
    pub radius: f32,
    pub width_segments: u32,
    pub height_segments: u32,
    pub phi_start: Rad,
    pub phi_length: Rad,
    pub theta_start: Rad,
    pub theta_length: Rad,
}

impl Default for Sphere {
    fn default() -> Self {
        Self {
            radius: 1.,
            width_segments: 8,
            height_segments: 6,
            phi_start: Rad(0.0),
            phi_length: Rad(std::f32::consts::PI * 2.),
            theta_start: Rad(0.0),
            theta_length: Rad(std::f32::consts::PI),
        }
    }
}

impl Sphere {
    fn vertices(&self) -> Vec<Vertex3D> {
        let mut vertices = vec![];
        let theta_end = std::f32::consts::PI.min(self.theta_start.0 + self.theta_length.0);

        for y in 0..=self.height_segments {
            let v = y as f32 / self.height_segments as f32;

            // special consideration for the poles
            let u_offset = if y == 0 && self.theta_start.0 == 0. {
                0.5 / self.width_segments as f32
            } else if y == self.height_segments && theta_end == std::f32::consts::PI {
                -0.5 / self.width_segments as f32
            } else {
                0.
            };

            for x in 0..=self.width_segments {
                let u = x as f32 / self.width_segments as f32;

                let position = nalgebra::Vector3::new(
                    -self.radius
                        * (self.phi_start.0 + u * self.phi_length.0).cos()
                        * (self.theta_start.0 + v * self.theta_length.0).sin(),
                    self.radius * (self.theta_start.0 + v * self.theta_length.0).cos(),
                    self.radius
                        * (self.phi_start.0 + u * self.phi_length.0).sin()
                        * (self.theta_start.0 + v * self.theta_length.0).sin(),
                );
                vertices.push(Vertex3D {
                    normal: position.normalize().into(),
                    position: position.into(),
                    uv: [u + u_offset, v],
                    color: [1., 1., 1., 1.],
                });
            }
        }

        vertices
    }

    fn indices(&self) -> Vec<u32> {
        let mut indices = vec![];
        let theta_end = std::f32::consts::PI.min(self.theta_start.0 + self.theta_length.0);

        let mut index = 0;
        let mut grid = vec![];
        for _y in 0..=self.height_segments {
            let mut vertices_row = vec![];
            for _x in 0..=self.width_segments {
                vertices_row.push(index);
                index += 1;
            }
            grid.push(vertices_row);
        }

        for iy in 0..self.height_segments as usize {
            for ix in 0..self.width_segments as usize {
                let a = grid[iy][ix + 1];
                let b = grid[iy][ix];
                let c = grid[iy + 1][ix];
                let d = grid[iy + 1][ix + 1];

                if iy as u32 != 0 || self.theta_start.0 > 0. {
                    indices.extend_from_slice(&[a, b, d]);
                }
                if iy as u32 != self.height_segments - 1 || theta_end < std::f32::consts::PI {
                    indices.extend_from_slice(&[b, c, d]);
                }
            }
        }

        indices
    }
}

impl From<&Sphere> for Geometry<'_, Vertex3D> {
    fn from(s: &Sphere) -> Self {
        Self::new(s.vertices(), Some(s.indices()))
    }
}

impl From<Sphere> for Geometry<'_, Vertex3D> {
    fn from(s: Sphere) -> Self {
        (&s).into()
    }
}