Skip to main content

gl_utils/mesh/
sphere.rs

1use std;
2use crate::{math, vertex};
3use super::*;
4
5/// (vertex count, index count)
6pub const fn lines3d_vertex_index_counts (
7  latitude_divisions : u16, longitude_divisions : u16
8) -> (u32, u32) {
9  let latitude_divisions  = (latitude_divisions - latitude_divisions % 2) as u32;
10  let longitude_divisions = longitude_divisions as u32;
11  ( 2 + (latitude_divisions - 1) * longitude_divisions,      // vertex_count
12    2 * longitude_divisions * (2 * latitude_divisions - 1) ) // index count
13}
14
15/// Produces vertices and indices for a 3D lines list unit sphere (radius
16/// $1.0$) with the given number of latitudinal divisions rounded down to the
17/// nearest even number (ensuring the sphere always has an 'equator'), and with
18/// the given number of longitudinal divisions.
19///
20/// The number of parallels will be equal to `latitude_divisions -
21/// (latitude_divisions % 2) - 1` and the number of meridians will be equal to
22/// `longitude_divisions`.
23///
24/// There will be `2 + (latitude_divisions-1) * longitude_divisions`
25/// vertices and `4 * longitude_divisions * latitude_divisions -
26/// 2 * longitude_divisions` indices.
27///
28/// The first `4 * (latitude_divisions / 2) * longitude_divisions` indices
29/// can be used to render only the top hemisphere (the sphere itself is
30/// computed progressively by adding more vertices and indices onto a base
31/// hemisphere to form the southern hemisphere).
32///
33/// # Panics
34///
35/// Panics if `latitude_divisions` or `longitude_divisions` are less than two
36/// (i.e. there must be at least an equator latitude dividing the north and
37/// south hemispheres and a prime meridian dividing the east and west
38/// hemispheres.
39impl Lines3d {
40  pub fn sphere (index_offset : u32, latitude_divisions : u16, longitude_divisions : u16)
41    -> Self
42  {
43    use std::f32::consts::PI;
44    assert!(2 <= latitude_divisions);
45    assert!(2 <= longitude_divisions);
46    let (num_vertices, num_indices) = lines3d_vertex_index_counts (
47      latitude_divisions, longitude_divisions);
48    let latitude_divisions  = (latitude_divisions - latitude_divisions % 2) as u32;
49    let hemisphere_latitude_divisions = latitude_divisions / 2;
50    let longitude_divisions = longitude_divisions as u32;
51    let num_parallels       = latitude_divisions - 1;
52    let num_meridians       = longitude_divisions;
53    let latitude_angle      = PI / latitude_divisions  as f32;
54    let longitude_angle     = 2.0 * (PI / longitude_divisions as f32);
55    let (mut vertices, mut indices) = {
56      let mut vertices =
57        Vec::<vertex::Vert3dInstanced>::with_capacity (num_vertices as usize);
58      let mut indices = Vec::<u32>::with_capacity (num_indices as usize);
59      let Lines3d {
60        vertices: mut hemisphere_vertices, indices: mut hemisphere_indices
61      } = Lines3d::hemisphere (
62        index_offset,
63        hemisphere_latitude_divisions as u16,
64        longitude_divisions as u16
65      );
66      vertices.append (&mut hemisphere_vertices);
67      indices.append  (&mut hemisphere_indices);
68      (vertices, indices)
69    };
70    let south_pole_index = index_offset + vertices.len() as u32;
71    // the "south pole"
72    vertices.push (vertex::Vert3dInstanced {
73      inst_position: (-1.0 * math::Vector3::<f32>::unit_z()).into_array()
74    });
75    // for each southern parallel, generate a vertex for each meridian
76    for i in 0..num_parallels / 2 {
77      let i = i as f32 + 1.0;
78      let v = *math::Rotation3::from_angle_x (math::Rad (-i * latitude_angle))
79        * (-1.0 * math::Vector3::<f32>::unit_z());
80      for j in 0..num_meridians {
81        let j = j as f32;
82        let w = *math::Rotation3::from_angle_z (math::Rad (j * longitude_angle)) * v;
83        // each meridian for this parallel
84        vertices.push (vertex::Vert3dInstanced { inst_position: w.into_array() });
85      }
86    }
87    debug_assert_eq!(vertices.len(), num_vertices as usize);
88
89    // connect south pole to last parallel
90    for i in 0..num_meridians {
91      indices.push (south_pole_index);
92      indices.push (south_pole_index + 1 + i);
93    }
94    // connect each southern parallel with itself and the next parallel along the
95    // meridians
96    for i in hemisphere_latitude_divisions..num_parallels {
97      let parallel_base_index = index_offset + 2 + i * num_meridians;
98      for j in 0..num_meridians {
99        indices.push (parallel_base_index + j);
100        indices.push (parallel_base_index + (j + 1) % num_meridians);
101        indices.push (parallel_base_index + j);
102        if i < num_parallels-1 {
103          // if this is not the last parallel, connect this to the next parallel
104          indices.push (parallel_base_index + j + num_meridians);
105        } else {
106          // if this is the last parallel, connect it with the equator
107          indices.push (south_pole_index - num_meridians + j);
108        }
109      }
110    }
111    debug_assert_eq!(indices.len(), num_indices as usize);
112
113    Lines3d { vertices, indices }
114  }
115}