#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlatonicKind {
Tetrahedron,
Cube,
Octahedron,
Dodecahedron,
Icosahedron,
}
#[derive(Debug, Clone)]
pub struct PlatonicSolid {
pub kind: PlatonicKind,
pub verts: Vec<[f32; 3]>,
pub tris: Vec<[u32; 3]>,
}
fn normalize(v: [f32; 3]) -> [f32; 3] {
let len = (v[0].powi(2) + v[1].powi(2) + v[2].powi(2)).sqrt();
if len < 1e-9 {
return v;
}
[v[0] / len, v[1] / len, v[2] / len]
}
pub fn build_platonic_solid(kind: PlatonicKind) -> PlatonicSolid {
match kind {
PlatonicKind::Tetrahedron => build_tetrahedron(),
PlatonicKind::Cube => build_cube(),
PlatonicKind::Octahedron => build_octahedron(),
PlatonicKind::Dodecahedron => build_dodecahedron(),
PlatonicKind::Icosahedron => build_icosahedron(),
}
}
fn build_tetrahedron() -> PlatonicSolid {
let s = 1.0f32 / 3.0f32.sqrt();
let verts: Vec<[f32; 3]> = [
[1.0, 1.0, 1.0],
[1.0, -1.0, -1.0],
[-1.0, 1.0, -1.0],
[-1.0, -1.0, 1.0],
]
.iter()
.map(|&v| normalize([v[0] * s, v[1] * s, v[2] * s]))
.collect();
let tris = vec![[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]];
PlatonicSolid {
kind: PlatonicKind::Tetrahedron,
verts,
tris,
}
}
fn build_cube() -> PlatonicSolid {
let s = 1.0f32 / 3.0f32.sqrt();
let verts: Vec<[f32; 3]> = [
[-1.0, -1.0, -1.0],
[1.0, -1.0, -1.0],
[1.0, 1.0, -1.0],
[-1.0, 1.0, -1.0],
[-1.0, -1.0, 1.0],
[1.0, -1.0, 1.0],
[1.0, 1.0, 1.0],
[-1.0, 1.0, 1.0],
]
.iter()
.map(|&v| [v[0] * s, v[1] * s, v[2] * s])
.collect();
let tris = vec![
[0, 2, 1],
[0, 3, 2],
[4, 5, 6],
[4, 6, 7],
[0, 1, 5],
[0, 5, 4],
[2, 3, 7],
[2, 7, 6],
[0, 4, 7],
[0, 7, 3],
[1, 2, 6],
[1, 6, 5],
];
PlatonicSolid {
kind: PlatonicKind::Cube,
verts,
tris,
}
}
fn build_octahedron() -> PlatonicSolid {
let verts = vec![
[1.0, 0.0, 0.0],
[-1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, -1.0, 0.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, -1.0],
];
let tris = vec![
[0, 2, 4],
[2, 1, 4],
[1, 3, 4],
[3, 0, 4],
[0, 5, 2],
[2, 5, 1],
[1, 5, 3],
[3, 5, 0],
];
PlatonicSolid {
kind: PlatonicKind::Octahedron,
verts,
tris,
}
}
fn build_icosahedron() -> PlatonicSolid {
let phi = (1.0 + 5.0f32.sqrt()) / 2.0;
let verts: Vec<[f32; 3]> = [
[-1.0, phi, 0.0],
[1.0, phi, 0.0],
[-1.0, -phi, 0.0],
[1.0, -phi, 0.0],
[0.0, -1.0, phi],
[0.0, 1.0, phi],
[0.0, -1.0, -phi],
[0.0, 1.0, -phi],
[phi, 0.0, -1.0],
[phi, 0.0, 1.0],
[-phi, 0.0, -1.0],
[-phi, 0.0, 1.0],
]
.iter()
.map(|&v| normalize(v))
.collect();
let tris = 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],
];
PlatonicSolid {
kind: PlatonicKind::Icosahedron,
verts,
tris,
}
}
fn build_dodecahedron() -> PlatonicSolid {
let phi = (1.0f32 + 5.0f32.sqrt()) / 2.0;
let inv_phi = 1.0 / phi;
let raw: [[f32; 3]; 20] = [
[1.0, 1.0, 1.0], [1.0, 1.0, -1.0], [1.0, -1.0, 1.0], [1.0, -1.0, -1.0], [-1.0, 1.0, 1.0], [-1.0, 1.0, -1.0], [-1.0, -1.0, 1.0], [-1.0, -1.0, -1.0], [0.0, inv_phi, phi], [0.0, inv_phi, -phi], [0.0, -inv_phi, phi], [0.0, -inv_phi, -phi], [inv_phi, phi, 0.0], [inv_phi, -phi, 0.0], [-inv_phi, phi, 0.0], [-inv_phi, -phi, 0.0], [phi, 0.0, inv_phi], [phi, 0.0, -inv_phi], [-phi, 0.0, inv_phi], [-phi, 0.0, -inv_phi], ];
let verts: Vec<[f32; 3]> = raw.iter().map(|&v| normalize(v)).collect();
let pentagons: [[u32; 5]; 12] = [
[0, 8, 10, 2, 16],
[0, 16, 17, 1, 12],
[0, 12, 14, 4, 8],
[4, 14, 5, 19, 18],
[4, 18, 6, 10, 8],
[6, 18, 19, 7, 15],
[6, 15, 13, 2, 10],
[2, 13, 3, 17, 16],
[1, 17, 3, 11, 9],
[1, 9, 5, 14, 12],
[5, 9, 11, 7, 19],
[7, 11, 3, 13, 15],
];
let mut tris: Vec<[u32; 3]> = Vec::with_capacity(36);
for pent in &pentagons {
let v0 = pent[0];
tris.push([v0, pent[1], pent[2]]);
tris.push([v0, pent[2], pent[3]]);
tris.push([v0, pent[3], pent[4]]);
}
PlatonicSolid {
kind: PlatonicKind::Dodecahedron,
verts,
tris,
}
}
pub fn platonic_vertex_count(s: &PlatonicSolid) -> usize {
s.verts.len()
}
pub fn platonic_tri_count(s: &PlatonicSolid) -> usize {
s.tris.len()
}
pub fn validate_platonic(s: &PlatonicSolid) -> bool {
let n = s.verts.len() as u32;
s.tris.iter().all(|t| t[0] < n && t[1] < n && t[2] < n)
}
pub fn is_unit_sphere(s: &PlatonicSolid) -> bool {
s.verts.iter().all(|&v| {
let r2 = v[0].powi(2) + v[1].powi(2) + v[2].powi(2);
(r2 - 1.0).abs() < 0.01
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tetrahedron_vertex_count() {
assert_eq!(
platonic_vertex_count(&build_platonic_solid(PlatonicKind::Tetrahedron)),
4
);
}
#[test]
fn test_tetrahedron_tri_count() {
assert_eq!(
platonic_tri_count(&build_platonic_solid(PlatonicKind::Tetrahedron)),
4
);
}
#[test]
fn test_cube_vertex_count() {
assert_eq!(
platonic_vertex_count(&build_platonic_solid(PlatonicKind::Cube)),
8
);
}
#[test]
fn test_cube_tri_count() {
assert_eq!(
platonic_tri_count(&build_platonic_solid(PlatonicKind::Cube)),
12
);
}
#[test]
fn test_octahedron_vertex_count() {
assert_eq!(
platonic_vertex_count(&build_platonic_solid(PlatonicKind::Octahedron)),
6
);
}
#[test]
fn test_icosahedron_vertex_count() {
assert_eq!(
platonic_vertex_count(&build_platonic_solid(PlatonicKind::Icosahedron)),
12
);
}
#[test]
fn test_icosahedron_unit_sphere() {
assert!(is_unit_sphere(&build_platonic_solid(
PlatonicKind::Icosahedron
)));
}
#[test]
fn test_validate_all() {
for kind in [
PlatonicKind::Tetrahedron,
PlatonicKind::Cube,
PlatonicKind::Octahedron,
PlatonicKind::Icosahedron,
PlatonicKind::Dodecahedron,
] {
assert!(
validate_platonic(&build_platonic_solid(kind)),
"{kind:?} failed"
);
}
}
#[test]
fn test_icosahedron_tri_count() {
assert_eq!(
platonic_tri_count(&build_platonic_solid(PlatonicKind::Icosahedron)),
20
);
}
#[test]
fn test_dodecahedron_vertex_count() {
let solid = build_platonic_solid(PlatonicKind::Dodecahedron);
assert_eq!(solid.verts.len(), 20);
}
#[test]
fn test_dodecahedron_tri_count() {
let solid = build_platonic_solid(PlatonicKind::Dodecahedron);
assert_eq!(solid.tris.len(), 36);
}
#[test]
fn test_dodecahedron_unit_sphere() {
let solid = build_platonic_solid(PlatonicKind::Dodecahedron);
assert!(is_unit_sphere(&solid), "dodecahedron vertices not on unit sphere");
}
#[test]
fn test_dodecahedron_no_degenerate_tris() {
let solid = build_platonic_solid(PlatonicKind::Dodecahedron);
for tri in &solid.tris {
assert_ne!(tri[0], tri[1], "degenerate tri: indices 0 and 1 equal");
assert_ne!(tri[1], tri[2], "degenerate tri: indices 1 and 2 equal");
assert_ne!(tri[0], tri[2], "degenerate tri: indices 0 and 2 equal");
}
}
#[test]
fn test_dodecahedron_edge_equality() {
let pentagons: [[u32; 5]; 12] = [
[0, 8, 10, 2, 16],
[0, 16, 17, 1, 12],
[0, 12, 14, 4, 8],
[4, 14, 5, 19, 18],
[4, 18, 6, 10, 8],
[6, 18, 19, 7, 15],
[6, 15, 13, 2, 10],
[2, 13, 3, 17, 16],
[1, 17, 3, 11, 9],
[1, 9, 5, 14, 12],
[5, 9, 11, 7, 19],
[7, 11, 3, 13, 15],
];
let mut edge_set: std::collections::HashSet<(u32, u32)> = std::collections::HashSet::new();
for pent in &pentagons {
for k in 0..5usize {
let a = pent[k];
let b = pent[(k + 1) % 5];
let edge = if a < b { (a, b) } else { (b, a) };
edge_set.insert(edge);
}
}
assert_eq!(edge_set.len(), 30, "expected exactly 30 unique pentagon edges");
let solid = build_platonic_solid(PlatonicKind::Dodecahedron);
let v = &solid.verts;
let edge_lengths: Vec<f32> = edge_set
.iter()
.map(|&(a, b)| {
let va = v[a as usize];
let vb = v[b as usize];
let dx = va[0] - vb[0];
let dy = va[1] - vb[1];
let dz = va[2] - vb[2];
(dx * dx + dy * dy + dz * dz).sqrt()
})
.collect();
let first = edge_lengths[0];
for (i, &len) in edge_lengths.iter().enumerate() {
assert!(
(len - first).abs() < 1e-3,
"edge {i} length {len} differs from first edge length {first} by more than 1e-3"
);
}
}
}