1use super::decode_obj_vertices_for_async;
2use crate::animation::{AnimationClip, Keyframe, SkeletonHierarchy, SkeletonJoint, Track};
3use crate::components::{Material, Mesh};
4use crate::renderer::Vertex;
5use gizmo_math::{Quat, Vec3};
6use std::sync::Arc;
7use wgpu::util::DeviceExt;
8
9pub struct GltfNodeData {
14 pub index: usize,
15 pub name: Option<String>,
16 pub skin_index: Option<usize>,
18 pub translation: [f32; 3],
19 pub rotation: [f32; 4],
20 pub scale: [f32; 3],
21 pub primitives: Vec<(Mesh, Option<Material>)>,
23 pub children: Vec<GltfNodeData>,
24}
25
26pub struct GltfSceneAsset {
27 pub roots: Vec<GltfNodeData>,
28 pub animations: Vec<AnimationClip>,
29 pub skeletons: Vec<SkeletonHierarchy>,
30}
31
32impl super::AssetManager {
37 pub fn install_obj_mesh(
44 &mut self,
45 device: &wgpu::Device,
46 file_path: &str,
47 vertices: Vec<Vertex>,
48 _aabb: gizmo_math::Aabb,
49 ) -> Mesh {
50 let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
51 label: Some(&format!("OBJ VBuf: {file_path}")),
52 contents: bytemuck::cast_slice(&vertices),
53 usage: wgpu::BufferUsages::VERTEX,
54 });
55 let mesh = Mesh::new(
56 device,
57 Arc::new(vbuf),
58 &vertices,
59 Vec3::ZERO,
60 format!("obj:{file_path}"),
61 );
62 self.mesh_cache.insert(file_path.to_string(), mesh.clone());
63 mesh
64 }
65
66 pub fn load_obj(&mut self, device: &wgpu::Device, file_path_or_uuid: &str) -> Mesh {
68 let file_path = match self.resolve_path_from_meta_source(file_path_or_uuid) {
69 Ok(p) => p,
70 Err(e) => {
71 tracing::error!("[AssetManager] ERROR: {e}");
72 return self.loading_placeholder_mesh(device);
73 }
74 };
75
76 let cache_key = self
78 .get_uuid(&file_path)
79 .map(|id| id.to_string())
80 .unwrap_or_else(|| file_path.clone());
81
82 if let Some(cached) = self.mesh_cache.get(&cache_key) {
83 return cached.clone();
84 }
85
86 let (vertices, aabb) = match decode_obj_vertices_for_async(&file_path) {
87 Ok(v) => v,
88 Err(e) => {
89 tracing::error!("[AssetManager] OBJ load failed: {file_path} — {e}");
90 let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
92 label: Some("Fallback VBuf (not found)"),
93 contents: &[],
94 usage: wgpu::BufferUsages::VERTEX,
95 });
96 return Mesh::empty(Arc::new(vbuf), format!("obj:missing_{file_path}"));
97 }
98 };
99
100 self.install_obj_mesh(device, &cache_key, vertices, aabb)
101 }
102
103 pub fn load_gltf_scene(
110 &mut self,
111 device: &wgpu::Device,
112 queue: &wgpu::Queue,
113 texture_bind_group_layout: &wgpu::BindGroupLayout,
114 default_tbind: Arc<wgpu::BindGroup>,
115 path_or_uuid: &str,
116 ) -> Result<GltfSceneAsset, String> {
117 let file_path = self.resolve_path_from_meta_source(path_or_uuid)?;
118 let cache_key = self
119 .get_uuid(&file_path)
120 .map(|id| id.to_string())
121 .unwrap_or_else(|| file_path.clone());
122
123 let import_result = if let Some(data) = self.embedded_assets.get(&file_path) {
124 gltf::import_slice(data.as_ref())
125 .map_err(|e| format!("Embedded glTF read failed ({file_path}): {e}"))
126 } else {
127 gltf::import(&file_path)
128 .map_err(|e| format!("glTF file load failed ({file_path}): {e}"))
129 };
130
131 let (document, buffers, images) = import_result?;
132 self.load_gltf_from_import(
133 device,
134 queue,
135 texture_bind_group_layout,
136 default_tbind,
137 &cache_key,
138 document,
139 buffers,
140 images,
141 )
142 }
143
144 pub fn load_gltf_from_import(
150 &mut self,
151 device: &wgpu::Device,
152 queue: &wgpu::Queue,
153 texture_bind_group_layout: &wgpu::BindGroupLayout,
154 default_tbind: Arc<wgpu::BindGroup>,
155 file_path: &str,
156 document: gltf::Document,
157 buffers: Vec<gltf::buffer::Data>,
158 images: Vec<gltf::image::Data>,
159 ) -> Result<GltfSceneAsset, String> {
160 let gltf_textures =
162 self.upload_gltf_textures(device, queue, texture_bind_group_layout, file_path, &images);
163
164 let gltf_materials = build_gltf_materials(&document, &gltf_textures, &default_tbind);
166
167 let mut roots = Vec::new();
169 for scene in document.scenes() {
170 for node in scene.nodes() {
171 roots.push(self.parse_gltf_node(
172 device,
173 &node,
174 &buffers,
175 &gltf_materials,
176 file_path,
177 ));
178 }
179 }
180
181 let animations = parse_animations(&document, &buffers);
183
184 let node_parents: std::collections::HashMap<usize, usize> = document
188 .nodes()
189 .flat_map(|parent| {
190 parent
191 .children()
192 .map(move |child| (child.index(), parent.index()))
193 })
194 .collect();
195
196 let nodes_by_index: Vec<gltf::Node> = document.nodes().collect();
198
199 let skeletons = parse_skeletons(&document, &buffers, &node_parents, &nodes_by_index);
200
201 Ok(GltfSceneAsset {
202 roots,
203 animations,
204 skeletons,
205 })
206 }
207
208 fn upload_gltf_textures(
211 &mut self,
212 device: &wgpu::Device,
213 queue: &wgpu::Queue,
214 texture_bind_group_layout: &wgpu::BindGroupLayout,
215 file_path: &str,
216 images: &[gltf::image::Data],
217 ) -> Vec<(Arc<wgpu::BindGroup>, String)> {
218 let mut gltf_textures = Vec::with_capacity(images.len());
219
220 for (i, image) in images.iter().enumerate() {
221 let (width, height) = (image.width, image.height);
222
223 let rgba: Vec<u8> = convert_image_to_rgba8(image, i, file_path);
225
226 let texture_size = wgpu::Extent3d {
227 width,
228 height,
229 depth_or_array_layers: 1,
230 };
231
232 let texture = device.create_texture(&wgpu::TextureDescriptor {
233 size: texture_size,
234 mip_level_count: 1,
235 sample_count: 1,
236 dimension: wgpu::TextureDimension::D2,
237 format: wgpu::TextureFormat::Rgba8UnormSrgb,
238 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
239 label: Some(&format!("{file_path}_tex_{i}")),
240 view_formats: &[],
241 });
242
243 queue.write_texture(
244 wgpu::ImageCopyTexture {
245 texture: &texture,
246 mip_level: 0,
247 origin: wgpu::Origin3d::ZERO,
248 aspect: wgpu::TextureAspect::All,
249 },
250 &rgba,
251 wgpu::ImageDataLayout {
252 offset: 0,
253 bytes_per_row: Some(4 * width),
254 rows_per_image: Some(height),
255 },
256 texture_size,
257 );
258
259 let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
260 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
261 address_mode_u: wgpu::AddressMode::Repeat,
262 address_mode_v: wgpu::AddressMode::Repeat,
263 address_mode_w: wgpu::AddressMode::Repeat,
264 mag_filter: wgpu::FilterMode::Linear,
265 min_filter: wgpu::FilterMode::Linear,
266 mipmap_filter: wgpu::FilterMode::Linear,
267 ..Default::default()
268 });
269
270 let bg = Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
271 label: Some(&format!("{file_path}_bg_{i}")),
272 layout: texture_bind_group_layout,
273 entries: &[
274 wgpu::BindGroupEntry {
275 binding: 0,
276 resource: wgpu::BindingResource::TextureView(&view),
277 },
278 wgpu::BindGroupEntry {
279 binding: 1,
280 resource: wgpu::BindingResource::Sampler(&sampler),
281 },
282 ],
283 }));
284
285 let tex_source = format!("gltf_tex_{file_path}_{i}");
286 self.texture_cache.insert(tex_source.clone(), bg.clone());
287 gltf_textures.push((bg, tex_source));
288 }
289
290 gltf_textures
291 }
292
293 fn parse_gltf_node(
296 &mut self,
297 device: &wgpu::Device,
298 node: &gltf::Node,
299 buffers: &[gltf::buffer::Data],
300 materials: &[Material],
301 file_name: &str,
302 ) -> GltfNodeData {
303 let (translation, rotation, scale) = node.transform().decomposed();
304
305 let mut primitives = Vec::new();
306
307 if let Some(mesh) = node.mesh() {
308 for (prim_i, primitive) in mesh.primitives().enumerate() {
309 if primitive.mode() != gltf::mesh::Mode::Triangles {
311 tracing::error!(
312 "[GLTF WARN] Skipping non-triangle primitive (mode={:?}) on node '{}'",
313 primitive.mode(),
314 node.name().unwrap_or("<unnamed>"),
315 );
316 continue;
317 }
318
319 let reader = primitive.reader(|buf| Some(&buffers[buf.index()]));
320
321 let positions: Vec<[f32; 3]> = reader
322 .read_positions()
323 .map(|it| it.collect())
324 .unwrap_or_default();
325
326 if positions.is_empty() {
327 continue; }
329
330 let supplied_normals: Option<Vec<[f32; 3]>> =
331 reader.read_normals().map(|it| it.collect());
332
333 let supplied_tangents: Option<Vec<[f32; 4]>> =
334 reader.read_tangents().map(|it| it.collect());
335
336 let tex_coords: Vec<[f32; 2]> = reader
337 .read_tex_coords(0)
338 .map(|it| it.into_f32().collect())
339 .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
340
341 let joints: Option<Vec<[u16; 4]>> =
342 reader.read_joints(0).map(|it| it.into_u16().collect());
343 let weights: Option<Vec<[f32; 4]>> =
344 reader.read_weights(0).map(|it| it.into_f32().collect());
345
346 let mut all_vertices: Vec<Vertex> = Vec::new();
348 let mut aabb = gizmo_math::Aabb::empty();
349
350 let make_vertex = |idx: usize| -> Vertex {
351 let pos = positions[idx];
352
353 let normal = supplied_normals
355 .as_ref()
356 .and_then(|n| n.get(idx).copied())
357 .unwrap_or([0.0, 1.0, 0.0]);
358 let uv = tex_coords.get(idx).copied().unwrap_or([0.0, 0.0]);
359 let j = joints
360 .as_ref()
361 .and_then(|js| js.get(idx))
362 .map(|&[a, b, c, d]| [a as u32, b as u32, c as u32, d as u32])
363 .unwrap_or([0; 4]);
364 let w = weights
365 .as_ref()
366 .and_then(|ws| ws.get(idx))
367 .copied()
368 .unwrap_or([0.0; 4]);
369
370 let tangent = if let Some(ref tangents) = supplied_tangents {
371 tangents.get(idx).copied().unwrap_or([1.0, 0.0, 0.0, 1.0])
372 } else {
373 let n = gizmo_math::Vec3::from(normal);
375 let t = if n.x.abs() > 0.9 {
376 gizmo_math::Vec3::new(0.0, 1.0, 0.0).cross(n).normalize()
377 } else {
378 gizmo_math::Vec3::new(1.0, 0.0, 0.0).cross(n).normalize()
379 };
380 [t.x, t.y, t.z, 1.0]
381 };
382
383 Vertex {
384 position: pos,
385 normal,
386 tex_coords: uv,
387 color: [1.0, 1.0, 1.0],
388 joint_indices: j,
389 joint_weights: w,
390 tangent,
391 }
392 };
393
394 if let Some(indices) = reader.read_indices() {
395 for idx in indices.into_u32() {
396 let i = idx as usize;
397 if i < positions.len() {
398 let pos = positions[i];
399 aabb.extend(Vec3::new(pos[0], pos[1], pos[2]));
400 all_vertices.push(make_vertex(i));
401 }
402 }
403 } else {
404 for i in 0..positions.len() {
405 let pos = positions[i];
406 aabb.extend(Vec3::new(pos[0], pos[1], pos[2]));
407 all_vertices.push(make_vertex(i));
408 }
409 }
410
411 if supplied_normals.is_none() {
414 compute_flat_normals(&mut all_vertices);
415 }
416
417 let vbuf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
418 label: Some(&format!("GLTF VBuf: {file_name}_prim{prim_i}")),
419 contents: bytemuck::cast_slice(&all_vertices),
420 usage: wgpu::BufferUsages::VERTEX,
421 });
422
423 let mesh_source = format!(
425 "gltf_mesh_{file_name}_{}_p{prim_i}",
426 node.name().unwrap_or("<unnamed>")
427 );
428 let mesh_comp = Mesh::new(
429 device,
430 Arc::new(vbuf),
431 &all_vertices,
432 Vec3::ZERO,
433 mesh_source.clone(),
434 );
435 self.mesh_cache.insert(mesh_source, mesh_comp.clone());
436
437 let mat_opt = primitive
438 .material()
439 .index()
440 .and_then(|idx| materials.get(idx).cloned());
441
442 primitives.push((mesh_comp, mat_opt));
443 }
444 }
445
446 let children = node
447 .children()
448 .map(|child| self.parse_gltf_node(device, &child, buffers, materials, file_name))
449 .collect();
450
451 GltfNodeData {
452 index: node.index(),
453 name: node.name().map(str::to_owned),
454 skin_index: node.skin().map(|s| s.index()),
455 translation,
456 rotation,
457 scale,
458 primitives,
459 children,
460 }
461 }
462}
463
464fn convert_image_to_rgba8(image: &gltf::image::Data, idx: usize, file_path: &str) -> Vec<u8> {
470 let (w, h) = (image.width as usize, image.height as usize);
471 let pixel_count = w * h;
472
473 match image.format {
474 gltf::image::Format::R8G8B8A8 => {
475 let expected = pixel_count * 4;
478 if image.pixels.len() >= expected {
479 image.pixels[..expected].to_vec()
480 } else {
481 let mut out = image.pixels.clone();
483 out.resize(expected, 255);
484 out
485 }
486 }
487
488 gltf::image::Format::R8G8B8 => {
489 let mut out = Vec::with_capacity(pixel_count * 4);
493 for chunk in image.pixels.chunks_exact(3) {
494 out.extend_from_slice(&[chunk[0], chunk[1], chunk[2], 255]);
495 }
496 out.resize(pixel_count * 4, 255);
498 out
499 }
500
501 gltf::image::Format::R8G8 => {
502 let mut out = Vec::with_capacity(pixel_count * 4);
504 for chunk in image.pixels.chunks_exact(2) {
505 out.extend_from_slice(&[chunk[0], chunk[0], chunk[0], chunk[1]]);
506 }
507 out.resize(pixel_count * 4, 255);
508 out
509 }
510
511 gltf::image::Format::R8 => {
512 let mut out = Vec::with_capacity(pixel_count * 4);
514 for &lum in &image.pixels {
515 out.extend_from_slice(&[lum, lum, lum, 255]);
516 }
517 out.resize(pixel_count * 4, 255);
518 out
519 }
520
521 unknown => {
522 tracing::error!(
523 "[GLTF WARN] Unknown pixel format {unknown:?} on image {idx} in '{file_path}'. \
524 Falling back to RGBA8 with clamped copy."
525 );
526 let expected = pixel_count * 4;
527 let mut out = vec![0u8; expected];
529 for px in 0..pixel_count {
531 out[px * 4 + 3] = 255;
532 }
533 let copy_len = image.pixels.len().min(expected);
534 out[..copy_len].copy_from_slice(&image.pixels[..copy_len]);
535 out
536 }
537 }
538}
539
540fn compute_flat_normals(vertices: &mut [Vertex]) {
548 for tri in vertices.chunks_exact_mut(3) {
549 let v0 = Vec3::from(tri[0].position);
550 let v1 = Vec3::from(tri[1].position);
551 let v2 = Vec3::from(tri[2].position);
552
553 let edge1 = v1 - v0;
554 let edge2 = v2 - v0;
555 let cross = edge1.cross(edge2);
556
557 let normal = if cross.length_squared() > 1e-10 {
558 cross.normalize()
559 } else {
560 Vec3::Y };
562
563 let n = [normal.x, normal.y, normal.z];
564 tri[0].normal = n;
565 tri[1].normal = n;
566 tri[2].normal = n;
567 }
568}
569
570fn build_gltf_materials(
575 document: &gltf::Document,
576 gltf_textures: &[(Arc<wgpu::BindGroup>, String)],
577 default_tbind: &Arc<wgpu::BindGroup>,
578) -> Vec<Material> {
579 document
580 .materials()
581 .map(|material| {
582 let pbr = material.pbr_metallic_roughness();
583 let base_color = pbr.base_color_factor();
584
585 let mut mat = pbr
586 .base_color_texture()
587 .and_then(|ti| gltf_textures.get(ti.texture().source().index()))
588 .map(|(bg, src)| {
589 let mut m = Material::new(bg.clone());
590 m.texture_source = Some(src.clone());
591 m
592 })
593 .unwrap_or_else(|| Material::new(default_tbind.clone()));
594
595 let mat_name = material.name().unwrap_or("").to_lowercase();
596 let is_glass = mat_name.contains("glass");
597
598 let alpha = if is_glass {
599 0.25 } else if material.alpha_mode() == gltf::material::AlphaMode::Opaque {
601 1.0
602 } else {
603 base_color[3]
604 };
605
606 println!("GLTF LOAD MAT: name={:?}, alpha_mode={:?}, alpha_factor={}, base_color={:?}, double_sided={}",
607 material.name(), material.alpha_mode(), alpha, base_color, material.double_sided());
608
609 mat.albedo = gizmo_math::Vec4::new(base_color[0], base_color[1], base_color[2], alpha);
610 mat.metallic = pbr.metallic_factor();
611 mat.roughness = pbr.roughness_factor();
612
613 mat.is_transparent = material.alpha_mode() != gltf::material::AlphaMode::Opaque || alpha < 0.99 || is_glass;
614 mat.is_double_sided = material.double_sided();
615
616 mat
617 })
618 .collect()
619}
620
621fn parse_animations(
626 document: &gltf::Document,
627 buffers: &[gltf::buffer::Data],
628) -> Vec<AnimationClip> {
629 document
630 .animations()
631 .map(|anim| {
632 let mut translations = Vec::new();
633 let mut rotations = Vec::new();
634 let mut scales = Vec::new();
635
636 for channel in anim.channels() {
637 let target_node = channel.target().node().index();
638 let target_node_name = channel.target().node().name().map(str::to_owned);
639 let reader = channel.reader(|b| Some(&buffers[b.index()]));
640
641 let times: Vec<f32> = match reader.read_inputs() {
642 Some(it) => it.collect(),
643 None => continue,
644 };
645
646 let interp = match channel.sampler().interpolation() {
647 gltf::animation::Interpolation::Step => {
648 crate::animation::InterpolationMode::Step
649 }
650 gltf::animation::Interpolation::CubicSpline => {
651 crate::animation::InterpolationMode::CubicSpline
652 }
653 _ => crate::animation::InterpolationMode::Linear,
654 };
655
656 let outputs = match reader.read_outputs() {
657 Some(o) => o,
658 None => continue,
659 };
660
661 match outputs {
662 gltf::animation::util::ReadOutputs::Translations(tr) => {
663 let keyframes = times
664 .iter()
665 .zip(tr)
666 .map(|(&t, v)| Keyframe {
667 time: t,
668 value: Vec3::new(v[0], v[1], v[2]),
669 })
670 .collect();
671 translations.push(Track {
672 target_node,
673 target_node_name: target_node_name.clone(),
674 interpolation: interp,
675 keyframes,
676 });
677 }
678 gltf::animation::util::ReadOutputs::Rotations(rt) => {
679 let keyframes = times
680 .iter()
681 .zip(rt.into_f32())
682 .map(|(&t, v)| Keyframe {
683 time: t,
684 value: Quat::from_xyzw(v[0], v[1], v[2], v[3]),
685 })
686 .collect();
687 rotations.push(Track {
688 target_node,
689 target_node_name: target_node_name.clone(),
690 interpolation: interp,
691 keyframes,
692 });
693 }
694 gltf::animation::util::ReadOutputs::Scales(sc) => {
695 let keyframes = times
696 .iter()
697 .zip(sc)
698 .map(|(&t, v)| Keyframe {
699 time: t,
700 value: Vec3::new(v[0], v[1], v[2]),
701 })
702 .collect();
703 scales.push(Track {
704 target_node,
705 target_node_name,
706 interpolation: interp,
707 keyframes,
708 });
709 }
710 _ => {} }
712 }
713
714 let d_tr = translations
716 .iter()
717 .filter_map(|t| t.keyframes.last().map(|k| k.time))
718 .fold(0.0f32, f32::max);
719 let d_rot = rotations
720 .iter()
721 .filter_map(|t| t.keyframes.last().map(|k| k.time))
722 .fold(0.0f32, f32::max);
723 let d_scl = scales
724 .iter()
725 .filter_map(|t| t.keyframes.last().map(|k| k.time))
726 .fold(0.0f32, f32::max);
727 let duration = d_tr.max(d_rot).max(d_scl);
728
729 AnimationClip {
730 name: anim.name().unwrap_or("unnamed").to_string(),
731 duration,
732 translations,
733 rotations,
734 scales,
735 }
736 })
737 .collect()
738}
739
740fn parse_skeletons(
745 document: &gltf::Document,
746 buffers: &[gltf::buffer::Data],
747 node_parents: &std::collections::HashMap<usize, usize>,
748 nodes_by_index: &[gltf::Node],
749) -> Vec<SkeletonHierarchy> {
750 document
751 .skins()
752 .map(|skin| {
753 let reader = skin.reader(|b| Some(&buffers[b.index()]));
754
755 let identity_mat = [
756 [1.0, 0., 0., 0.],
757 [0., 1., 0., 0.],
758 [0., 0., 1., 0.],
759 [0., 0., 0., 1.],
760 ];
761 let ibm: Vec<[[f32; 4]; 4]> = reader
762 .read_inverse_bind_matrices()
763 .map(|v| v.collect())
764 .unwrap_or_else(|| vec![identity_mat; skin.joints().count()]);
765
766 let node_to_bone: std::collections::HashMap<usize, usize> = skin
768 .joints()
769 .enumerate()
770 .map(|(bone_idx, node)| (node.index(), bone_idx))
771 .collect();
772
773 let joints: Vec<SkeletonJoint> = skin
774 .joints()
775 .enumerate()
776 .map(|(bone_idx, joint_node)| {
777 let inverse_bind_matrix = gizmo_math::Mat4::from_cols_array_2d(&ibm[bone_idx]);
778
779 let parent_index = node_parents
780 .get(&joint_node.index())
781 .and_then(|p| node_to_bone.get(p).copied());
782
783 let (t, r, s) = joint_node.transform().decomposed();
784 let bind_translation = Vec3::new(t[0], t[1], t[2]);
785 let bind_rotation = Quat::from_array(r);
786 let bind_scale = Vec3::new(s[0], s[1], s[2]);
787
788 let local_bind_transform = gizmo_math::Mat4::from_translation(bind_translation)
789 * gizmo_math::Mat4::from_quat(bind_rotation)
790 * gizmo_math::Mat4::from_scale(bind_scale);
791
792 SkeletonJoint {
793 name: joint_node.name().unwrap_or("bone").to_string(),
794 node_index: joint_node.index(),
795 inverse_bind_matrix,
796 parent_index,
797 local_bind_transform,
798 bind_translation,
799 bind_rotation,
800 bind_scale,
801 }
802 })
803 .collect();
804
805 let root_transform =
811 compute_armature_root_transform(&skin, node_parents, &node_to_bone, nodes_by_index);
812
813 SkeletonHierarchy {
814 joints,
815 root_transform,
816 }
817 })
818 .collect()
819}
820
821fn compute_armature_root_transform(
824 skin: &gltf::Skin,
825 node_parents: &std::collections::HashMap<usize, usize>,
826 node_to_bone: &std::collections::HashMap<usize, usize>,
827 nodes_by_index: &[gltf::Node],
828) -> gizmo_math::Mat4 {
829 let mut root_transform = gizmo_math::Mat4::IDENTITY;
830
831 let first_joint = match skin.joints().next() {
832 Some(j) => j,
833 None => return root_transform,
834 };
835
836 let mut current_idx = first_joint.index();
837 let mut ancestor_transforms: Vec<gizmo_math::Mat4> = Vec::new();
838
839 while let Some(&parent_idx) = node_parents.get(¤t_idx) {
840 if node_to_bone.contains_key(&parent_idx) {
843 break;
844 }
845
846 if let Some(parent_node) = nodes_by_index.get(parent_idx) {
847 let (t, r, s) = parent_node.transform().decomposed();
848 let mat = gizmo_math::Mat4::from_translation(Vec3::new(t[0], t[1], t[2]))
849 * gizmo_math::Mat4::from_quat(Quat::from_array(r))
850 * gizmo_math::Mat4::from_scale(Vec3::new(s[0], s[1], s[2]));
851 ancestor_transforms.push(mat);
852 }
853
854 current_idx = parent_idx;
855 }
856
857 for mat in ancestor_transforms.into_iter().rev() {
859 root_transform *= mat;
860 }
861
862 root_transform
863}