bevy/crates/bevy_gltf/src/loader/gltf_ext/scene.rs
2025-07-14 03:30:25 +02:00

107 lines
3.0 KiB
Rust

use bevy_ecs::name::Name;
use bevy_math::{Mat4, Vec3};
use bevy_transform::components::Transform;
use gltf::scene::Node;
use fixedbitset::FixedBitSet;
use itertools::Itertools;
#[cfg(feature = "bevy_animation")]
use bevy_platform::collections::{HashMap, HashSet};
use crate::{
convert_coordinates::{ConvertCameraCoordinates as _, ConvertCoordinates as _},
GltfError,
};
pub(crate) fn node_name(node: &Node) -> Name {
let name = node
.name()
.map(ToString::to_string)
.unwrap_or_else(|| format!("GltfNode{}", node.index()));
Name::new(name)
}
/// Calculate the transform of gLTF [`Node`].
///
/// This should be used instead of calling [`gltf::scene::Transform::matrix()`]
/// on [`Node::transform()`](gltf::Node::transform) directly because it uses optimized glam types and
/// if `libm` feature of `bevy_math` crate is enabled also handles cross
/// platform determinism properly.
pub(crate) fn node_transform(node: &Node, convert_coordinates: bool) -> Transform {
let transform = match node.transform() {
gltf::scene::Transform::Matrix { matrix } => {
Transform::from_matrix(Mat4::from_cols_array_2d(&matrix))
}
gltf::scene::Transform::Decomposed {
translation,
rotation,
scale,
} => Transform {
translation: Vec3::from(translation),
rotation: bevy_math::Quat::from_array(rotation),
scale: Vec3::from(scale),
},
};
if convert_coordinates {
if node.camera().is_some() || node.light().is_some() {
transform.convert_camera_coordinates()
} else {
transform.convert_coordinates()
}
} else {
transform
}
}
#[cfg_attr(
not(target_arch = "wasm32"),
expect(
clippy::result_large_err,
reason = "need to be signature compatible with `load_gltf`"
)
)]
/// Check if [`Node`] is part of cycle
pub(crate) fn check_is_part_of_cycle(
node: &Node,
visited: &mut FixedBitSet,
) -> Result<(), GltfError> {
// Do we have a cycle?
if visited.contains(node.index()) {
return Err(GltfError::CircularChildren(format!(
"glTF nodes form a cycle: {} -> {}",
visited.ones().map(|bit| bit.to_string()).join(" -> "),
node.index()
)));
}
// Recurse.
visited.insert(node.index());
for kid in node.children() {
check_is_part_of_cycle(&kid, visited)?;
}
visited.remove(node.index());
Ok(())
}
#[cfg(feature = "bevy_animation")]
pub(crate) fn collect_path(
node: &Node,
current_path: &[Name],
paths: &mut HashMap<usize, (usize, Vec<Name>)>,
root_index: usize,
visited: &mut HashSet<usize>,
) {
let mut path = current_path.to_owned();
path.push(node_name(node));
visited.insert(node.index());
for child in node.children() {
if !visited.contains(&child.index()) {
collect_path(&child, &path, paths, root_index, visited);
}
}
paths.insert(node.index(), (root_index, path));
}