1use std::path::Path;
70
71pub use gltf;
75
76use ranim_core::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::frame::EvaluatedFrame;
417
418 use super::*;
419
420 fn push_f32(buf: &mut Vec<u8>, value: f32) {
421 buf.extend_from_slice(&value.to_le_bytes());
422 }
423
424 fn triangle_bin() -> Vec<u8> {
426 let mut bin = Vec::new();
427 for p in [[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] {
428 for c in p {
429 push_f32(&mut bin, c);
430 }
431 }
432 for _ in 0..3 {
433 for c in [0.0f32, 0.0, 1.0] {
434 push_f32(&mut bin, c);
435 }
436 }
437 for rgba in [
438 [1.0f32, 0.0, 0.0, 1.0],
439 [0.0, 1.0, 0.0, 1.0],
440 [0.0, 0.0, 1.0, 1.0],
441 ] {
442 for c in rgba {
443 push_f32(&mut bin, c);
444 }
445 }
446 for i in [0u32, 1, 2] {
447 bin.extend_from_slice(&i.to_le_bytes());
448 }
449 bin
450 }
451
452 fn triangle_json(uri: Option<&str>) -> String {
455 let buffers = match uri {
456 Some(uri) => format!(r#"{{"uri": "{uri}", "byteLength": 132}}"#),
457 None => r#"{"byteLength": 132}"#.to_string(),
458 };
459 r#"
460 {
461 "asset": {"version": "2.0"},
462 "scene": 0,
463 "scenes": [{"nodes": [0]}],
464 "nodes": [
465 {"name": "parent", "translation": [1.0, 2.0, 3.0],
466 "rotation": [0.0, 0.0, 0.7071067811865476, 0.7071067811865476],
467 "children": [1]},
468 {"name": "child", "scale": [2.0, 2.0, 2.0], "mesh": 0}
469 ],
470 "meshes": [{"name": "tri", "primitives": [{
471 "attributes": {"POSITION": 0, "NORMAL": 1, "COLOR_0": 2},
472 "indices": 3
473 }]}],
474 "buffers": [[BUFFERS]],
475 "bufferViews": [
476 {"buffer": 0, "byteOffset": 0, "byteLength": 36},
477 {"buffer": 0, "byteOffset": 36, "byteLength": 36},
478 {"buffer": 0, "byteOffset": 72, "byteLength": 48},
479 {"buffer": 0, "byteOffset": 120, "byteLength": 12}
480 ],
481 "accessors": [
482 {"bufferView": 0, "componentType": 5126, "count": 3,
483 "type": "VEC3", "min": [0.0, 0.0, 0.0], "max": [1.0, 1.0, 0.0]},
484 {"bufferView": 1, "componentType": 5126, "count": 3, "type": "VEC3"},
485 {"bufferView": 2, "componentType": 5126, "count": 3, "type": "VEC4"},
486 {"bufferView": 3, "componentType": 5125, "count": 3, "type": "SCALAR"}
487 ]
488 }"#
489 .replace("[BUFFERS]", &buffers)
490 }
491
492 fn pack_glb(json: &str, bin: &[u8]) -> Vec<u8> {
494 let mut json_chunk = json.as_bytes().to_vec();
495 while !json_chunk.len().is_multiple_of(4) {
496 json_chunk.push(b' ');
497 }
498 let mut bin_chunk = bin.to_vec();
499 while !bin_chunk.len().is_multiple_of(4) {
500 bin_chunk.push(0);
501 }
502
503 let total = 12 + 8 + json_chunk.len() + 8 + bin_chunk.len();
504 let mut glb = Vec::with_capacity(total);
505 glb.extend_from_slice(&0x46546C67u32.to_le_bytes()); glb.extend_from_slice(&2u32.to_le_bytes());
507 glb.extend_from_slice(&(total as u32).to_le_bytes());
508 glb.extend_from_slice(&(json_chunk.len() as u32).to_le_bytes());
509 glb.extend_from_slice(&0x4E4F534Au32.to_le_bytes()); glb.extend_from_slice(&json_chunk);
511 glb.extend_from_slice(&(bin_chunk.len() as u32).to_le_bytes());
512 glb.extend_from_slice(&0x004E4942u32.to_le_bytes()); glb.extend_from_slice(&bin_chunk);
514 glb
515 }
516
517 fn triangle_glb() -> Vec<u8> {
520 pack_glb(&triangle_json(None), &triangle_bin())
521 }
522
523 fn triangle_tree() -> GltfTree {
524 let glb = triangle_glb();
525 let gltf = gltf::Gltf::from_slice(&glb).unwrap();
526 let blob = gltf.blob.clone();
527 node_tree_from_gltf(&gltf.document, |buffer| match buffer.source() {
528 gltf::buffer::Source::Bin => blob.as_deref(),
529 gltf::buffer::Source::Uri(_) => None,
530 })
531 }
532
533 #[test]
534 fn nested_mesh_import_preserves_payloads_and_composes_world_poses() {
535 let tree = triangle_tree();
536 let parent = &tree.children()[0];
537 let child = &parent.inner.children()[0];
538 assert_eq!(parent.inner.id.as_deref(), Some("parent"));
539 assert_eq!(child.inner.id.as_deref(), Some("child"));
540 assert!(
541 parent
542 .transform
543 .transform_point3(DVec3::ZERO)
544 .abs_diff_eq(dvec3(1.0, -3.0, 2.0), 1e-6)
545 );
546 assert!((parent.transform.matrix3 * DVec3::X).abs_diff_eq(DVec3::Z, 1e-6));
547 assert!(
548 child
549 .transform
550 .transform_point3(DVec3::ONE)
551 .abs_diff_eq(DVec3::splat(2.0), 1e-9)
552 );
553 let leaf = child.inner.item().unwrap();
554 assert_eq!(leaf.points.as_slice(), &[DVec3::ZERO, DVec3::X, DVec3::Y]);
555 assert_eq!(leaf.triangle_indices, [0, 1, 2]);
556 assert!(
557 leaf.vertex_normals
558 .iter()
559 .all(|normal| normal.abs_diff_eq(DVec3::Z, 1e-6))
560 );
561 assert!(
562 leaf.vertex_colors[0]
563 .0
564 .abs_diff_eq(Vec4::new(1.0, 0.0, 0.0, 1.0), 1e-6)
565 );
566 let (world, _) = tree.leaves().next().unwrap();
568 assert!(
569 world
570 .transform_point3(DVec3::X)
571 .abs_diff_eq(dvec3(1.0, -3.0, 4.0), 1e-6)
572 );
573 let mut frame = EvaluatedFrame::new();
574 frame.push((0, 0), tree.tree);
575 let stored = &frame.items::<Node<MeshItem>>()[0].value;
576 let (placement, mesh) = stored.leaves().next().unwrap();
577 assert_eq!(mesh.points.as_slice(), &[DVec3::ZERO, DVec3::X, DVec3::Y]);
578 assert!(placement.abs_diff_eq(world, 1e-9));
579 assert!(
580 placement
581 .transform_point3(DVec3::X)
582 .abs_diff_eq(dvec3(1.0, -3.0, 4.0), 1e-6)
583 );
584 }
585
586 #[test]
587 fn loads_a_glb_file_from_disk() {
588 let path = std::env::temp_dir().join(format!("ranim_gltf_test_{}.glb", std::process::id()));
589 std::fs::write(&path, triangle_glb()).unwrap();
590 let loaded = node_tree_from_path(&path);
591 let _ = std::fs::remove_file(&path);
592
593 let tree = loaded.unwrap();
594 assert_eq!(tree.leaves().count(), 1);
595 let leaf = tree.children()[0].inner.children()[0].inner.item().unwrap();
596 assert_eq!(leaf.triangle_indices, vec![0, 1, 2]);
597 }
598
599 #[test]
600 fn resolves_external_bin_buffers_next_to_a_gltf() {
601 let dir = std::env::temp_dir();
602 let stem = format!("ranim_gltf_ext_{}", std::process::id());
603 let gltf_path = dir.join(format!("{stem}.gltf"));
604 let bin_path = dir.join(format!("{stem}.bin"));
605 std::fs::write(&gltf_path, triangle_json(Some(&format!("{stem}.bin")))).unwrap();
606 std::fs::write(&bin_path, triangle_bin()).unwrap();
607 let loaded = node_tree_from_path(&gltf_path);
608 let _ = std::fs::remove_file(&gltf_path);
609 let _ = std::fs::remove_file(&bin_path);
610
611 let tree = loaded.unwrap();
614 let leaf = tree.children()[0].inner.children()[0].inner.item().unwrap();
615 assert_eq!(leaf.triangle_indices, vec![0, 1, 2]);
616 assert_eq!(leaf.points.len(), 3);
617 }
618
619 #[test]
620 fn node_paths_map_document_indices_to_tree_positions() {
621 let tree = triangle_tree();
622
623 let parent = tree.node(0).unwrap();
627 assert_eq!(parent.inner.id.as_deref(), Some("parent"));
628 let child = tree.node(1).unwrap();
629 assert_eq!(child.inner.id.as_deref(), Some("child"));
630 assert_eq!(tree.node_paths[0], Some(vec![0]));
631 assert_eq!(tree.node_paths[1], Some(vec![0, 0]));
632
633 assert!(tree.node(7).is_none());
635 let mut tree = tree;
637 tree.node_mut(1).unwrap().inner.id = Some("renamed".into());
638 assert_eq!(tree.node(1).unwrap().inner.id.as_deref(), Some("renamed"));
639 }
640}