summaryrefslogtreecommitdiff
path: root/src/gltf/extras.rs
blob: eade453dc72febdba7c26d1272455217aabfcbcf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
use std::borrow::Cow;

use base64::Engine;
use gltf::Gltf;
use serde_json::{json, value::to_raw_value, Value};

use crate::{Codec, Error};

/// Codec for embedding data in a GLTF file "extras" entry. It uses the extras entry in the first
/// scene in the file and stores the data as base64.
#[derive(Default)]
pub struct ExtrasEntryCodec;

impl Codec for ExtrasEntryCodec {
    fn encode(&self, carrier: &[u8], payload: &[u8]) -> Result<Vec<u8>, crate::Error> {
        let gltf = match Gltf::from_slice(carrier) {
            Ok(gltf) => gltf,
            Err(e) => return Err(Error::DependencyError(e.to_string())),
        };

        let mut json = gltf.document.into_json();
        let mut scene = json.scenes.remove(0);
        let mut extras = match serde_json::from_str::<Value>(scene.extras.clone().unwrap_or(to_raw_value(&json!({})).unwrap()).get()) {
            Ok(extras) => extras,
            Err(e) => return Err(Error::DependencyError(e.to_string())),
        };
        match &mut extras {
            Value::Object(object) => {
                let base64_payload = base64::engine::general_purpose::STANDARD.encode(payload);
                object.insert("occule".into(), Value::String(base64_payload));
            },
            _ => return Err(Error::DataInvalid("Carrier has extras in non-object format, not gonna mess with that.".into()))
        }
        let extras = match to_raw_value(&extras) {
            Ok(raw) => Some(raw),
            Err(e) => return Err(Error::DependencyError(e.to_string())),
        };
        scene.extras = extras;
        json.scenes.insert(0, scene);
        let json_string = match gltf_json::serialize::to_string(&json) {
            Ok(json_string) => json_string,
            Err(e) => return Err(Error::DependencyError(e.to_string())),
        };

        let mut glb = match gltf::binary::Glb::from_slice(carrier) {
            Ok(glb) => glb,
            Err(e) => return Err(Error::DependencyError(e.to_string())),
        };
        glb.header.length = (glb.header.length as usize - glb.json.len() + align_to_multiple_of_four(json_string.len())) as u32;
        glb.json = Cow::Owned(json_string.into_bytes());        

        Ok(match glb.to_vec() {
            Ok(vec) => vec,
            Err(e) => return Err(Error::DependencyError(e.to_string()))
        })
    }

    fn decode(&self, encoded: &[u8]) -> Result<(Vec<u8>, Vec<u8>), crate::Error> {
        let gltf = match Gltf::from_slice(encoded) {
            Ok(gltf) => gltf,
            Err(e) => return Err(Error::DependencyError(e.to_string())),
        };
        let mut json = gltf.document.into_json();
        let mut extras = match &json.scenes[0].extras {
            Some(extras) => match serde_json::from_str::<Value>(extras.get()) {
                Ok(Value::Object(value)) => value,
                _ => return Err(Error::DataNotEncoded),
            },
            None => return Err(Error::DataNotEncoded),
        };
        let payload = match extras.remove("occule".into()) {
            Some(Value::String(payload)) => match base64::engine::general_purpose::STANDARD.decode(payload) {
                Ok(payload) => payload,
                Err(e) => return Err(Error::DependencyError(e.to_string())),
            },
            _ => return Err(Error::DataNotEncoded),
        };

        json.scenes[0].extras = match to_raw_value(&Value::Object(extras)) {
            Ok(extras) => Some(extras),
            Err(e) => return Err(Error::DependencyError(e.to_string()))
        };

        // TODO: remove payload from carrier
        Ok((encoded.to_vec(), payload))      
    }
}

fn align_to_multiple_of_four(n: usize) -> usize {
    (n + 3) & !3
}