egraphics-io 0.0.1-alpha.9

IO operations for processing graphics formats in 3D space.
Documentation
use gltf_json as json;

use gltf_json::buffer::Stride;
use json::validation::Checked::Valid;
use std::io::Write;
use std::path::Path;
use std::{fs, mem};
use tracing::info;

fn to_padded_byte_vector<T>(vec: Vec<T>) -> Vec<u8> {
    let byte_length = vec.len() * mem::size_of::<T>();
    let byte_capacity = vec.capacity() * mem::size_of::<T>();
    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
}

/// See: https://github.com/gltf-rs/gltf/blob/master/examples/export/main.rs
pub fn write_gltf_file(triangle_mesh: egraphics_core::TriangleMesh, gltf_path: impl AsRef<Path>) {
    assert!(!triangle_mesh.is_empty(), "No triangles found");

    let bin_path = gltf_path.as_ref().with_extension("bin");
    let bin_filename = bin_path.file_name().unwrap().to_str().unwrap().to_string();

    let triangle_vertices = triangle_mesh.all_vertices();
    let position_min = triangle_mesh.get_min();
    let position_max = triangle_mesh.get_max();

    let buffer_length = triangle_vertices.len() * mem::size_of::<egraphics_core::Vertex>();
    let buffer = json::Buffer {
        byte_length: buffer_length.into(),
        extensions: Default::default(),
        extras: Default::default(),
        name: None,
        uri: Some(bin_filename),
    };
    let buffer_view = json::buffer::View {
        buffer: json::Index::new(0),
        byte_length: buffer.byte_length,
        byte_offset: None,
        byte_stride: Some(Stride(mem::size_of::<egraphics_core::Vertex>())),
        extensions: Default::default(),
        extras: Default::default(),
        name: None,
        target: Some(Valid(json::buffer::Target::ArrayBuffer)),
    };
    let positions = json::Accessor {
        buffer_view: Some(json::Index::new(0)),
        byte_offset: None,
        count: triangle_vertices.len().into(),
        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![
            position_min.x,
            position_min.y,
            position_min.z,
        ])),
        max: Some(json::Value::from(vec![
            position_max.x,
            position_max.y,
            position_max.z,
        ])),
        name: None,
        normalized: false,
        sparse: None,
    };
    let colors = json::Accessor {
        buffer_view: Some(json::Index::new(0)),
        byte_offset: Some((3 * mem::size_of::<f32>()).into()),
        count: triangle_vertices.len().into(),
        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), json::Index::new(0));
            map.insert(Valid(json::mesh::Semantic::Colors(0)), json::Index::new(1));
            map
        },
        extensions: Default::default(),
        extras: Default::default(),
        indices: None,
        material: None,
        mode: Valid(json::mesh::Mode::Triangles),
        targets: None,
    };

    /*let primitive2 = json::mesh::Primitive {
        attributes: {
            let mut map = std::collections::HashMap::new();
            map.insert(Valid(json::mesh::Semantic::Positions), json::Index::new(1));
            map.insert(Valid(json::mesh::Semantic::Colors(0)), json::Index::new(2));
            map
        },
        extensions: Default::default(),
        extras: Default::default(),
        indices: None,
        material: None,
        mode: Valid(json::mesh::Mode::Triangles),
        targets: None,
    };*/

    let mesh = json::Mesh {
        extensions: Default::default(),
        extras: Default::default(),
        name: None,
        primitives: vec![primitive],
        weights: None,
    };

    let node = json::Node {
        camera: None,
        children: None,
        extensions: Default::default(),
        extras: Default::default(),
        matrix: None,
        mesh: Some(json::Index::new(0)),
        name: None,
        rotation: None,
        scale: None,
        translation: None,
        skin: None,
        weights: None,
    };

    let root = json::Root {
        accessors: vec![positions, colors],
        buffers: vec![buffer],
        buffer_views: vec![buffer_view],
        meshes: vec![mesh],
        nodes: vec![node],
        scenes: vec![json::Scene {
            extensions: Default::default(),
            extras: Default::default(),
            name: None,
            nodes: vec![json::Index::new(0)],
        }],
        ..Default::default()
    };

    info!("Write to {:?}", gltf_path.as_ref());
    let writer = fs::File::create(gltf_path).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(bin_path).expect("I/O error");
    writer.write_all(&bin).expect("I/O error");
}