use glam::{Vec2, Vec3};
use crate::mesh::MeshData;
pub fn generate_sphere(radius: f32, sectors: u32, rings: u32) -> MeshData {
let sector_count = sectors as usize;
let ring_count = rings as usize;
let vert_count = (sector_count + 1) * (ring_count + 1);
let mut positions = Vec::with_capacity(vert_count);
let mut normals = Vec::with_capacity(vert_count);
let mut texcoords = Vec::with_capacity(vert_count);
let mut tangents = Vec::with_capacity(vert_count);
let mut indices = Vec::with_capacity(sector_count * ring_count * 6);
for i in 0..=ring_count {
let phi = std::f32::consts::PI * i as f32 / ring_count as f32;
let sin_phi = phi.sin();
let cos_phi = phi.cos();
for j in 0..=sector_count {
let theta = 2.0 * std::f32::consts::PI * j as f32 / sector_count as f32;
let sin_theta = theta.sin();
let cos_theta = theta.cos();
let x = sin_phi * cos_theta;
let y = cos_phi;
let z = sin_phi * sin_theta;
positions.push(Vec3::new(radius * x, radius * y, radius * z));
normals.push(Vec3::new(x, y, z));
texcoords.push(Vec2::new(
j as f32 / sector_count as f32,
i as f32 / ring_count as f32,
));
let tx = -sin_theta;
let tz = cos_theta;
let t_len = (tx * tx + tz * tz).sqrt().max(1e-6);
tangents.push([tx / t_len, 0.0, tz / t_len, 1.0]);
}
}
let stride = (sector_count + 1) as u32;
for i in 0..rings {
for j in 0..sectors {
let top_left = i * stride + j;
let bottom_left = (i + 1) * stride + j;
indices.push(top_left);
indices.push(bottom_left);
indices.push(top_left + 1);
indices.push(top_left + 1);
indices.push(bottom_left);
indices.push(bottom_left + 1);
}
}
MeshData {
positions,
normals,
texcoords,
tangents,
indices,
}
}
pub fn generate_plane(width: f32, depth: f32) -> MeshData {
let hw = width * 0.5;
let hd = depth * 0.5;
MeshData {
positions: vec![
Vec3::new(-hw, 0.0, -hd),
Vec3::new(hw, 0.0, -hd),
Vec3::new(hw, 0.0, hd),
Vec3::new(-hw, 0.0, hd),
],
normals: vec![Vec3::Y; 4],
texcoords: vec![
Vec2::new(0.0, 0.0),
Vec2::new(1.0, 0.0),
Vec2::new(1.0, 1.0),
Vec2::new(0.0, 1.0),
],
tangents: vec![[1.0, 0.0, 0.0, 1.0]; 4],
indices: vec![0, 1, 2, 0, 2, 3],
}
}
pub fn generate_box(half_extents: [f32; 3]) -> MeshData {
let [hx, hy, hz] = half_extents;
let faces: [(Vec3, [f32; 4], [Vec3; 4]); 6] = [
(
Vec3::Z,
[1.0, 0.0, 0.0, 1.0],
[
Vec3::new(-hx, -hy, hz),
Vec3::new(hx, -hy, hz),
Vec3::new(hx, hy, hz),
Vec3::new(-hx, hy, hz),
],
),
(
Vec3::NEG_Z,
[-1.0, 0.0, 0.0, 1.0],
[
Vec3::new(hx, -hy, -hz),
Vec3::new(-hx, -hy, -hz),
Vec3::new(-hx, hy, -hz),
Vec3::new(hx, hy, -hz),
],
),
(
Vec3::X,
[0.0, 0.0, 1.0, 1.0],
[
Vec3::new(hx, -hy, hz),
Vec3::new(hx, -hy, -hz),
Vec3::new(hx, hy, -hz),
Vec3::new(hx, hy, hz),
],
),
(
Vec3::NEG_X,
[0.0, 0.0, -1.0, 1.0],
[
Vec3::new(-hx, -hy, -hz),
Vec3::new(-hx, -hy, hz),
Vec3::new(-hx, hy, hz),
Vec3::new(-hx, hy, -hz),
],
),
(
Vec3::Y,
[1.0, 0.0, 0.0, 1.0],
[
Vec3::new(-hx, hy, hz),
Vec3::new(hx, hy, hz),
Vec3::new(hx, hy, -hz),
Vec3::new(-hx, hy, -hz),
],
),
(
Vec3::NEG_Y,
[1.0, 0.0, 0.0, 1.0],
[
Vec3::new(-hx, -hy, -hz),
Vec3::new(hx, -hy, -hz),
Vec3::new(hx, -hy, hz),
Vec3::new(-hx, -hy, hz),
],
),
];
let face_uvs: [Vec2; 4] = [
Vec2::new(0.0, 1.0),
Vec2::new(1.0, 1.0),
Vec2::new(1.0, 0.0),
Vec2::new(0.0, 0.0),
];
let mut positions = Vec::with_capacity(24);
let mut normals_vec = Vec::with_capacity(24);
let mut texcoords = Vec::with_capacity(24);
let mut tangents = Vec::with_capacity(24);
let mut indices = Vec::with_capacity(36);
for (face_idx, (normal, tangent, face_positions)) in faces.iter().enumerate() {
let base = (face_idx * 4) as u32;
for (v_idx, pos) in face_positions.iter().enumerate() {
positions.push(*pos);
normals_vec.push(*normal);
texcoords.push(face_uvs[v_idx]);
tangents.push(*tangent);
}
indices.extend_from_slice(&[base, base + 1, base + 2, base, base + 2, base + 3]);
}
MeshData {
positions,
normals: normals_vec,
texcoords,
tangents,
indices,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sphere_vertex_count() {
let sectors = 24u32;
let rings = 16u32;
let mesh = generate_sphere(1.0, sectors, rings);
let expected_verts = (sectors + 1) as usize * (rings + 1) as usize;
assert_eq!(mesh.vertex_count(), expected_verts);
let expected_indices = (sectors * rings * 6) as usize;
assert_eq!(mesh.index_count(), expected_indices);
}
#[test]
fn test_sphere_normals_match_position_direction() {
let mesh = generate_sphere(2.0, 12, 8);
for (pos, normal) in mesh.positions.iter().zip(mesh.normals.iter()) {
if pos.length() > 1e-4 {
let expected_normal = pos.normalize();
let dot = normal.dot(expected_normal);
assert!(
dot > 0.99,
"Normal {:?} doesn't match position direction {:?}, dot={}",
normal, expected_normal, dot
);
}
}
}
#[test]
fn test_sphere_small() {
let mesh = generate_sphere(0.5, 4, 2);
assert_eq!(mesh.vertex_count(), 5 * 3);
assert_eq!(mesh.index_count(), 4 * 2 * 6);
}
#[test]
fn test_plane_basic() {
let mesh = generate_plane(10.0, 10.0);
assert_eq!(mesh.vertex_count(), 4);
assert_eq!(mesh.index_count(), 6);
for pos in &mesh.positions {
assert_eq!(pos.y, 0.0);
}
for normal in &mesh.normals {
assert_eq!(*normal, Vec3::Y);
}
}
#[test]
fn test_box_basic() {
let mesh = generate_box([0.5, 0.5, 0.5]);
assert_eq!(mesh.vertex_count(), 24);
assert_eq!(mesh.index_count(), 36);
}
#[test]
fn test_box_normals_unit_length() {
let mesh = generate_box([1.0, 2.0, 3.0]);
for normal in &mesh.normals {
let len = normal.length();
assert!(
(len - 1.0).abs() < 1e-5,
"Normal {:?} has length {}",
normal, len
);
}
}
#[test]
fn test_box_asymmetric() {
let mesh = generate_box([1.0, 0.5, 2.0]);
assert_eq!(mesh.vertex_count(), 24);
assert_eq!(mesh.index_count(), 36);
for pos in &mesh.positions {
assert!(pos.x.abs() <= 1.0 + 1e-5);
assert!(pos.y.abs() <= 0.5 + 1e-5);
assert!(pos.z.abs() <= 2.0 + 1e-5);
}
}
}