I am learning rust by writing a terminal utility I need.
This tool needs to take a gltf file and apply a linear transformation to the nodes and to the buffers.
Right now I am trying to scale down the inverse bind matrix data.
To that effect I am using this crate:
https://docs.rs/gltf/latest/gltf/
With the data loaded I am trying reach the binary data like this:
use gltf::Gltf;
fn main() -> Result<(), gltf::Error>
{
let gltf = Gltf::open("Assets/werewolf_animated.gltf")?;
for skin in gltf.skins() {
for joint in skin.joints()
{
println!(
"Skin #{} has {} joint",
skin.index(),
joint.index(),
);
}
for ibm_accessor in skin.inverse_bind_matrices()
{
println!("Accessor #{}", ibm_accessor.index());
println!("offset: {}", ibm_accessor.offset());
println!("count: {}", ibm_accessor.count());
println!("data_type: {:?}", ibm_accessor.data_type());
println!("dimensions: {:?}", ibm_accessor.dimensions());
println!("buffer: {:?}", ibm_accessor.view().unwrap().buffer().source());
}
}
Ok(())
}
If this were C or C at this stage I would have a float* and I would be able to start reading and writing to the binary data, treating it as a float array. But this is rust.
Any suggestions as to how I can start tampering with the data under ibm_accessor.view().unwrap().buffer().source()
?
CodePudding user response:
Disclaimer: I have never used the glTF data structure or the glft
crate before. However, I found this article, as well as this documentation suggesting that Source data may be encoded as Base64.
Note: We will need to use the data_url helper library
According to the documentation, Source
is an enum that has two variants: Bin
and Uri(&'a str)
.
The idiomatic way of handling the enum would be to match on it:
// In the import section
use data_url::DataUrl;
// In your code
let source = ibm_accessor.view().unwrap().buffer().source();
match source {
Bin => {
// This case seems to only apply to .glb (binary glTF) files.
// If it doesn't apply, maybe skip it?
continue;
},
Uri(data) => {
// Here we can handle the data which is of type `&str`
// As mentioned in the linked documentation, this is
// probably (?) a `data:` Base64 encoded URI, something like
// data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=
// Mozilla/SimonSapin (well-known Rust dev) wrote this library
// to handle `data:` URIs: https://docs.rs/data-url/latest/data_url/
// let's use that.
let url = match DataUrl::process(data) {
Ok(url) => url,
Err(_) => {
// This is probably not a `data` URI? Maybe it's a https:// link or something?
continue;
}
};
let (body, _fragment) = match url.decode_to_vec() {
Ok(res) => res,
Err(_) => {
// The base64 was malformed!
panic!("Or handle this error somehow?");
}
};
// `body` is now a `Vec<u8>` (vector of bytes) which you can loop over.
}
}
data_url Proof-Of-Concept
Just to prove to myself what the data_url
crate does, I did this small POC
Carto.toml
[package]
name = "data-url-test"
version = "0.1.0"
edition = "2021"
[dependencies]
data-url = "0.1.1"
main.rs
use data_url::{DataUrl, mime};
fn main() {
// URL is from https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md
let data = "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=";
let url = match DataUrl::process(data) {
Ok(url) => url,
Err(e) => {
eprintln!("Error {:#?}", e);
return;
}
};
let (body, _fragment) = match url.decode_to_vec() {
Ok(res) => res,
Err(_) => {
// The base64 was malformed!
panic!("Or handle this error somehow?");
}
};
println!("body {:?}", body);
}