use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LodLevel {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Copy)]
pub struct Vertex {
pub position: [f32; 3],
pub normal: [f32; 3],
pub uv: [f32; 2],
}
#[derive(Debug, Clone)]
pub struct MeshData {
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
}
impl MeshData {
pub fn vertex_count(&self) -> usize {
self.vertices.len()
}
pub fn triangle_count(&self) -> usize {
self.indices.len() / 3
}
pub fn positions_flat(&self) -> Vec<f32> {
self.vertices.iter().flat_map(|v| v.position).collect()
}
pub fn normals_flat(&self) -> Vec<f32> {
self.vertices.iter().flat_map(|v| v.normal).collect()
}
pub fn uvs_flat(&self) -> Vec<f32> {
self.vertices.iter().flat_map(|v| v.uv).collect()
}
}
fn subdivisions_for_lod(lod: LodLevel) -> u32 {
match lod {
LodLevel::Low => 2,
LodLevel::Medium => 4,
LodLevel::High => 6,
}
}
fn midpoint_index(
vertices: &mut Vec<[f32; 3]>,
cache: &mut HashMap<u64, u32>,
i1: u32,
i2: u32,
) -> u32 {
let key = if i1 < i2 {
(i1 as u64) << 32 | i2 as u64
} else {
(i2 as u64) << 32 | i1 as u64
};
if let Some(&idx) = cache.get(&key) {
return idx;
}
let a = vertices[i1 as usize];
let b = vertices[i2 as usize];
let mid = [
(a[0] + b[0]) * 0.5,
(a[1] + b[1]) * 0.5,
(a[2] + b[2]) * 0.5,
];
let len = (mid[0] * mid[0] + mid[1] * mid[1] + mid[2] * mid[2]).sqrt();
let normalized = [mid[0] / len, mid[1] / len, mid[2] / len];
let idx = vertices.len() as u32;
vertices.push(normalized);
cache.insert(key, idx);
idx
}
fn build_icosphere(subdivisions: u32) -> (Vec<[f32; 3]>, Vec<u32>) {
let t = (1.0 + 5.0_f32.sqrt()) / 2.0;
let mut vertices: Vec<[f32; 3]> = vec![
[-1.0, t, 0.0],
[1.0, t, 0.0],
[-1.0, -t, 0.0],
[1.0, -t, 0.0],
[0.0, -1.0, t],
[0.0, 1.0, t],
[0.0, -1.0, -t],
[0.0, 1.0, -t],
[t, 0.0, -1.0],
[t, 0.0, 1.0],
[-t, 0.0, -1.0],
[-t, 0.0, 1.0],
];
for v in &mut vertices {
let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
v[0] /= len;
v[1] /= len;
v[2] /= len;
}
let mut indices: Vec<u32> = vec![
0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7,
1, 8, 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9,
8, 1,
];
for _ in 0..subdivisions {
let mut cache: HashMap<u64, u32> = HashMap::new();
let mut new_indices = Vec::with_capacity(indices.len() * 4);
for tri in indices.chunks(3) {
let a = tri[0];
let b = tri[1];
let c = tri[2];
let ab = midpoint_index(&mut vertices, &mut cache, a, b);
let bc = midpoint_index(&mut vertices, &mut cache, b, c);
let ca = midpoint_index(&mut vertices, &mut cache, c, a);
new_indices.extend_from_slice(&[a, ab, ca]);
new_indices.extend_from_slice(&[b, bc, ab]);
new_indices.extend_from_slice(&[c, ca, bc]);
new_indices.extend_from_slice(&[ab, bc, ca]);
}
indices = new_indices;
}
(vertices, indices)
}
fn sphere_uv(normal: &[f32; 3]) -> [f32; 2] {
let u = 0.5 + normal[2].atan2(normal[0]) / (2.0 * std::f32::consts::PI);
let v = 0.5 - normal[1].asin() / std::f32::consts::PI;
[u, v]
}
pub fn generate_sphere(radius: f32, lod: LodLevel) -> MeshData {
let subdivisions = subdivisions_for_lod(lod);
generate_sphere_subdivisions(radius, subdivisions)
}
pub fn generate_sphere_subdivisions(radius: f32, subdivisions: u32) -> MeshData {
let (unit_verts, indices) = build_icosphere(subdivisions);
let vertices = unit_verts
.iter()
.map(|n| Vertex {
position: [n[0] * radius, n[1] * radius, n[2] * radius],
normal: *n,
uv: sphere_uv(n),
})
.collect();
MeshData { vertices, indices }
}
pub fn generate_oblate_sphere(
equatorial_radius: f32,
polar_radius: f32,
lod: LodLevel,
) -> MeshData {
let subdivisions = subdivisions_for_lod(lod);
generate_oblate_sphere_subdivisions(equatorial_radius, polar_radius, subdivisions)
}
pub fn generate_oblate_sphere_subdivisions(
equatorial_radius: f32,
polar_radius: f32,
subdivisions: u32,
) -> MeshData {
let (unit_verts, indices) = build_icosphere(subdivisions);
let eq2 = equatorial_radius * equatorial_radius;
let po2 = polar_radius * polar_radius;
let vertices = unit_verts
.iter()
.map(|n| {
let px = n[0] * equatorial_radius;
let py = n[1] * polar_radius;
let pz = n[2] * equatorial_radius;
let nx = n[0] / eq2;
let ny = n[1] / po2;
let nz = n[2] / eq2;
let nlen = (nx * nx + ny * ny + nz * nz).sqrt();
Vertex {
position: [px, py, pz],
normal: [nx / nlen, ny / nlen, nz / nlen],
uv: sphere_uv(n),
}
})
.collect();
MeshData { vertices, indices }
}
pub fn generate_ring(
inner_radius: f32,
outer_radius: f32,
segments: u32,
radial_segments: u32,
) -> MeshData {
let mut vertices = Vec::new();
let mut indices = Vec::new();
for r in 0..=radial_segments {
let t = r as f32 / radial_segments as f32;
let radius = inner_radius + t * (outer_radius - inner_radius);
for s in 0..=segments {
let theta = s as f32 / segments as f32 * 2.0 * std::f32::consts::PI;
let x = radius * theta.cos();
let z = radius * theta.sin();
vertices.push(Vertex {
position: [x, 0.0, z],
normal: [0.0, 1.0, 0.0],
uv: [s as f32 / segments as f32, t],
});
}
}
let stride = segments + 1;
for r in 0..radial_segments {
for s in 0..segments {
let i0 = r * stride + s;
let i1 = i0 + 1;
let i2 = i0 + stride;
let i3 = i2 + 1;
indices.extend_from_slice(&[i0, i2, i1, i1, i2, i3]);
}
}
MeshData { vertices, indices }
}