From 98f3c44ab3e944c331791675f8afe31dbe933868 Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 25 Nov 2025 12:40:26 +0300 Subject: [PATCH] add super-duper complicated sphere mesh generator --- src/lib.rs | 2 +- src/shape.rs | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4393e0e..ddd026c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -384,7 +384,7 @@ impl Core { if args.show_shapes { let mut meshes = Vec::new(); for obj in &scene.objects { - let mesh = shape::sphere((obj.radius * 45.) as usize + 7); + let mesh = shape::octo_sphere((obj.radius * 45.) as usize + 7); let obj_mesh = render::faces::Mesh::new( &self.device, &mesh diff --git a/src/shape.rs b/src/shape.rs index e1b5865..311076d 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -60,6 +60,114 @@ pub fn sphere(subdiv: usize) -> Mesh { 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; @@ -137,4 +245,104 @@ mod tests { ] ); } + + #[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], + ] + ); + } }