Skip to main content

pfx_load/
mesh.rs

1use pfx_core::anim::{Interpolation, Property, Trs};
2
3#[derive(Clone, Debug, PartialEq)]
4pub struct Mesh {
5    pub nodes: Vec<Node>,
6    pub groups: Vec<Group>,
7    pub materials: Vec<Material>,
8    pub roots: Vec<u32>,
9    pub scenes: Vec<Scene>,
10    pub skins: Vec<Skin>,
11    pub animations: Vec<Animation>,
12}
13
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct Scene {
16    pub name: Option<String>,
17    pub roots: Vec<u32>,
18}
19
20#[derive(Clone, Debug, PartialEq)]
21pub struct Node {
22    pub name: String,
23    pub parent: Option<u32>,
24    pub children: Vec<u32>,
25    pub transform: [[f32; 4]; 4],
26    pub trs: Trs,
27    pub group: Option<u32>,
28    pub skin: Option<u32>,
29    pub extras: Option<serde_json::Value>,
30}
31
32#[derive(Clone, Debug, PartialEq)]
33pub struct Skin {
34    pub name: String,
35    pub joints: Vec<u32>,
36    pub inverse_binds: Vec<[[f32; 4]; 4]>,
37}
38
39#[derive(Clone, Debug, PartialEq)]
40pub struct Animation {
41    pub name: String,
42    pub channels: Vec<AnimationChannel>,
43}
44
45#[derive(Clone, Debug, PartialEq)]
46pub struct AnimationChannel {
47    pub node: u32,
48    pub property: Property,
49    pub interpolation: Interpolation,
50    pub times: Vec<f32>,
51    pub values: Vec<f32>,
52}
53
54#[derive(Clone, Debug, PartialEq)]
55pub struct Group {
56    pub name: String,
57    pub primitives: Vec<Primitive>,
58    pub extras: Option<serde_json::Value>,
59}
60
61#[derive(Clone, Debug, PartialEq)]
62pub struct Primitive {
63    pub positions: Vec<[f32; 3]>,
64    pub normals: Vec<[f32; 3]>,
65    pub tangents: Vec<[f32; 4]>,
66    pub uvs: Vec<[f32; 2]>,
67    pub uvs1: Option<Vec<[f32; 2]>>,
68    pub indices: Vec<u32>,
69    pub material: Option<u32>,
70    pub joints: Option<Vec<[u16; 4]>>,
71    pub weights: Option<Vec<[f32; 4]>>,
72}
73
74#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct Material {
76    pub name: String,
77    pub extras: Option<serde_json::Value>,
78}
79
80macro_rules! extras_helpers {
81    ($($kind:ty),*) => {$(
82        impl $kind {
83            pub fn extra(&self, key: &str) -> Option<&serde_json::Value> {
84                self.extras.as_ref()?.get(key)
85            }
86
87            pub fn extra_str(&self, key: &str) -> Option<&str> {
88                self.extra(key)?.as_str()
89            }
90
91            pub fn extra_f64(&self, key: &str) -> Option<f64> {
92                self.extra(key)?.as_f64()
93            }
94
95            pub fn extra_bool(&self, key: &str) -> Option<bool> {
96                self.extra(key)?.as_bool()
97            }
98
99            pub fn extra_array(&self, key: &str) -> Option<&[serde_json::Value]> {
100                self.extra(key)?.as_array().map(Vec::as_slice)
101            }
102
103            pub fn extra_object(
104                &self,
105                key: &str,
106            ) -> Option<&serde_json::Map<String, serde_json::Value>> {
107                self.extra(key)?.as_object()
108            }
109
110            pub fn extra_f64s(&self, key: &str) -> Option<Vec<f64>> {
111                self.extra_array(key)?
112                    .iter()
113                    .map(serde_json::Value::as_f64)
114                    .collect()
115            }
116        }
117    )*};
118}
119
120extras_helpers!(Node, Group, Material);
121
122fn read_extras(extras: &gltf::json::Extras) -> Result<Option<serde_json::Value>, String> {
123    match extras {
124        Some(raw) => serde_json::from_str(raw.get())
125            .map(Some)
126            .map_err(|e| format!("mesh: bad extras: {e}")),
127        None => Ok(None),
128    }
129}
130
131const IDENTITY: [[f32; 4]; 4] = [
132    [1.0, 0.0, 0.0, 0.0],
133    [0.0, 1.0, 0.0, 0.0],
134    [0.0, 0.0, 1.0, 0.0],
135    [0.0, 0.0, 0.0, 1.0],
136];
137
138impl Mesh {
139    pub fn parse(bytes: &[u8]) -> Result<Self, String> {
140        Self::parse_with(bytes, |uri| Err(format!("mesh: external buffer {uri}")))
141    }
142
143    pub fn parse_with<F>(bytes: &[u8], read: F) -> Result<Self, String>
144    where
145        F: Fn(&str) -> Result<Vec<u8>, String>,
146    {
147        let gltf = gltf::Gltf::from_slice(bytes).map_err(|e| format!("mesh: {e}"))?;
148        let buffers = buffers(&gltf, &read)?;
149        let nodes = nodes(&gltf)?;
150        let groups = groups(&gltf, &buffers)?;
151        let materials = materials(&gltf)?;
152        let skins = skins(&gltf, &buffers)?;
153        let animations = animations(&gltf, &buffers)?;
154        for node in &nodes {
155            if let Some(group) = node.group
156                && groups.get(group as usize).is_none()
157            {
158                return Err("mesh: a node names a missing mesh".into());
159            }
160        }
161        for group in &groups {
162            for primitive in &group.primitives {
163                if let Some(material) = primitive.material
164                    && materials.get(material as usize).is_none()
165                {
166                    return Err("mesh: a triangle names a missing material".into());
167                }
168            }
169        }
170        let roots = gltf
171            .default_scene()
172            .or_else(|| gltf.scenes().next())
173            .map(|scene| scene.nodes().map(|node| node.index() as u32).collect())
174            .unwrap_or_default();
175        let scenes = gltf
176            .scenes()
177            .map(|scene| Scene {
178                name: scene.name().map(str::to_string),
179                roots: scene.nodes().map(|node| node.index() as u32).collect(),
180            })
181            .collect();
182        Ok(Mesh {
183            nodes,
184            groups,
185            materials,
186            roots,
187            scenes,
188            skins,
189            animations,
190        })
191    }
192
193    pub fn all_roots(&self) -> Vec<u32> {
194        let mut seen = std::collections::HashSet::new();
195        self.scenes
196            .iter()
197            .flat_map(|scene| scene.roots.iter().copied())
198            .filter(|root| seen.insert(*root))
199            .collect()
200    }
201
202    pub fn world(&self, node: u32) -> Option<[[f32; 4]; 4]> {
203        let mut chain = Vec::new();
204        let mut at = node;
205        for _ in 0..=self.nodes.len() {
206            chain.push(at);
207            match self.nodes.get(at as usize)?.parent {
208                Some(parent) => at = parent,
209                None => {
210                    let mut matrix = IDENTITY;
211                    for id in chain.iter().rev() {
212                        matrix = crate::scene::multiply(matrix, self.nodes[*id as usize].transform);
213                    }
214                    return Some(matrix);
215                }
216            }
217        }
218        None
219    }
220}
221
222fn buffers<F>(gltf: &gltf::Gltf, read: &F) -> Result<Vec<Vec<u8>>, String>
223where
224    F: Fn(&str) -> Result<Vec<u8>, String>,
225{
226    let mut blob = gltf.blob.clone();
227    let mut out = Vec::new();
228    for buffer in gltf.buffers() {
229        let mut data = match buffer.source() {
230            gltf::buffer::Source::Bin => blob
231                .take()
232                .ok_or_else(|| "mesh: missing BIN chunk".to_string())?,
233            gltf::buffer::Source::Uri(uri) => load_uri(uri, read)?,
234        };
235        if data.len() < buffer.length() {
236            return Err(format!(
237                "mesh: buffer {} is {} bytes, the file says {}",
238                buffer.index(),
239                data.len(),
240                buffer.length()
241            ));
242        }
243        while !data.len().is_multiple_of(4) {
244            data.push(0);
245        }
246        out.push(data);
247    }
248    Ok(out)
249}
250
251fn load_uri<F>(uri: &str, read: &F) -> Result<Vec<u8>, String>
252where
253    F: Fn(&str) -> Result<Vec<u8>, String>,
254{
255    if let Some(rest) = uri.strip_prefix("data:") {
256        let (meta, payload) = rest
257            .split_once(',')
258            .ok_or_else(|| "mesh: bad data uri".to_string())?;
259        if meta.split(';').any(|part| part == "base64") {
260            return decode_base64(payload).map_err(|e| format!("mesh: {e}"));
261        }
262        return percent_decode(payload).map(|text| text.into_bytes());
263    }
264    if uri.contains(':') {
265        return Err(format!("mesh: unsupported buffer uri {uri}"));
266    }
267    read(&percent_decode(uri)?)
268}
269
270fn nodes(gltf: &gltf::Gltf) -> Result<Vec<Node>, String> {
271    let mut nodes = Vec::new();
272    for (index, node) in gltf.nodes().enumerate() {
273        if node.index() != index {
274            return Err("mesh: node order is not packed".into());
275        }
276        nodes.push(Node {
277            name: node.name().unwrap_or("").to_string(),
278            parent: None,
279            children: node.children().map(|child| child.index() as u32).collect(),
280            transform: node.transform().matrix(),
281            trs: {
282                let (translation, rotation, scale) = node.transform().decomposed();
283                Trs {
284                    translation,
285                    rotation,
286                    scale,
287                }
288            },
289            group: node.mesh().map(|mesh| mesh.index() as u32),
290            skin: node.skin().map(|skin| skin.index() as u32),
291            extras: read_extras(node.extras())?,
292        });
293    }
294    let children: Vec<Vec<u32>> = nodes.iter().map(|node| node.children.clone()).collect();
295    for (index, child_list) in children.iter().enumerate() {
296        for &child in child_list {
297            let node = nodes
298                .get_mut(child as usize)
299                .ok_or("mesh: a node names a missing child")?;
300            if node.parent.is_some() {
301                return Err("mesh: a node has two parents".into());
302            }
303            node.parent = Some(index as u32);
304        }
305    }
306    Ok(nodes)
307}
308
309fn groups(gltf: &gltf::Gltf, buffers: &[Vec<u8>]) -> Result<Vec<Group>, String> {
310    let mut groups = Vec::new();
311    for (index, mesh) in gltf.meshes().enumerate() {
312        if mesh.index() != index {
313            return Err("mesh: mesh order is not packed".into());
314        }
315        let mut primitives = Vec::new();
316        for primitive in mesh.primitives() {
317            if let Some(primitive) = read_primitive(&primitive, buffers)? {
318                primitives.push(primitive);
319            }
320        }
321        groups.push(Group {
322            name: mesh.name().unwrap_or("").to_string(),
323            primitives,
324            extras: read_extras(mesh.extras())?,
325        });
326    }
327    Ok(groups)
328}
329
330fn materials(gltf: &gltf::Gltf) -> Result<Vec<Material>, String> {
331    let mut materials = Vec::new();
332    for (index, material) in gltf.materials().enumerate() {
333        if material.index() != Some(index) {
334            return Err("mesh: material order is not packed".into());
335        }
336        materials.push(Material {
337            name: material.name().unwrap_or("").to_string(),
338            extras: read_extras(material.extras())?,
339        });
340    }
341    Ok(materials)
342}
343
344fn skins(gltf: &gltf::Gltf, buffers: &[Vec<u8>]) -> Result<Vec<Skin>, String> {
345    let mut skins = Vec::new();
346    for (index, skin) in gltf.skins().enumerate() {
347        if skin.index() != index {
348            return Err("mesh: skin order is not packed".into());
349        }
350        let joints: Vec<u32> = skin.joints().map(|joint| joint.index() as u32).collect();
351        let reader = skin.reader(|buffer| buffers.get(buffer.index()).map(Vec::as_slice));
352        let inverse_binds: Vec<[[f32; 4]; 4]> = match reader.read_inverse_bind_matrices() {
353            Some(matrices) => matrices.collect(),
354            None => vec![IDENTITY; joints.len()],
355        };
356        if inverse_binds.len() != joints.len() {
357            return Err(format!(
358                "mesh: skin {index} has {} inverse bind matrices for {} joints",
359                inverse_binds.len(),
360                joints.len()
361            ));
362        }
363        skins.push(Skin {
364            name: skin.name().unwrap_or("").to_string(),
365            joints,
366            inverse_binds,
367        });
368    }
369    Ok(skins)
370}
371
372fn animations(gltf: &gltf::Gltf, buffers: &[Vec<u8>]) -> Result<Vec<Animation>, String> {
373    use gltf::animation::util::ReadOutputs;
374    let mut out = Vec::new();
375    for (index, animation) in gltf.animations().enumerate() {
376        let mut channels = Vec::new();
377        for channel in animation.channels() {
378            let property = match channel.target().property() {
379                gltf::animation::Property::Translation => Property::Translation,
380                gltf::animation::Property::Rotation => Property::Rotation,
381                gltf::animation::Property::Scale => Property::Scale,
382                gltf::animation::Property::MorphTargetWeights => continue,
383            };
384            let interpolation = match channel.sampler().interpolation() {
385                gltf::animation::Interpolation::Linear => Interpolation::Linear,
386                gltf::animation::Interpolation::Step => Interpolation::Step,
387                gltf::animation::Interpolation::CubicSpline => Interpolation::CubicSpline,
388            };
389            let reader = channel.reader(|buffer| buffers.get(buffer.index()).map(Vec::as_slice));
390            let times: Vec<f32> = reader
391                .read_inputs()
392                .ok_or_else(|| format!("mesh: animation {index} has a channel without key times"))?
393                .collect();
394            let values: Vec<f32> = match reader.read_outputs() {
395                Some(ReadOutputs::Translations(values)) => values.flatten().collect(),
396                Some(ReadOutputs::Scales(values)) => values.flatten().collect(),
397                Some(ReadOutputs::Rotations(values)) => values.into_f32().flatten().collect(),
398                Some(ReadOutputs::MorphTargetWeights(_)) | None => {
399                    return Err(format!(
400                        "mesh: animation {index} has a channel without key values"
401                    ));
402                }
403            };
404            channels.push(AnimationChannel {
405                node: channel.target().node().index() as u32,
406                property,
407                interpolation,
408                times,
409                values,
410            });
411        }
412        out.push(Animation {
413            name: animation
414                .name()
415                .map_or_else(|| format!("animation{index}"), str::to_string),
416            channels,
417        });
418    }
419    Ok(out)
420}
421
422fn read_primitive(
423    primitive: &gltf::Primitive<'_>,
424    buffers: &[Vec<u8>],
425) -> Result<Option<Primitive>, String> {
426    if primitive.mode() != gltf::mesh::Mode::Triangles {
427        return Err("mesh: only triangles are supported".into());
428    }
429    let reader = primitive.reader(|buffer| buffers.get(buffer.index()).map(Vec::as_slice));
430    let Some(positions) = reader.read_positions() else {
431        return Ok(None);
432    };
433    let positions: Vec<[f32; 3]> = positions.collect();
434    let normals = fit(
435        reader
436            .read_normals()
437            .map(|values| values.collect())
438            .unwrap_or_default(),
439        positions.len(),
440        "normals",
441    )?;
442    let tangents = fit(
443        reader
444            .read_tangents()
445            .map(|values| values.collect())
446            .unwrap_or_default(),
447        positions.len(),
448        "tangents",
449    )?;
450    let uvs = fit(
451        reader
452            .read_tex_coords(0)
453            .map(|values| values.into_f32().collect())
454            .unwrap_or_default(),
455        positions.len(),
456        "uvs",
457    )?;
458    let uvs1 = reader
459        .read_tex_coords(1)
460        .map(|values| fit(values.into_f32().collect(), positions.len(), "uvs1"))
461        .transpose()?
462        .filter(|values| !values.is_empty());
463    let indices: Vec<u32> = match reader.read_indices() {
464        Some(indices) => indices.into_u32().collect(),
465        None => (0..positions.len() as u32).collect(),
466    };
467    if !indices.len().is_multiple_of(3) {
468        return Err("mesh: indices are not a list of triangles".into());
469    }
470    if indices
471        .iter()
472        .any(|index| *index as usize >= positions.len())
473    {
474        return Err("mesh: an index is outside the vertex list".into());
475    }
476    let joints = reader
477        .read_joints(0)
478        .map(|values| fit(values.into_u16().collect(), positions.len(), "joints"))
479        .transpose()?
480        .filter(|values| !values.is_empty());
481    let weights = reader
482        .read_weights(0)
483        .map(|values| fit(values.into_f32().collect(), positions.len(), "weights"))
484        .transpose()?
485        .filter(|values| !values.is_empty());
486    if joints.is_some() != weights.is_some() {
487        return Err(
488            "mesh: a primitive has joints without weights or weights without joints".into(),
489        );
490    }
491    Ok(Some(Primitive {
492        positions,
493        normals,
494        tangents,
495        uvs,
496        uvs1,
497        indices,
498        material: primitive.material().index().map(|index| index as u32),
499        joints,
500        weights,
501    }))
502}
503
504fn fit<T>(values: Vec<T>, len: usize, what: &str) -> Result<Vec<T>, String> {
505    if values.is_empty() || values.len() == len {
506        Ok(values)
507    } else {
508        Err(format!(
509            "mesh: {what} has {} values for {len} vertices",
510            values.len()
511        ))
512    }
513}
514
515fn percent_decode(input: &str) -> Result<String, String> {
516    let bytes = input.as_bytes();
517    let mut out = Vec::with_capacity(bytes.len());
518    let mut index = 0;
519    while index < bytes.len() {
520        if bytes[index] == b'%' {
521            if index + 2 >= bytes.len() {
522                return Err("mesh: bad percent escape".into());
523            }
524            let hex = std::str::from_utf8(&bytes[index + 1..index + 3])
525                .map_err(|_| "mesh: bad percent escape")?;
526            let value = u8::from_str_radix(hex, 16).map_err(|_| "mesh: bad percent escape")?;
527            out.push(value);
528            index += 3;
529        } else {
530            out.push(bytes[index]);
531            index += 1;
532        }
533    }
534    String::from_utf8(out).map_err(|_| "mesh: buffer uri is not utf-8".into())
535}
536
537fn decode_base64(input: &str) -> Result<Vec<u8>, String> {
538    fn value(byte: u8) -> Option<u8> {
539        match byte {
540            b'A'..=b'Z' => Some(byte - b'A'),
541            b'a'..=b'z' => Some(byte - b'a' + 26),
542            b'0'..=b'9' => Some(byte - b'0' + 52),
543            b'+' => Some(62),
544            b'/' => Some(63),
545            _ => None,
546        }
547    }
548    let bytes: Vec<u8> = input
549        .bytes()
550        .filter(|byte| !byte.is_ascii_whitespace())
551        .collect();
552    if !bytes.len().is_multiple_of(4) {
553        return Err("bad base64".into());
554    }
555    let mut out = Vec::with_capacity(bytes.len() / 4 * 3);
556    for chunk in bytes.chunks_exact(4) {
557        let pad = chunk.iter().rev().take_while(|byte| **byte == b'=').count();
558        if pad > 2 {
559            return Err("bad base64".into());
560        }
561        let mut packed = 0u32;
562        for (index, &byte) in chunk.iter().enumerate() {
563            if byte == b'=' {
564                if index < 4 - pad {
565                    return Err("bad base64".into());
566                }
567                continue;
568            }
569            let value = value(byte).ok_or("bad base64")?;
570            packed |= u32::from(value) << (18 - 6 * index);
571        }
572        out.push((packed >> 16) as u8);
573        if pad < 2 {
574            out.push((packed >> 8) as u8);
575        }
576        if pad < 1 {
577            out.push(packed as u8);
578        }
579    }
580    Ok(out)
581}
582
583#[cfg(test)]
584mod tests {
585    use super::*;
586    use crate::Source;
587    use std::path::Path;
588
589    fn triangle(mesh: &Mesh) {
590        assert_eq!(mesh.roots, vec![0]);
591        assert_eq!(mesh.nodes[0].name, "root");
592        assert_eq!(mesh.nodes[0].children, vec![1]);
593        assert_eq!(mesh.nodes[0].parent, None);
594        assert_eq!(mesh.nodes[1].name, "triangle");
595        assert_eq!(mesh.nodes[1].parent, Some(0));
596        assert_eq!(mesh.nodes[1].group, Some(0));
597        assert_eq!(mesh.groups[0].name, "sheet");
598        assert_eq!(
599            mesh.materials,
600            vec![Material {
601                name: "ink".into(),
602                extras: None
603            }]
604        );
605        let primitive = &mesh.groups[0].primitives[0];
606        assert_eq!(
607            primitive.positions,
608            vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
609        );
610        assert_eq!(primitive.normals, vec![[0.0, 0.0, 1.0]; 3]);
611        assert_eq!(primitive.tangents, vec![[1.0, 0.0, 0.0, 1.0]; 3]);
612        assert_eq!(primitive.uvs, vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
613        assert_eq!(primitive.indices, vec![0, 1, 2]);
614        assert_eq!(primitive.material, Some(0));
615        let world = mesh.world(1).unwrap();
616        assert_eq!(world[3], [1.0, 2.0, 3.0, 1.0]);
617        assert_eq!(mesh.world(0).unwrap()[0], [1.0, 0.0, 0.0, 0.0]);
618    }
619
620    #[test]
621    fn a_hand_made_glb_triangle_keeps_its_names_and_local_vertices() {
622        let glb = Mesh::parse(include_bytes!("../tests/data/triangle.glb")).unwrap();
623        triangle(&glb);
624        let folder = Source::folder(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data"));
625        let json = String::from_utf8(folder.read("triangle.gltf").unwrap()).unwrap();
626        let gltf = Mesh::parse_with(json.as_bytes(), |uri| {
627            folder.read_beside("triangle.gltf", uri)
628        })
629        .unwrap();
630        assert_eq!(gltf, glb);
631        let encoded = base64_encode(include_bytes!("../tests/data/triangle.bin"));
632        let inline = json.replace(
633            "\"uri\":\"triangle.bin\"",
634            &format!("\"uri\":\"data:application/octet-stream;base64,{encoded}\""),
635        );
636        assert_eq!(Mesh::parse(inline.as_bytes()).unwrap(), glb);
637        assert_eq!(decode_base64("TQ==").unwrap(), b"M");
638        assert_eq!(decode_base64("TWE=").unwrap(), b"Ma");
639        assert_eq!(decode_base64("TWFu").unwrap(), b"Man");
640        assert_eq!(percent_decode("a%2Fb.bin").unwrap(), "a/b.bin");
641    }
642
643    fn scenes_json(scenes: &str, default: &str) -> String {
644        format!(
645            "{{\"asset\":{{\"version\":\"2.0\"}}{scenes}{default},\"nodes\":[\
646             {{\"name\":\"a\"}},{{\"name\":\"b\"}},{{\"name\":\"c\"}},{{\"name\":\"d\"}}]}}"
647        )
648    }
649
650    #[test]
651    fn every_scene_keeps_its_roots_and_all_roots_lists_each_node_once() {
652        let json = scenes_json(
653            ",\"scenes\":[{\"name\":\"first\",\"nodes\":[0,1]},\
654             {\"name\":\"second\",\"nodes\":[1,2]},{\"nodes\":[3]}]",
655            "",
656        );
657        let mesh = Mesh::parse(json.as_bytes()).unwrap();
658        assert_eq!(
659            mesh.scenes,
660            vec![
661                Scene {
662                    name: Some("first".into()),
663                    roots: vec![0, 1]
664                },
665                Scene {
666                    name: Some("second".into()),
667                    roots: vec![1, 2]
668                },
669                Scene {
670                    name: None,
671                    roots: vec![3]
672                },
673            ]
674        );
675        assert_eq!(mesh.roots, vec![0, 1]);
676        assert_eq!(mesh.all_roots(), vec![0, 1, 2, 3]);
677
678        let json = scenes_json(
679            ",\"scenes\":[{\"nodes\":[0,1]},{\"nodes\":[1,2]},{\"nodes\":[3,0]}]",
680            ",\"scene\":1",
681        );
682        let mesh = Mesh::parse(json.as_bytes()).unwrap();
683        assert_eq!(mesh.roots, vec![1, 2]);
684        assert_eq!(mesh.scenes.len(), 3);
685        assert_eq!(mesh.all_roots(), vec![0, 1, 2, 3]);
686    }
687
688    #[test]
689    fn a_file_without_scenes_has_no_roots() {
690        let json = scenes_json("", "");
691        let mesh = Mesh::parse(json.as_bytes()).unwrap();
692        assert_eq!(mesh.nodes.len(), 4);
693        assert!(mesh.roots.is_empty());
694        assert!(mesh.scenes.is_empty());
695        assert!(mesh.all_roots().is_empty());
696    }
697
698    const EXTRAS_NODE_A: &str = "\"extras\":{\"mover\":true,\"kind\":\"door\",\"speed\":2.5,\"axis\":[0,1,0],\"limits\":{\"min\":-1,\"max\":1.5,\"tags\":[\"a\",\"b\"]},\"none\":null}";
699
700    fn with_extras(json: &str) -> String {
701        json.replace(
702            "{\"name\":\"root\",\"children\":[1]}",
703            &format!("{{\"name\":\"root\",\"children\":[1],{EXTRAS_NODE_A}}}"),
704        )
705        .replace(
706            "\"translation\":[1,2,3]}",
707            "\"translation\":[1,2,3],\"extras\":{\"flag\":false,\"id\":7}}",
708        )
709        .replace(
710            "\"name\":\"sheet\",",
711            "\"name\":\"sheet\",\"extras\":{\"shadow\":\"only\"},",
712        )
713        .replace(
714            "{\"name\":\"ink\"}",
715            "{\"name\":\"ink\",\"extras\":{\"wet\":1.5,\"layers\":[1,2,3]}}",
716        )
717    }
718
719    fn glb(json: &str, bin: &[u8]) -> Vec<u8> {
720        let mut json = json.as_bytes().to_vec();
721        while !json.len().is_multiple_of(4) {
722            json.push(b' ');
723        }
724        let mut bin = bin.to_vec();
725        while !bin.len().is_multiple_of(4) {
726            bin.push(0);
727        }
728        let total = 12 + 8 + json.len() + 8 + bin.len();
729        let mut out = Vec::new();
730        out.extend_from_slice(b"glTF");
731        out.extend_from_slice(&2u32.to_le_bytes());
732        out.extend_from_slice(&(total as u32).to_le_bytes());
733        out.extend_from_slice(&(json.len() as u32).to_le_bytes());
734        out.extend_from_slice(b"JSON");
735        out.extend_from_slice(&json);
736        out.extend_from_slice(&(bin.len() as u32).to_le_bytes());
737        out.extend_from_slice(b"BIN\0");
738        out.extend_from_slice(&bin);
739        out
740    }
741
742    #[test]
743    fn extras_of_every_json_kind_are_read_from_nodes_a_mesh_and_a_material() {
744        let json = include_str!("../tests/data/triangle.gltf");
745        let bin = include_bytes!("../tests/data/triangle.bin");
746        let embedded = with_extras(json).replace("{\"uri\":\"triangle.bin\",", "{");
747        let mesh = Mesh::parse(&glb(&embedded, bin)).unwrap();
748
749        let root = &mesh.nodes[0];
750        assert_eq!(root.extra_bool("mover"), Some(true));
751        assert_eq!(root.extra_str("kind"), Some("door"));
752        assert_eq!(root.extra_f64("speed"), Some(2.5));
753        assert_eq!(root.extra_f64s("axis"), Some(vec![0.0, 1.0, 0.0]));
754        assert_eq!(root.extra_array("axis").unwrap().len(), 3);
755        let limits = root.extra_object("limits").unwrap();
756        assert_eq!(limits["min"], serde_json::json!(-1));
757        assert_eq!(limits["tags"], serde_json::json!(["a", "b"]));
758        assert_eq!(root.extra("none"), Some(&serde_json::Value::Null));
759        assert_eq!(root.extra_str("none"), None);
760
761        let triangle = &mesh.nodes[1];
762        assert_eq!(triangle.extra_bool("flag"), Some(false));
763        assert_eq!(triangle.extra_f64("id"), Some(7.0));
764
765        assert_eq!(mesh.groups[0].extra_str("shadow"), Some("only"));
766        assert_eq!(mesh.materials[0].extra_f64("wet"), Some(1.5));
767        assert_eq!(
768            mesh.materials[0].extra_f64s("layers"),
769            Some(vec![1.0, 2.0, 3.0])
770        );
771
772        let text = Mesh::parse_with(with_extras(json).as_bytes(), |uri| {
773            if uri == "triangle.bin" {
774                Ok(include_bytes!("../tests/data/triangle.bin").to_vec())
775            } else {
776                Err(format!("unexpected {uri}"))
777            }
778        })
779        .unwrap();
780        assert_eq!(text, mesh);
781    }
782
783    #[test]
784    fn extra_helpers_never_panic_on_missing_or_mistyped_keys() {
785        let json = include_str!("../tests/data/triangle.gltf");
786        let bin = include_bytes!("../tests/data/triangle.bin");
787        let embedded = with_extras(json).replace("{\"uri\":\"triangle.bin\",", "{");
788        let mesh = Mesh::parse(&glb(&embedded, bin)).unwrap();
789        let root = &mesh.nodes[0];
790        assert_eq!(root.extra("missing"), None);
791        assert_eq!(root.extra_str("mover"), None);
792        assert_eq!(root.extra_bool("kind"), None);
793        assert_eq!(root.extra_f64("kind"), None);
794        assert_eq!(root.extra_array("kind"), None);
795        assert_eq!(root.extra_object("axis"), None);
796        assert_eq!(root.extra_f64s("limits"), None);
797        let mixed = Node {
798            extras: Some(serde_json::json!({"mixed": [1, "x"], "list": 4})),
799            ..mesh.nodes[0].clone()
800        };
801        assert_eq!(mixed.extra_f64s("mixed"), None);
802        let scalar = Node {
803            extras: Some(serde_json::json!(5)),
804            ..mesh.nodes[0].clone()
805        };
806        assert_eq!(scalar.extra("anything"), None);
807        let none = Node {
808            extras: None,
809            ..mesh.nodes[0].clone()
810        };
811        assert_eq!(none.extra_str("kind"), None);
812    }
813
814    #[test]
815    fn a_file_without_extras_loads_with_none_and_the_same_geometry() {
816        let plain = Mesh::parse(include_bytes!("../tests/data/triangle.glb")).unwrap();
817        assert!(plain.nodes.iter().all(|node| node.extras.is_none()));
818        assert!(plain.groups.iter().all(|group| group.extras.is_none()));
819        assert!(
820            plain
821                .materials
822                .iter()
823                .all(|material| material.extras.is_none())
824        );
825        let json = include_str!("../tests/data/triangle.gltf");
826        let bin = include_bytes!("../tests/data/triangle.bin");
827        let embedded = with_extras(json).replace("{\"uri\":\"triangle.bin\",", "{");
828        let extra = Mesh::parse(&glb(&embedded, bin)).unwrap();
829        assert_eq!(extra.groups[0].primitives, plain.groups[0].primitives);
830        assert_eq!(extra.nodes[1].transform, plain.nodes[1].transform);
831        assert_eq!(extra.roots, plain.roots);
832    }
833
834    fn with_second_uvs(uvs1: &[[f32; 2]]) -> Vec<u8> {
835        let json = include_str!("../tests/data/triangle.gltf");
836        let mut bin = include_bytes!("../tests/data/triangle.bin").to_vec();
837        while !bin.len().is_multiple_of(4) {
838            bin.push(0);
839        }
840        let offset = bin.len();
841        for uv in uvs1 {
842            for value in uv {
843                bin.extend_from_slice(&value.to_le_bytes());
844            }
845        }
846        let json = json
847            .replace("{\"uri\":\"triangle.bin\",\"byteLength\":150}", &format!("{{\"byteLength\":{}}}", bin.len()))
848            .replace(
849                "\"TEXCOORD_0\":3}",
850                "\"TEXCOORD_0\":3,\"TEXCOORD_1\":5}",
851            )
852            .replace(
853                "\"type\":\"SCALAR\"}]",
854                &format!(
855                    "\"type\":\"SCALAR\"}},{{\"bufferView\":5,\"componentType\":5126,\"count\":{},\"type\":\"VEC2\"}}]",
856                    uvs1.len()
857                ),
858            )
859            .replace(
860                "\"byteLength\":6}]",
861                &format!(
862                    "\"byteLength\":6}},{{\"buffer\":0,\"byteOffset\":{offset},\"byteLength\":{}}}]",
863                    uvs1.len() * 8
864                ),
865            );
866        glb(&json, &bin)
867    }
868
869    #[test]
870    fn a_second_uv_set_loads_beside_the_first() {
871        let second = [[0.25, 0.5], [0.75, 0.5], [0.25, 1.0]];
872        let mesh = Mesh::parse(&with_second_uvs(&second)).unwrap();
873        let primitive = &mesh.groups[0].primitives[0];
874        assert_eq!(primitive.uvs1.as_deref(), Some(&second[..]));
875        assert_eq!(primitive.uvs, vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
876        let plain = Mesh::parse(include_bytes!("../tests/data/triangle.glb")).unwrap();
877        assert_eq!(plain.groups[0].primitives[0].uvs1, None);
878        let without = Primitive {
879            uvs1: None,
880            ..primitive.clone()
881        };
882        assert_eq!(without, plain.groups[0].primitives[0]);
883        assert!(
884            Mesh::parse(&with_second_uvs(&second[..2]))
885                .unwrap_err()
886                .contains("uvs1 has 2 values for 3 vertices")
887        );
888    }
889
890    fn base64_encode(bytes: &[u8]) -> String {
891        const ALPHA: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
892        let mut out = String::new();
893        let mut index = 0;
894        while index + 3 <= bytes.len() {
895            let packed = (u32::from(bytes[index]) << 16)
896                | (u32::from(bytes[index + 1]) << 8)
897                | u32::from(bytes[index + 2]);
898            for shift in [18, 12, 6, 0] {
899                out.push(ALPHA[((packed >> shift) & 63) as usize] as char);
900            }
901            index += 3;
902        }
903        out
904    }
905}