use std::f32::consts::PI; use glam::{Vec3, vec3}; pub type Face = [u16; 3]; #[derive(Debug, Clone)] pub struct Mesh { pub vertices: Vec, pub indices: Vec, } pub fn sphere(subdiv: usize) -> Mesh { assert!(subdiv >= 2); assert!(2 * subdiv * (subdiv - 1) + 2 <= 1 << 16); let mut vertices = Vec::new(); vertices.push(Vec3::Z); for j in 1..subdiv { let v = j as f32 / subdiv as f32; let theta = PI * v; let (xy, z) = theta.sin_cos(); for i in 0..2 * subdiv { let u = i as f32 / (2 * subdiv) as f32; let phi = 2. * PI * u; let (y, x) = phi.sin_cos(); vertices.push(vec3(xy * x, xy * y, z)); } } vertices.push(-Vec3::Z); assert_eq!(vertices.len(), 2 * subdiv * (subdiv - 1) + 2); let mut indices = Vec::new(); let subdiv = subdiv as u16; for i in 0..2 * subdiv - 1 { indices.push([0, i + 1, i + 2]); } indices.push([0, 2 * subdiv, 1]); for j in 0..subdiv - 2 { let k1 = j * 2 * subdiv + 1; let k2 = k1 + 2 * subdiv; for i in 0..2 * subdiv - 1 { let a = k1 + i; let b = k2 + i; indices.push([a, b, b + 1]); indices.push([a, b + 1, a + 1]); } let a = k1 + 2 * subdiv - 1; let b = k2 + 2 * subdiv - 1; indices.push([a, b, k2]); indices.push([a, k2, k1]); } let k1 = 2 * subdiv * (subdiv - 2) + 1; let k2 = 2 * subdiv * (subdiv - 1) + 1; for i in 0..2 * subdiv - 1 { indices.push([k1 + i, k2, k1 + i + 1]); } indices.push([k1 + 2 * subdiv - 1, k2, k1]); Mesh { vertices, indices } } pub fn octo_sphere(subdiv: usize) -> Mesh { assert!(subdiv >= 2); let mut vertices = Vec::new(); let theta_step = PI / subdiv as f32; let v_belt = |j: usize, n: usize| { let phi_step = 2. * PI / n as f32; let theta = j as f32 * theta_step; let (xy, z) = theta.sin_cos(); (0..n).map(move |i| { let phi = i as f32 * phi_step; let (y, x) = phi.sin_cos(); vec3(xy * x, xy * y, z) }) }; vertices.push(Vec3::Z); for j in 1..subdiv / 2 { vertices.extend(v_belt(j, 4 * j)); } { let j = subdiv / 2; vertices.extend(v_belt(j, 4 * j)); } for j in (subdiv + 2) / 2..subdiv { vertices.extend(v_belt(j, 4 * (subdiv - j))); } vertices.push(-Vec3::Z); fn i_cap(top: usize, start: usize) -> impl Iterator { gen move { yield [top, start, start + 1]; yield [top, start + 1, start + 2]; yield [top, start + 2, start + 3]; yield [top, start + 3, start]; } .map(face) } fn i_skew_belt( short_start: usize, long_start: usize, seg_short_face: usize, ) -> impl Iterator { gen move { let seg_long_face = seg_short_face + 1; let n_short_total = 4 * seg_short_face; let n_long_total = 4 * seg_long_face; for face in 0..4 { let short = move |k| short_start + (face * seg_short_face + k) % n_short_total; let long = move |k| long_start + (face * seg_long_face + k) % n_long_total; yield [short(0), long(0), long(1)]; for k in 0..seg_short_face { yield [short(k), long(k + 1), short(k + 1)]; yield [short(k + 1), long(k + 1), long(k + 2)]; } } } .map(face) } fn i_belt(start1: usize, start2: usize, seg_per_face: usize) -> impl Iterator { gen move { let n = 4 * seg_per_face; for k in 0..n { let k2 = (k + 1) % n; let a = start1 + k; let b = start2 + k; let c = start1 + k2; let d = start2 + k2; yield [a, b, c]; yield [c, b, d]; } } .map(face) } fn face(f: [usize; 3]) -> Face { f.map(|i| i as u16) } fn flip(f: Face) -> Face { [f[1], f[0], f[2]] } let mut indices = Vec::new(); indices.extend(i_cap(0, 1)); let mut k = 1; for j in 1..subdiv - 1 { let j2 = (subdiv - 1) - j; match j.cmp(&j2) { std::cmp::Ordering::Less => { let n = 4 * j; indices.extend(i_skew_belt(k, k + n, j)); k += n; } std::cmp::Ordering::Equal => { let n = 4 * j; indices.extend(i_belt(k, k + n, j)); k += n; } std::cmp::Ordering::Greater => { let n = 4 * (j2 + 1); indices.extend(i_skew_belt(k + n, k, j2).map(flip)); k += n; } } } indices.extend(i_cap(k + 4, k).map(flip)); Mesh { vertices, indices } } #[cfg(test)] mod tests { use approx::abs_diff_eq; use super::*; #[test] fn test_sphere_2_topology() { assert_eq!( sphere(2).indices, vec![ [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [1, 5, 2], [2, 5, 3], [3, 5, 4], [4, 5, 1], ] ); } fn are_equal_eps(left: &[Vec3], right: &[Vec3], eps: f32) -> bool { if left.len() != right.len() { return false; } left.iter() .zip(right.iter()) .all(|(a, b)| abs_diff_eq!(a, b, epsilon = eps)) } #[test] fn test_sphere_2_geometry() { let left = sphere(2).vertices; let right = [Vec3::Z, Vec3::X, Vec3::Y, -Vec3::X, -Vec3::Y, -Vec3::Z]; assert!( are_equal_eps(&left, &right, 1e-6), "assertion `left == right` failed\n left: {left:?}\n right: {right:?}" ); } #[test] fn test_sphere_3_topology() { assert_eq!( sphere(3).indices, vec![ // top [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 5], [0, 5, 6], [0, 6, 1], // belt [1, 7, 8], [1, 8, 2], [2, 8, 9], [2, 9, 3], [3, 9, 10], [3, 10, 4], [4, 10, 11], [4, 11, 5], [5, 11, 12], [5, 12, 6], [6, 12, 7], [6, 7, 1], // bottom [7, 13, 8], [8, 13, 9], [9, 13, 10], [10, 13, 11], [11, 13, 12], [12, 13, 7], ] ); } #[test] fn test_octo_sphere_2_topology() { assert_eq!( octo_sphere(2).indices, vec![ [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], [1, 5, 2], [2, 5, 3], [3, 5, 4], [4, 5, 1], ] ); } #[test] fn test_octo_sphere_2_geometry() { let left = octo_sphere(2).vertices; let right = [Vec3::Z, Vec3::X, Vec3::Y, -Vec3::X, -Vec3::Y, -Vec3::Z]; assert!( are_equal_eps(&left, &right, 1e-6), "assertion `left == right` failed\n left: {left:?}\n right: {right:?}" ); } #[test] fn test_octo_sphere_3_topology() { assert_eq!( octo_sphere(3).indices, vec![ // top [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], // belt [1, 5, 2], [2, 5, 6], [2, 6, 3], [3, 6, 7], [3, 7, 4], [4, 7, 8], [4, 8, 1], [1, 8, 5], // bottom [5, 9, 6], [6, 9, 7], [7, 9, 8], [8, 9, 5], ] ); } #[test] fn test_octo_sphere_4_topology() { assert_eq!( octo_sphere(4).indices, vec![ // top [0, 1, 2], [0, 2, 3], [0, 3, 4], [0, 4, 1], // belt 1 [1, 5, 6], [1, 6, 2], [2, 6, 7], [2, 7, 8], [2, 8, 3], [3, 8, 9], [3, 9, 10], [3, 10, 4], [4, 10, 11], [4, 11, 12], [4, 12, 1], [1, 12, 5], // belt 2 [5, 13, 6], [6, 13, 14], [6, 14, 7], [7, 14, 8], [8, 14, 15], [8, 15, 9], [9, 15, 10], [10, 15, 16], [10, 16, 11], [11, 16, 12], [12, 16, 13], [12, 13, 5], // bottom [13, 17, 14], [14, 17, 15], [15, 17, 16], [16, 17, 13], ] ); } }