use genmesh::{generators::Cube, Quad, MapToVertices, Vertices, Vertex}; use obj; use cgmath::{Quaternion, Euler, Deg, Vector3}; use crate::robocraft; const SCALE: f32 = 0.5; const ROTATIONS: [Euler>; 24] = [ Euler{x: Deg(0.0), y: Deg(0.0), z: Deg(0.0)}, // 0 Euler{x: Deg(0.0), y: Deg(0.0), z: Deg(90.0)}, Euler{x: Deg(0.0), y: Deg(0.0), z: Deg(180.0)}, // 2 Euler{x: Deg(0.0), y: Deg(0.0), z: Deg(-90.0)}, Euler{x: Deg(0.0), y: Deg(90.0), z: Deg(0.0)}, // 4 Euler{x: Deg(0.0), y: Deg(90.0), z: Deg(90.0)}, Euler{x: Deg(-90.0), y: Deg(-90.0), z: Deg(0.0)}, // 6 Euler{x: Deg(0.0), y: Deg(90.0), z: Deg(-90.0)}, Euler{x: Deg(0.0), y: Deg(-90.0), z: Deg(90.0)}, // 8 Euler{x: Deg(0.0), y: Deg(-90.0), z: Deg(-90.0)}, Euler{x: Deg(90.0), y: Deg(-90.0), z: Deg(0.0)}, // 10 Euler{x: Deg(90.0), y: Deg(90.0), z: Deg(0.0)}, Euler{x: Deg(-90.0), y: Deg(90.0), z: Deg(0.0)}, // 12 Euler{x: Deg(0.0), y: Deg(90.0), z: Deg(180.0)}, Euler{x: Deg(0.0), y: Deg(180.0), z: Deg(0.0)}, // 14 Euler{x: Deg(0.0), y: Deg(180.0), z: Deg(90.0)}, Euler{x: Deg(0.0), y: Deg(180.0), z: Deg(0.0)}, // 16 Euler{x: Deg(0.0), y: Deg(180.0), z: Deg(180.0)}, Euler{x: Deg(0.0), y: Deg(-90.0), z: Deg(-90.0)}, // 18 Euler{x: Deg(0.0), y: Deg(-90.0), z: Deg(0.0)}, Euler{x: Deg(90.0), y: Deg(0.0), z: Deg(180.0)}, // 20 Euler{x: Deg(90.0), y: Deg(180.0), z: Deg(0.0)}, Euler{x: Deg(-90.0), y: Deg(0.0), z: Deg(0.0)}, // 22 Euler{x: Deg(-90.0), y: Deg(180.0), z: Deg(0.0)}, // 23 ]; /// Convert a Robocraft robot's orientation enum into a physical rotation pub fn cube_rotation_to_quat(orientation: u8) -> Quaternion { ROTATIONS[orientation as usize].into() } /// Convert a Robocraft robot to a 3D model in Wavefront OBJ format. pub fn cubes_to_model(robot: robocraft::Cubes) -> obj::Obj { cubes_to_model_with_lut(robot, default_model_lut) } /// Convert a Robocraft robot to a 3D model in Wavefront OBJ format using the provided lookup table function. pub fn cubes_to_model_with_lut Vec>>(robot: robocraft::Cubes, mut lut: F) -> obj::Obj { let mut positions = Vec::<[f32; 3]>::new(); // vertex positions let mut normals = Vec::<[f32; 3]>::new(); // vertex normals let mut objects = Vec::::new(); // blocks let mut last = 0; for cube in robot.into_iter() { // generate simple cube for every block // TODO rotate blocks let vertices = lut(cube.id); // Use lookup table to find correct id <-> block translation let rotation: Quaternion<_> = cube_rotation_to_quat(cube.orientation); positions.extend::>( vertices.clone().into_iter().vertex(|v| { let rotated = rotation * Vector3{x: v.pos.x * SCALE, y: v.pos.y * SCALE, z: v.pos.z * SCALE}; [rotated.x + (cube.x as f32), rotated.y + (cube.y as f32), rotated.z + (cube.z as f32)] }) .vertices() .collect() ); normals.extend::>( vertices.clone().into_iter().vertex(|v| { let rotated = rotation * Vector3{x: v.normal.x * SCALE, y: v.normal.y * SCALE, z: v.normal.z * SCALE}; [rotated.x + (cube.x as f32), rotated.y + (cube.y as f32), rotated.z + (cube.z as f32)] }) .vertices() .collect() ); let polys = vertices.clone().into_iter().vertex(|_| {last+=1; return last-1;}) .map(|Quad{x: v0, y: v1, z: v2, w: v3}| obj::SimplePolygon(vec![ obj::IndexTuple(v0, Some(0), Some(v0)), obj::IndexTuple(v1, Some(0), Some(v1)), obj::IndexTuple(v2, Some(0), Some(v2)), obj::IndexTuple(v3, Some(0), Some(v3)) ]) /*obj::SimplePolygon(vec![ obj::IndexTuple(v0, None, None), obj::IndexTuple(v1, None, None), obj::IndexTuple(v2, None, None), obj::IndexTuple(v3, None, None) ])*/ ).collect(); objects.push( obj::Object{ name: format!("Cube-ID{}-NUM{}", cube.id, objects.len()), groups: vec![ obj::Group { name: format!("Cube-ID{}-NUM{}-0", cube.id, objects.len()), index: 0, material: None, polys: polys }, ] } ); } println!("Last (index): {}, Vertices (len): {}", last, positions.len()); obj::Obj{ data: obj::ObjData { position: positions, texture: vec![[0.0, 0.0]], normal: normals, objects: objects, material_libs: Vec::new(), }, path: std::path::PathBuf::new(), } } pub fn default_model_lut(id: u32) -> Vec> { // TODO generate non-cube blocks properly match id { _ => Cube::new().collect(), } }