1use std::path::Path;
70
71pub use gltf;
75
76use ranim_core::core_item::transformed::Transformed;
77
78use crate::hierarchy::Node;
79use crate::mesh::MeshItem;
80use ranim_core::components::rgba::Rgba;
81use ranim_core::glam::{DAffine3, DMat4, DQuat, DVec3, Vec4, dvec3};
82
83pub struct GltfTree {
93 pub tree: Node<MeshItem>,
96 pub node_paths: Vec<Option<Vec<usize>>>,
100}
101
102impl GltfTree {
103 pub fn node(&self, index: usize) -> Option<&Transformed<Node<MeshItem>, DAffine3>> {
106 self.tree.get(self.node_paths.get(index)?.as_deref()?)
107 }
108
109 pub fn node_mut(&mut self, index: usize) -> Option<&mut Transformed<Node<MeshItem>, DAffine3>> {
111 self.tree.get_mut(self.node_paths.get(index)?.as_deref()?)
112 }
113}
114
115impl std::ops::Deref for GltfTree {
116 type Target = Node<MeshItem>;
117
118 fn deref(&self) -> &Self::Target {
119 &self.tree
120 }
121}
122
123fn y_up_to_z_up() -> DAffine3 {
127 DAffine3::from_rotation_x(std::f64::consts::FRAC_PI_2)
128}
129
130#[derive(Debug)]
132pub enum GltfLoadError {
133 Io(std::io::Error),
135 Parse(gltf::Error),
137}
138
139impl std::fmt::Display for GltfLoadError {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 match self {
142 GltfLoadError::Io(error) => write!(f, "failed to read glTF file: {error}"),
143 GltfLoadError::Parse(error) => write!(f, "failed to parse glTF file: {error}"),
144 }
145 }
146}
147
148impl std::error::Error for GltfLoadError {
149 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
150 match self {
151 GltfLoadError::Io(error) => Some(error),
152 GltfLoadError::Parse(error) => Some(error),
153 }
154 }
155}
156
157pub fn node_tree_from_path(path: impl AsRef<Path>) -> Result<GltfTree, GltfLoadError> {
165 let path = path.as_ref();
166 let bytes = std::fs::read(path).map_err(GltfLoadError::Io)?;
167 let gltf = gltf::Gltf::from_slice(&bytes).map_err(GltfLoadError::Parse)?;
168 let blob = gltf.blob.clone();
169 let base = path.parent().unwrap_or_else(|| Path::new("."));
170
171 let external: Vec<Option<Vec<u8>>> = gltf
174 .document
175 .buffers()
176 .map(|buffer| match buffer.source() {
177 gltf::buffer::Source::Bin => Ok(None),
178 gltf::buffer::Source::Uri(uri) if uri.starts_with("data:") => {
179 tracing::warn!("data: buffer URIs are not supported yet, skipping buffer {uri}");
180 Ok(None)
181 }
182 gltf::buffer::Source::Uri(uri) => std::fs::read(base.join(uri))
183 .map(Some)
184 .map_err(GltfLoadError::Io),
185 })
186 .collect::<Result<_, _>>()?;
187
188 Ok(node_tree_from_gltf(&gltf.document, |buffer| {
189 match buffer.source() {
190 gltf::buffer::Source::Bin => blob.as_deref(),
191 gltf::buffer::Source::Uri(_) => external
192 .get(buffer.index())
193 .and_then(|data| data.as_deref()),
194 }
195 }))
196}
197
198pub fn node_tree_from_gltf<'s, F>(doc: &gltf::Document, get_buffer_data: F) -> GltfTree
210where
211 F: Clone + for<'b> Fn(gltf::buffer::Buffer<'b>) -> Option<&'s [u8]>,
212{
213 let mut node_paths = vec![None; doc.nodes().count()];
214 let scene = match doc.default_scene().or_else(|| doc.scenes().next()) {
215 Some(scene) => scene,
216 None => {
217 tracing::warn!("glTF document has no scene, importing an empty tree");
218 return GltfTree {
219 tree: Node::frame(),
220 node_paths,
221 };
222 }
223 };
224 let children = scene
225 .nodes()
226 .enumerate()
227 .map(|(slot, node)| {
228 let mut wrapper =
229 node_tree_from_gltf_node(node, &[slot], &mut node_paths, &get_buffer_data);
230 wrapper.compose_outer(y_up_to_z_up());
231 wrapper
232 })
233 .collect::<Vec<_>>();
234 GltfTree {
235 tree: Node::group(children),
236 node_paths,
237 }
238}
239
240fn node_tree_from_gltf_node<'s, F>(
246 node: gltf::scene::Node<'_>,
247 path: &[usize],
248 node_paths: &mut Vec<Option<Vec<usize>>>,
249 get_buffer_data: &F,
250) -> Transformed<Node<MeshItem>, DAffine3>
251where
252 F: Clone + for<'b> Fn(gltf::buffer::Buffer<'b>) -> Option<&'s [u8]>,
253{
254 let transform = node_transform(node.transform());
255 let mut id = non_empty(node.name()).map(str::to_string);
256
257 let mut children: Vec<Transformed<Node<MeshItem>, DAffine3>> = Vec::new();
258 let mut item: Option<MeshItem> = None;
259 if let Some(mesh) = node.mesh() {
260 let primitive_count = mesh.primitives().count();
261 if primitive_count == 1 {
262 let (index, primitive) = mesh.primitives().enumerate().next().unwrap();
263 warn_if_not_triangles(&primitive, mesh.name(), index);
268 item = Some(primitive_mesh_item(primitive, get_buffer_data));
269 if id.is_none() {
270 id = non_empty(mesh.name()).map(str::to_string);
271 }
272 } else {
273 for (index, primitive) in mesh.primitives().enumerate() {
276 warn_if_not_triangles(&primitive, mesh.name(), index);
277 let leaf = Node::leaf(primitive_mesh_item(primitive, get_buffer_data));
278 children.push(
279 match primitive_leaf_id(mesh.name(), node.name(), index) {
280 Some(id) => leaf.with_id(id),
281 None => leaf,
282 }
283 .into(),
284 );
285 }
286 }
287 }
288 let payload_slots = children.len();
289 children.extend(node.children().enumerate().map(|(slot, child)| {
290 let mut child_path = path.to_vec();
291 child_path.push(payload_slots + slot);
292 node_tree_from_gltf_node(child, &child_path, node_paths, get_buffer_data)
293 }));
294
295 node_paths[node.index()] = Some(path.to_vec());
296 let node = Node::new(item, children);
297 match id {
298 Some(id) => Transformed::new(node.with_id(id), transform),
299 None => Transformed::new(node, transform),
300 }
301}
302
303fn warn_if_not_triangles(
306 primitive: &gltf::mesh::Primitive<'_>,
307 mesh_name: Option<&str>,
308 index: usize,
309) {
310 if primitive.mode() != gltf::mesh::Mode::Triangles {
311 tracing::warn!(
312 "primitive {index} of mesh {mesh_name:?} is not TRIANGLES, importing indices as-is"
313 );
314 }
315}
316
317fn primitive_mesh_item<'s, F>(primitive: gltf::mesh::Primitive<'_>, get_buffer_data: &F) -> MeshItem
319where
320 F: Clone + for<'b> Fn(gltf::buffer::Buffer<'b>) -> Option<&'s [u8]>,
321{
322 let reader = primitive.reader(get_buffer_data);
323
324 let points: Vec<DVec3> = match reader.read_positions() {
325 Some(positions) => positions
326 .map(|p| dvec3(p[0] as f64, p[1] as f64, p[2] as f64))
327 .collect(),
328 None => {
329 tracing::warn!("primitive without POSITION attribute, importing no vertices");
330 Vec::new()
331 }
332 };
333 let triangle_indices: Vec<u32> = match reader.read_indices() {
337 Some(indices) => indices.into_u32().collect(),
338 None => (0..points.len() as u32).collect(),
339 };
340 let vertex_normals: Vec<DVec3> = match reader.read_normals() {
342 Some(normals) => normals
343 .map(|n| dvec3(n[0] as f64, n[1] as f64, n[2] as f64))
344 .collect(),
345 None => vec![DVec3::ZERO; points.len()],
346 };
347 let vertex_colors: Vec<Rgba> = match reader.read_colors(0) {
350 Some(colors) => colors
351 .into_rgba_f32()
352 .map(|rgba| Rgba(Vec4::from_array(rgba)))
353 .collect(),
354 None => vec![Rgba::default(); points.len()],
355 };
356
357 MeshItem {
358 points: points.into(),
359 triangle_indices,
360 vertex_colors: vertex_colors.into(),
361 vertex_normals: vertex_normals.into(),
362 }
363}
364
365fn node_transform(transform: gltf::scene::Transform) -> DAffine3 {
369 match transform {
370 gltf::scene::Transform::Matrix { matrix } => {
371 let matrix = matrix.map(|col| col.map(|v| v as f64));
372 DAffine3::from_mat4(DMat4::from_cols_array_2d(&matrix))
373 }
374 gltf::scene::Transform::Decomposed {
375 translation,
376 rotation,
377 scale,
378 } => {
379 let rotation = DQuat::from_xyzw(
380 rotation[0] as f64,
381 rotation[1] as f64,
382 rotation[2] as f64,
383 rotation[3] as f64,
384 );
385 let translation = dvec3(
386 translation[0] as f64,
387 translation[1] as f64,
388 translation[2] as f64,
389 );
390 let scale = dvec3(scale[0] as f64, scale[1] as f64, scale[2] as f64);
391 DAffine3::from_rotation_translation(rotation, translation) * DAffine3::from_scale(scale)
392 }
393 }
394}
395
396fn primitive_leaf_id(
399 mesh_name: Option<&str>,
400 node_name: Option<&str>,
401 index: usize,
402) -> Option<String> {
403 match non_empty(mesh_name) {
404 Some(mesh_name) => Some(format!("{mesh_name}.{index}")),
405 None => non_empty(node_name).map(|node_name| format!("{node_name}.primitive{index}")),
406 }
407}
408
409fn non_empty(name: Option<&str>) -> Option<&str> {
411 name.filter(|name| !name.is_empty())
412}
413
414#[cfg(test)]
415mod tests {
416 use ranim_core::core_item::CoreItem;
417 use ranim_core::{Extract, glam::Vec3};
418
419 use super::*;
420
421 fn push_f32(buf: &mut Vec<u8>, value: f32) {
422 buf.extend_from_slice(&value.to_le_bytes());
423 }
424
425 fn triangle_bin() -> Vec<u8> {
427 let mut bin = Vec::new();
428 for p in [[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] {
429 for c in p {
430 push_f32(&mut bin, c);
431 }
432 }
433 for _ in 0..3 {
434 for c in [0.0f32, 0.0, 1.0] {
435 push_f32(&mut bin, c);
436 }
437 }
438 for rgba in [
439 [1.0f32, 0.0, 0.0, 1.0],
440 [0.0, 1.0, 0.0, 1.0],
441 [0.0, 0.0, 1.0, 1.0],
442 ] {
443 for c in rgba {
444 push_f32(&mut bin, c);
445 }
446 }
447 for i in [0u32, 1, 2] {
448 bin.extend_from_slice(&i.to_le_bytes());
449 }
450 assert_eq!(bin.len(), 132);
451 bin
452 }
453
454 fn triangle_json(uri: Option<&str>) -> String {
457 let buffers = match uri {
458 Some(uri) => format!(r#"{{"uri": "{uri}", "byteLength": 132}}"#),
459 None => r#"{"byteLength": 132}"#.to_string(),
460 };
461 r#"
462 {
463 "asset": {"version": "2.0"},
464 "scene": 0,
465 "scenes": [{"nodes": [0]}],
466 "nodes": [
467 {"name": "parent", "translation": [1.0, 2.0, 3.0],
468 "rotation": [0.0, 0.0, 0.7071067811865476, 0.7071067811865476],
469 "children": [1]},
470 {"name": "child", "scale": [2.0, 2.0, 2.0], "mesh": 0}
471 ],
472 "meshes": [{"name": "tri", "primitives": [{
473 "attributes": {"POSITION": 0, "NORMAL": 1, "COLOR_0": 2},
474 "indices": 3
475 }]}],
476 "buffers": [[BUFFERS]],
477 "bufferViews": [
478 {"buffer": 0, "byteOffset": 0, "byteLength": 36},
479 {"buffer": 0, "byteOffset": 36, "byteLength": 36},
480 {"buffer": 0, "byteOffset": 72, "byteLength": 48},
481 {"buffer": 0, "byteOffset": 120, "byteLength": 12}
482 ],
483 "accessors": [
484 {"bufferView": 0, "componentType": 5126, "count": 3,
485 "type": "VEC3", "min": [0.0, 0.0, 0.0], "max": [1.0, 1.0, 0.0]},
486 {"bufferView": 1, "componentType": 5126, "count": 3, "type": "VEC3"},
487 {"bufferView": 2, "componentType": 5126, "count": 3, "type": "VEC4"},
488 {"bufferView": 3, "componentType": 5125, "count": 3, "type": "SCALAR"}
489 ]
490 }"#
491 .replace("[BUFFERS]", &buffers)
492 }
493
494 fn pack_glb(json: &str, bin: &[u8]) -> Vec<u8> {
496 let mut json_chunk = json.as_bytes().to_vec();
497 while !json_chunk.len().is_multiple_of(4) {
498 json_chunk.push(b' ');
499 }
500 let mut bin_chunk = bin.to_vec();
501 while !bin_chunk.len().is_multiple_of(4) {
502 bin_chunk.push(0);
503 }
504
505 let total = 12 + 8 + json_chunk.len() + 8 + bin_chunk.len();
506 let mut glb = Vec::with_capacity(total);
507 glb.extend_from_slice(&0x46546C67u32.to_le_bytes()); glb.extend_from_slice(&2u32.to_le_bytes());
509 glb.extend_from_slice(&(total as u32).to_le_bytes());
510 glb.extend_from_slice(&(json_chunk.len() as u32).to_le_bytes());
511 glb.extend_from_slice(&0x4E4F534Au32.to_le_bytes()); glb.extend_from_slice(&json_chunk);
513 glb.extend_from_slice(&(bin_chunk.len() as u32).to_le_bytes());
514 glb.extend_from_slice(&0x004E4942u32.to_le_bytes()); glb.extend_from_slice(&bin_chunk);
516 glb
517 }
518
519 fn triangle_glb() -> Vec<u8> {
522 pack_glb(&triangle_json(None), &triangle_bin())
523 }
524
525 fn triangle_tree() -> GltfTree {
526 let glb = triangle_glb();
527 let gltf = gltf::Gltf::from_slice(&glb).unwrap();
528 let blob = gltf.blob.clone();
529 node_tree_from_gltf(&gltf.document, |buffer| match buffer.source() {
530 gltf::buffer::Source::Bin => blob.as_deref(),
531 gltf::buffer::Source::Uri(_) => None,
532 })
533 }
534
535 #[test]
536 fn tree_shape_mirrors_the_gltf_node_graph() {
537 let tree = triangle_tree();
538 assert!(tree.is_group());
539 let roots = tree.children();
540 assert_eq!(roots.len(), 1);
541
542 let parent = &roots[0];
543 assert_eq!(parent.inner.id.as_deref(), Some("parent"));
544 assert!(parent.inner.is_group());
545 let parent_children = parent.inner.children();
546 assert_eq!(parent_children.len(), 1);
547
548 let child = &parent_children[0];
549 assert_eq!(child.inner.id.as_deref(), Some("child"));
551 assert!(child.inner.is_leaf());
552 assert_eq!(child.inner.item().unwrap().triangle_indices, vec![0, 1, 2]);
553 }
554
555 #[test]
556 fn node_transforms_match_trs_semantics() {
557 let tree = triangle_tree();
558 let parent = &tree.children()[0];
559 let child = &parent.inner.children()[0];
560
561 let parent_t = &parent.transform;
568 assert!(
569 parent_t
570 .transform_point3(DVec3::ZERO)
571 .abs_diff_eq(dvec3(1.0, -3.0, 2.0), 1e-6)
572 );
573 assert!((parent_t.matrix3 * DVec3::X).abs_diff_eq(DVec3::Z, 1e-6));
574
575 assert!(
577 child
578 .transform
579 .transform_point3(dvec3(1.0, 1.0, 1.0))
580 .abs_diff_eq(dvec3(2.0, 2.0, 2.0), 1e-9)
581 );
582 }
583
584 #[test]
585 fn leaf_mesh_round_trips_primitive_data() {
586 let tree = triangle_tree();
587 let leaf = tree.children()[0].inner.children()[0].inner.item().unwrap();
588
589 let points: Vec<DVec3> = leaf.points.iter().cloned().collect();
590 assert_eq!(
591 points,
592 vec![
593 dvec3(0.0, 0.0, 0.0),
594 dvec3(1.0, 0.0, 0.0),
595 dvec3(0.0, 1.0, 0.0)
596 ]
597 );
598 assert_eq!(leaf.triangle_indices, vec![0, 1, 2]);
599 assert!(
600 leaf.vertex_normals
601 .iter()
602 .all(|n| n.abs_diff_eq(DVec3::Z, 1e-6))
603 );
604 let first_color = leaf.vertex_colors[0].0;
605 assert!((first_color - Vec4::new(1.0, 0.0, 0.0, 1.0)).abs_diff_eq(Vec4::ZERO, 1e-6));
606 }
607
608 #[test]
609 fn extraction_composes_the_world_transform_for_meshes() {
610 let tree = triangle_tree();
611
612 let (world, _) = tree.leaves().next().unwrap();
617 assert!(
618 world
619 .transform_point3(dvec3(1.0, 0.0, 0.0))
620 .abs_diff_eq(dvec3(1.0, -3.0, 4.0), 1e-6)
621 );
622
623 let extracted = tree.extract();
625 assert_eq!(extracted.len(), 1);
626 match &extracted[0] {
627 CoreItem::MeshItem(mesh) => {
628 let local = Vec3::new(1.0, 0.0, 0.0);
629 let world = mesh.transform.transform_point3(local);
630 assert!((world - Vec3::new(1.0, -3.0, 4.0)).length() < 1e-4);
631 assert_eq!(mesh.points.len(), 3);
633 }
634 _ => panic!("expected a MeshItem"),
635 }
636 }
637
638 #[test]
639 fn loads_a_glb_file_from_disk() {
640 let path = std::env::temp_dir().join(format!("ranim_gltf_test_{}.glb", std::process::id()));
641 std::fs::write(&path, triangle_glb()).unwrap();
642 let loaded = node_tree_from_path(&path);
643 let _ = std::fs::remove_file(&path);
644
645 let tree = loaded.unwrap();
646 assert_eq!(tree.leaves().count(), 1);
647 let leaf = tree.children()[0].inner.children()[0].inner.item().unwrap();
648 assert_eq!(leaf.triangle_indices, vec![0, 1, 2]);
649 }
650
651 #[test]
652 fn resolves_external_bin_buffers_next_to_a_gltf() {
653 let dir = std::env::temp_dir();
654 let stem = format!("ranim_gltf_ext_{}", std::process::id());
655 let gltf_path = dir.join(format!("{stem}.gltf"));
656 let bin_path = dir.join(format!("{stem}.bin"));
657 std::fs::write(&gltf_path, triangle_json(Some(&format!("{stem}.bin")))).unwrap();
658 std::fs::write(&bin_path, triangle_bin()).unwrap();
659 let loaded = node_tree_from_path(&gltf_path);
660 let _ = std::fs::remove_file(&gltf_path);
661 let _ = std::fs::remove_file(&bin_path);
662
663 let tree = loaded.unwrap();
666 let leaf = tree.children()[0].inner.children()[0].inner.item().unwrap();
667 assert_eq!(leaf.triangle_indices, vec![0, 1, 2]);
668 assert_eq!(leaf.points.len(), 3);
669 }
670
671 #[test]
672 fn node_paths_map_document_indices_to_tree_positions() {
673 let tree = triangle_tree();
674
675 let parent = tree.node(0).unwrap();
679 assert_eq!(parent.inner.id.as_deref(), Some("parent"));
680 let child = tree.node(1).unwrap();
681 assert_eq!(child.inner.id.as_deref(), Some("child"));
682 assert_eq!(tree.node_paths[0], Some(vec![0]));
683 assert_eq!(tree.node_paths[1], Some(vec![0, 0]));
684
685 assert!(tree.node(7).is_none());
687 assert_eq!(
688 tree.node(1).unwrap().inner.id,
689 tree.get(&tree.node_paths[1].clone().unwrap())
690 .unwrap()
691 .inner
692 .id
693 );
694
695 let mut tree = triangle_tree();
697 tree.node_mut(1).unwrap().inner.id = Some("renamed".into());
698 assert_eq!(tree.node(1).unwrap().inner.id.as_deref(), Some("renamed"));
699 }
700}