From 77cab94a6e50e8247c4e1a9c050facfeaadbb59c Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 17:10:12 +0300 Subject: [PATCH] add basic shape generation --- src/lib.rs | 1 + src/shape.rs | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 src/shape.rs diff --git a/src/lib.rs b/src/lib.rs index 18937dd..ce55aa2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ use crate::{ mod camera; mod ray; mod render; +mod shape; mod trace; const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; diff --git a/src/shape.rs b/src/shape.rs new file mode 100644 index 0000000..e1b5865 --- /dev/null +++ b/src/shape.rs @@ -0,0 +1,140 @@ +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 } +} + +#[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], + ] + ); + } +}