use super::*;
use std::f32::consts::*;
fn write_bin<T: dataview::Pod>(path: &std::path::Path, slice: &[T]) {
std::fs::write(path, dataview::bytes(slice)).unwrap();
}
fn icosahedron_base() -> ([Vec3f; 12], [[u8; 3]; 20]) {
let lat = (0.5_f32).atan();
let y_top = lat.sin();
let r_top = lat.cos();
let y_bot = -y_top;
let r_bot = r_top;
let mut positions = [Vec3f::ZERO; 12];
positions[0] = Vec3f(0.0, 1.0, 0.0);
for i in 0..5 {
let angle = (i as f32) * 2.0 * PI / 5.0;
positions[1 + i] = Vec3f(r_top * angle.sin(), y_top, r_top * angle.cos());
}
for i in 0..5 {
let angle = (i as f32 + 0.5) * 2.0 * PI / 5.0;
positions[6 + i] = Vec3f(r_bot * angle.sin(), y_bot, r_bot * angle.cos());
}
positions[11] = Vec3f(0.0, -1.0, 0.0);
for p in &mut positions {
*p = p.norm();
}
let triangles: [[u8; 3]; 20] = [
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
[0, 4, 5],
[0, 5, 1],
[1, 6, 2],
[2, 6, 7],
[2, 7, 3],
[3, 7, 8],
[3, 8, 4],
[4, 8, 9],
[4, 9, 5],
[5, 9, 10],
[5, 10, 1],
[1, 10, 6],
[11, 7, 6],
[11, 8, 7],
[11, 9, 8],
[11, 10, 9],
[11, 6, 10],
];
(positions, triangles)
}
fn spherical_uv(dir: Vec3f) -> Vec2f {
let u = 0.5 + dir.z.atan2(dir.x) / (2.0 * PI);
let v = 0.5 + dir.y.asin() / PI;
Vec2f(u, v)
}
fn is_pole(pos: Vec3f) -> bool {
pos.x.abs() < 1e-6 && pos.z.abs() < 1e-6
}
fn triangle_uvs(a: Vec3f, b: Vec3f, c: Vec3f) -> [Vec2f; 3] {
let mut uva = spherical_uv(a);
let mut uvb = spherical_uv(b);
let mut uvc = spherical_uv(c);
let a_pole = is_pole(a);
let b_pole = is_pole(b);
let c_pole = is_pole(c);
let non_pole_us: Vec<f32> = [
if !a_pole { Some(uva.x) } else { None },
if !b_pole { Some(uvb.x) } else { None },
if !c_pole { Some(uvc.x) } else { None },
]
.into_iter()
.flatten()
.collect();
if non_pole_us.len() >= 2 {
let min_u = non_pole_us.iter().cloned().reduce(f32::min).unwrap();
let max_u = non_pole_us.iter().cloned().reduce(f32::max).unwrap();
if max_u - min_u > 0.5 {
if !a_pole && uva.x < 0.5 {
uva.x += 1.0;
}
if !b_pole && uvb.x < 0.5 {
uvb.x += 1.0;
}
if !c_pole && uvc.x < 0.5 {
uvc.x += 1.0;
}
}
}
if a_pole || b_pole || c_pole {
let mut sum = 0.0;
let mut count = 0;
if !a_pole {
sum += uva.x;
count += 1;
}
if !b_pole {
sum += uvb.x;
count += 1;
}
if !c_pole {
sum += uvc.x;
count += 1;
}
let avg_u = if count > 0 { sum / count as f32 } else { 0.5 };
if a_pole {
uva.x = avg_u;
}
if b_pole {
uvb.x = avg_u;
}
if c_pole {
uvc.x = avg_u;
}
}
[uva, uvb, uvc]
}
fn generate_smooth() -> (Vec<TexturedVertexN>, Vec<u8>) {
let (positions, triangles) = icosahedron_base();
let mut vertices: Vec<TexturedVertexN> = Vec::new();
let mut indices: Vec<u8> = Vec::new();
use std::collections::HashMap;
let mut vertex_map: HashMap<(u8, i32, i32), u8> = HashMap::new();
for tri in triangles {
let pa = positions[tri[0] as usize];
let pb = positions[tri[1] as usize];
let pc = positions[tri[2] as usize];
let uvs = triangle_uvs(pa, pb, pc);
for (i, &pos_idx) in tri.iter().enumerate() {
let pos = positions[pos_idx as usize];
let uv = uvs[i];
let qu = (uv.x * 1000.0).round() as i32;
let qv = (uv.y * 1000.0).round() as i32;
let key = (pos_idx, qu, qv);
let vert_idx = if let Some(&idx) = vertex_map.get(&key) {
idx
} else {
let idx = vertices.len() as u8;
vertices.push(TexturedVertexN {
pos,
normal: pos, uv,
});
vertex_map.insert(key, idx);
idx
};
indices.push(vert_idx);
}
}
(vertices, indices)
}
fn generate_flat() -> Vec<TexturedVertexN> {
let (positions, triangles) = icosahedron_base();
let mut vertices = Vec::with_capacity(60);
for tri in triangles {
let a = positions[tri[0] as usize];
let b = positions[tri[1] as usize];
let c = positions[tri[2] as usize];
let normal = (b - a).cross(c - a).norm();
let uvs = triangle_uvs(a, b, c);
vertices.push(TexturedVertexN { pos: a, normal, uv: uvs[0] });
vertices.push(TexturedVertexN { pos: b, normal, uv: uvs[1] });
vertices.push(TexturedVertexN { pos: c, normal, uv: uvs[2] });
}
vertices
}
#[test]
#[ignore]
fn generate_icosahedron_bins() {
let (verts, inds) = generate_smooth();
assert_eq!(inds.len(), 60);
assert!(inds.iter().all(|&i| (i as usize) < verts.len()));
let flat_verts = generate_flat();
assert_eq!(flat_verts.len(), 60);
let out_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join(std::path::Path::new(file!()).parent().unwrap());
write_bin(&out_dir.join("vertices.bin"), &verts);
write_bin(&out_dir.join("indices.bin"), &inds);
write_bin(&out_dir.join("flat_vertices.bin"), &flat_verts);
}