#![allow(dead_code)]
use std::f32::consts::PI;
#[allow(dead_code)]
pub fn sphere_vert_count(lat: u32, lon: u32) -> usize {
2 + ((lat - 1) * lon) as usize
}
#[allow(dead_code)]
pub fn sphere_surface_area(radius: f32) -> f32 {
4.0 * PI * radius * radius
}
#[allow(dead_code)]
pub fn sphere_uv_coords(verts: &[[f32; 3]]) -> Vec<[f32; 2]> {
verts
.iter()
.map(|v| {
let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt().max(1e-8);
let u = (v[2].atan2(v[0]) / (2.0 * PI) + 0.5).clamp(0.0, 1.0);
let lat = (v[1] / len).clamp(-1.0, 1.0).asin();
let v_coord = (lat / PI + 0.5).clamp(0.0, 1.0);
[u, v_coord]
})
.collect()
}
#[allow(dead_code)]
pub fn make_uv_sphere(
radius: f32,
lat_segs: u32,
lon_segs: u32,
) -> (Vec<[f32; 3]>, Vec<[u32; 3]>) {
if lat_segs < 2 || lon_segs < 3 {
return (vec![], vec![]);
}
let mut verts: Vec<[f32; 3]> = Vec::new();
verts.push([0.0, radius, 0.0]);
for lat in 1..lat_segs {
let phi = PI * lat as f32 / lat_segs as f32;
let y = radius * phi.cos();
let r = radius * phi.sin();
for lon in 0..lon_segs {
let theta = 2.0 * PI * lon as f32 / lon_segs as f32;
verts.push([r * theta.cos(), y, r * theta.sin()]);
}
}
verts.push([0.0, -radius, 0.0]);
let south_pole_idx = (verts.len() - 1) as u32;
let mut tris: Vec<[u32; 3]> = Vec::new();
for lon in 0..lon_segs {
let next_lon = (lon + 1) % lon_segs;
tris.push([0, 1 + lon, 1 + next_lon]);
}
for lat in 0..(lat_segs - 2) {
for lon in 0..lon_segs {
let next_lon = (lon + 1) % lon_segs;
let a = 1 + lat * lon_segs + lon;
let b = 1 + lat * lon_segs + next_lon;
let c = 1 + (lat + 1) * lon_segs + lon;
let d = 1 + (lat + 1) * lon_segs + next_lon;
tris.push([a, b, c]);
tris.push([b, d, c]);
}
}
let last_ring_start = 1 + (lat_segs - 2) * lon_segs;
for lon in 0..lon_segs {
let next_lon = (lon + 1) % lon_segs;
tris.push([last_ring_start + lon, south_pole_idx, last_ring_start + next_lon]);
}
(verts, tris)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sphere_vert_count() {
assert_eq!(sphere_vert_count(4, 8), 2 + 3 * 8);
}
#[test]
fn test_make_uv_sphere_vert_count() {
let (verts, _) = make_uv_sphere(1.0, 4, 8);
assert_eq!(verts.len(), sphere_vert_count(4, 8));
}
#[test]
fn test_make_uv_sphere_empty() {
let (verts, tris) = make_uv_sphere(1.0, 1, 8);
assert!(verts.is_empty());
assert!(tris.is_empty());
}
#[test]
fn test_surface_area_unit_sphere() {
let area = sphere_surface_area(1.0);
let expected = 4.0 * PI;
assert!((area - expected).abs() < 1e-4);
}
#[test]
fn test_surface_area_scales() {
let r = 2.0f32;
let area = sphere_surface_area(r);
let expected = 4.0 * PI * r * r;
assert!((area - expected).abs() < 1e-4);
}
#[test]
fn test_sphere_uv_coords_length() {
let (verts, _) = make_uv_sphere(1.0, 6, 12);
let uvs = sphere_uv_coords(&verts);
assert_eq!(uvs.len(), verts.len());
}
#[test]
fn test_sphere_uv_range() {
let (verts, _) = make_uv_sphere(1.0, 6, 12);
let uvs = sphere_uv_coords(&verts);
for uv in &uvs {
assert!((0.0..=1.0).contains(&uv[0]));
assert!((0.0..=1.0).contains(&uv[1]));
}
}
#[test]
fn test_indices_in_range() {
let (verts, tris) = make_uv_sphere(1.0, 6, 8);
let nv = verts.len() as u32;
for tri in &tris {
assert!(tri[0] < nv);
assert!(tri[1] < nv);
assert!(tri[2] < nv);
}
}
#[test]
fn test_poles_on_axis() {
let (verts, _) = make_uv_sphere(1.0, 4, 8);
assert!((verts[0][1] - 1.0).abs() < 1e-5);
let last = verts.last().expect("should succeed");
assert!((last[1] + 1.0).abs() < 1e-5);
}
}