204 lines
6.3 KiB
Rust
204 lines
6.3 KiB
Rust
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<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
|
|
}
|
|
|
|
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::<Vertex>();
|
|
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::<Vertex>())),
|
|
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::<f32>())),
|
|
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);
|
|
}
|