//! Plane primitive — horizontal plane in XZ with subdivisions. use crate::core::geometry::Geometry; /// Generates a horizontal plane in the XZ plane (normal +Y), centered at (0, 0, 0), with /// `width` × `depth` dimensions, subdivided into `seg_x` × `seg_z` cells. pub fn plane(width: f32, depth: f32, seg_x: u32, seg_z: u32) -> Geometry { let sx = seg_x.max(1); let sz = seg_z.max(1); let (mut positions, mut normals, mut uvs, mut indices) = ( Vec::<[f32; 3]>::new(), Vec::<[f32; 3]>::new(), Vec::<[f32; 2]>::new(), Vec::::new(), ); for z in 0..=sz { let vz = z as f32 / sz as f32; for x in 0..=sx { let vx = x as f32 / sx as f32; positions.push([(vx - 0.5) * width, 0.0, (vz - 0.5) * depth]); normals.push([0.0, 1.0, 0.0]); uvs.push([vx, vz]); } } for z in 0..sz { for x in 0..sx { let a = z * (sx + 1) + x; let b = a + 1; let c = (z + 1) * (sx + 1) + x; let d = c + 1; indices .extend_from_slice(&[a as u16, c as u16, b as u16, b as u16, c as u16, d as u16]); } } Geometry::new(positions) .with_normals(normals) .with_uvs(uvs) .with_indices(indices) } #[cfg(test)] mod tests { use super::*; use glam::Vec3; fn assert_valid(geo: &Geometry) { geo.validate().expect("generated geometry must validate"); let positions = &geo.positions; let normals = geo.normals.as_ref().expect("normals present"); let uvs = geo.uvs.as_ref().expect("uvs present"); let indices = geo.indices.as_ref().expect("indices present"); assert_eq!(normals.len(), positions.len()); assert_eq!(uvs.len(), positions.len()); for n in normals { let len = Vec3::from_array(*n).length(); assert!((len - 1.0).abs() < 1e-3); } for &i in indices { assert!((i as usize) < positions.len()); } } #[test] fn plane_counts() { let g = plane(2.0, 3.0, 1, 1); assert_eq!(g.positions.len(), 4); assert_eq!(g.indices.as_ref().unwrap().len(), 6); assert_valid(&g); assert!(g.positions.iter().all(|p| p[1] == 0.0)); let g2 = plane(2.0, 3.0, 4, 5); assert_eq!(g2.positions.len(), (4 + 1) * (5 + 1)); assert_valid(&g2); } }