use gltf_json as json; use std::{fs, mem}; use json::validation::Checked::Valid; use json::validation::USize64; use std::borrow::Cow; use std::io::Write; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] enum Output { /// Output standard glTF. Standard, /// Output binary glTF. Binary, } #[derive(Copy, Clone, Debug)] #[repr(C)] struct Vertex { position: [f32; 3], color: [f32; 3], } /// Calculate bounding coordinates of a list of vertices, used for the clipping distance of the model fn bounding_coords(points: &[Vertex]) -> ([f32; 3], [f32; 3]) { let mut min = [f32::MAX, f32::MAX, f32::MAX]; let mut max = [f32::MIN, f32::MIN, f32::MIN]; for point in points { let p = point.position; for i in 0..3 { min[i] = f32::min(min[i], p[i]); max[i] = f32::max(max[i], p[i]); } } (min, max) } fn align_to_multiple_of_four(n: &mut usize) { *n = (*n + 3) & !3; } fn to_padded_byte_vector(vec: Vec) -> Vec { let byte_length = vec.len() * mem::size_of::(); let byte_capacity = vec.capacity() * mem::size_of::(); let alloc = vec.into_boxed_slice(); let ptr = Box::<[T]>::into_raw(alloc) as *mut u8; let mut new_vec = unsafe { Vec::from_raw_parts(ptr, byte_length, byte_capacity) }; while new_vec.len() % 4 != 0 { new_vec.push(0); // pad to multiple of four bytes } new_vec } fn export(output: Output) { let triangle_vertices = vec![ Vertex { position: [0.0, 0.5, 0.0], color: [1.0, 0.0, 0.0], }, Vertex { position: [-0.5, -0.5, 0.0], color: [0.0, 1.0, 0.0], }, Vertex { position: [0.5, -0.5, 0.0], color: [0.0, 0.0, 1.0], }, ]; let (min, max) = bounding_coords(&triangle_vertices); let mut root = gltf_json::Root::default(); let buffer_length = triangle_vertices.len() * mem::size_of::(); let buffer = root.push(json::Buffer { byte_length: USize64::from(buffer_length), extensions: Default::default(), extras: Default::default(), name: None, uri: if output == Output::Standard { Some("buffer0.bin".into()) } else { None }, }); let buffer_view = root.push(json::buffer::View { buffer, byte_length: USize64::from(buffer_length), byte_offset: None, byte_stride: Some(json::buffer::Stride(mem::size_of::())), extensions: Default::default(), extras: Default::default(), name: None, target: Some(Valid(json::buffer::Target::ArrayBuffer)), }); let positions = root.push(json::Accessor { buffer_view: Some(buffer_view), byte_offset: Some(USize64(0)), count: USize64::from(triangle_vertices.len()), component_type: Valid(json::accessor::GenericComponentType( json::accessor::ComponentType::F32, )), extensions: Default::default(), extras: Default::default(), type_: Valid(json::accessor::Type::Vec3), min: Some(json::Value::from(Vec::from(min))), max: Some(json::Value::from(Vec::from(max))), name: None, normalized: false, sparse: None, }); let colors = root.push(json::Accessor { buffer_view: Some(buffer_view), byte_offset: Some(USize64::from(3 * mem::size_of::())), count: USize64::from(triangle_vertices.len()), component_type: Valid(json::accessor::GenericComponentType( json::accessor::ComponentType::F32, )), extensions: Default::default(), extras: Default::default(), type_: Valid(json::accessor::Type::Vec3), min: None, max: None, name: None, normalized: false, sparse: None, }); let primitive = json::mesh::Primitive { attributes: { let mut map = std::collections::BTreeMap::new(); map.insert(Valid(json::mesh::Semantic::Positions), positions); map.insert(Valid(json::mesh::Semantic::Colors(0)), colors); map }, extensions: Default::default(), extras: Default::default(), indices: None, material: None, mode: Valid(json::mesh::Mode::Triangles), targets: None, }; let mesh = root.push(json::Mesh { extensions: Default::default(), extras: Default::default(), name: None, primitives: vec![primitive], weights: None, }); let node = root.push(json::Node { mesh: Some(mesh), ..Default::default() }); root.push(json::Scene { extensions: Default::default(), extras: Default::default(), name: None, nodes: vec![node], }); match output { Output::Standard => { let _ = fs::create_dir("triangle"); let writer = fs::File::create("triangle/triangle.gltf").expect("I/O error"); json::serialize::to_writer_pretty(writer, &root).expect("Serialization error"); let bin = to_padded_byte_vector(triangle_vertices); let mut writer = fs::File::create("triangle/buffer0.bin").expect("I/O error"); writer.write_all(&bin).expect("I/O error"); } Output::Binary => { let json_string = json::serialize::to_string(&root).expect("Serialization error"); let mut json_offset = json_string.len(); align_to_multiple_of_four(&mut json_offset); let glb = gltf::binary::Glb { header: gltf::binary::Header { magic: *b"glTF", version: 2, // N.B., the size of binary glTF file is limited to range of `u32`. length: (json_offset + buffer_length) .try_into() .expect("file size exceeds binary glTF limit"), }, bin: Some(Cow::Owned(to_padded_byte_vector(triangle_vertices))), json: Cow::Owned(json_string.into_bytes()), }; let writer = std::fs::File::create("triangle.glb").expect("I/O error"); glb.to_writer(writer).expect("glTF binary output error"); } } } fn main() { export(Output::Standard); export(Output::Binary); }