Refactor bevy_gltf
(#15994)
# Objective Refactor `bevy_gltf`, the criteria for the split is kind of arbitrary but at least it is not a 2.6k line file. ## Solution Move methods and structs found in `bevy_gltf/loader.rs` into multiple new modules. ## Testing `cargo run -p ci`
This commit is contained in:
parent
831ecf030c
commit
11db71727d
315
crates/bevy_gltf/src/assets.rs
Normal file
315
crates/bevy_gltf/src/assets.rs
Normal file
@ -0,0 +1,315 @@
|
||||
//! Representation of assets present in a glTF file
|
||||
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
use bevy_animation::AnimationClip;
|
||||
use bevy_asset::{Asset, Handle};
|
||||
use bevy_ecs::{component::Component, reflect::ReflectComponent};
|
||||
use bevy_pbr::StandardMaterial;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
|
||||
use bevy_render::mesh::{skinning::SkinnedMeshInverseBindposes, Mesh};
|
||||
use bevy_scene::Scene;
|
||||
|
||||
use crate::GltfAssetLabel;
|
||||
|
||||
/// Representation of a loaded glTF file.
|
||||
#[derive(Asset, Debug, TypePath)]
|
||||
pub struct Gltf {
|
||||
/// All scenes loaded from the glTF file.
|
||||
pub scenes: Vec<Handle<Scene>>,
|
||||
/// Named scenes loaded from the glTF file.
|
||||
pub named_scenes: HashMap<Box<str>, Handle<Scene>>,
|
||||
/// All meshes loaded from the glTF file.
|
||||
pub meshes: Vec<Handle<GltfMesh>>,
|
||||
/// Named meshes loaded from the glTF file.
|
||||
pub named_meshes: HashMap<Box<str>, Handle<GltfMesh>>,
|
||||
/// All materials loaded from the glTF file.
|
||||
pub materials: Vec<Handle<StandardMaterial>>,
|
||||
/// Named materials loaded from the glTF file.
|
||||
pub named_materials: HashMap<Box<str>, Handle<StandardMaterial>>,
|
||||
/// All nodes loaded from the glTF file.
|
||||
pub nodes: Vec<Handle<GltfNode>>,
|
||||
/// Named nodes loaded from the glTF file.
|
||||
pub named_nodes: HashMap<Box<str>, Handle<GltfNode>>,
|
||||
/// All skins loaded from the glTF file.
|
||||
pub skins: Vec<Handle<GltfSkin>>,
|
||||
/// Named skins loaded from the glTF file.
|
||||
pub named_skins: HashMap<Box<str>, Handle<GltfSkin>>,
|
||||
/// Default scene to be displayed.
|
||||
pub default_scene: Option<Handle<Scene>>,
|
||||
/// All animations loaded from the glTF file.
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
pub animations: Vec<Handle<AnimationClip>>,
|
||||
/// Named animations loaded from the glTF file.
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
pub named_animations: HashMap<Box<str>, Handle<AnimationClip>>,
|
||||
/// The gltf root of the gltf asset, see <https://docs.rs/gltf/latest/gltf/struct.Gltf.html>. Only has a value when `GltfLoaderSettings::include_source` is true.
|
||||
pub source: Option<gltf::Gltf>,
|
||||
}
|
||||
|
||||
/// A glTF mesh, which may consist of multiple [`GltfPrimitives`](GltfPrimitive)
|
||||
/// and an optional [`GltfExtras`].
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh).
|
||||
#[derive(Asset, Debug, Clone, TypePath)]
|
||||
pub struct GltfMesh {
|
||||
/// Index of the mesh inside the scene
|
||||
pub index: usize,
|
||||
/// Computed name for a mesh - either a user defined mesh name from gLTF or a generated name from index
|
||||
pub name: String,
|
||||
/// Primitives of the glTF mesh.
|
||||
pub primitives: Vec<GltfPrimitive>,
|
||||
/// Additional data.
|
||||
pub extras: Option<GltfExtras>,
|
||||
}
|
||||
|
||||
impl GltfMesh {
|
||||
/// Create a mesh extracting name and index from glTF def
|
||||
pub fn new(
|
||||
mesh: &gltf::Mesh,
|
||||
primitives: Vec<GltfPrimitive>,
|
||||
extras: Option<GltfExtras>,
|
||||
) -> Self {
|
||||
Self {
|
||||
index: mesh.index(),
|
||||
name: if let Some(name) = mesh.name() {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("GltfMesh{}", mesh.index())
|
||||
},
|
||||
primitives,
|
||||
extras,
|
||||
}
|
||||
}
|
||||
|
||||
/// Subasset label for this mesh within the gLTF parent asset.
|
||||
pub fn asset_label(&self) -> GltfAssetLabel {
|
||||
GltfAssetLabel::Mesh(self.index)
|
||||
}
|
||||
}
|
||||
|
||||
/// A glTF node with all of its child nodes, its [`GltfMesh`],
|
||||
/// [`Transform`](bevy_transform::prelude::Transform), its optional [`GltfSkin`]
|
||||
/// and an optional [`GltfExtras`].
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-node).
|
||||
#[derive(Asset, Debug, Clone, TypePath)]
|
||||
pub struct GltfNode {
|
||||
/// Index of the node inside the scene
|
||||
pub index: usize,
|
||||
/// Computed name for a node - either a user defined node name from gLTF or a generated name from index
|
||||
pub name: String,
|
||||
/// Direct children of the node.
|
||||
pub children: Vec<Handle<GltfNode>>,
|
||||
/// Mesh of the node.
|
||||
pub mesh: Option<Handle<GltfMesh>>,
|
||||
/// Skin of the node.
|
||||
pub skin: Option<Handle<GltfSkin>>,
|
||||
/// Local transform.
|
||||
pub transform: bevy_transform::prelude::Transform,
|
||||
/// Is this node used as an animation root
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
pub is_animation_root: bool,
|
||||
/// Additional data.
|
||||
pub extras: Option<GltfExtras>,
|
||||
}
|
||||
|
||||
impl GltfNode {
|
||||
/// Create a node extracting name and index from glTF def
|
||||
pub fn new(
|
||||
node: &gltf::Node,
|
||||
children: Vec<Handle<GltfNode>>,
|
||||
mesh: Option<Handle<GltfMesh>>,
|
||||
transform: bevy_transform::prelude::Transform,
|
||||
skin: Option<Handle<GltfSkin>>,
|
||||
extras: Option<GltfExtras>,
|
||||
) -> Self {
|
||||
Self {
|
||||
index: node.index(),
|
||||
name: if let Some(name) = node.name() {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("GltfNode{}", node.index())
|
||||
},
|
||||
children,
|
||||
mesh,
|
||||
transform,
|
||||
skin,
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
is_animation_root: false,
|
||||
extras,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a node with animation root mark
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
pub fn with_animation_root(self, is_animation_root: bool) -> Self {
|
||||
Self {
|
||||
is_animation_root,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Subasset label for this node within the gLTF parent asset.
|
||||
pub fn asset_label(&self) -> GltfAssetLabel {
|
||||
GltfAssetLabel::Node(self.index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Part of a [`GltfMesh`] that consists of a [`Mesh`], an optional [`StandardMaterial`] and [`GltfExtras`].
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh-primitive).
|
||||
#[derive(Asset, Debug, Clone, TypePath)]
|
||||
pub struct GltfPrimitive {
|
||||
/// Index of the primitive inside the mesh
|
||||
pub index: usize,
|
||||
/// Index of the parent [`GltfMesh`] of this primitive
|
||||
pub parent_mesh_index: usize,
|
||||
/// Computed name for a primitive - either a user defined primitive name from gLTF or a generated name from index
|
||||
pub name: String,
|
||||
/// Topology to be rendered.
|
||||
pub mesh: Handle<Mesh>,
|
||||
/// Material to apply to the `mesh`.
|
||||
pub material: Option<Handle<StandardMaterial>>,
|
||||
/// Additional data.
|
||||
pub extras: Option<GltfExtras>,
|
||||
/// Additional data of the `material`.
|
||||
pub material_extras: Option<GltfExtras>,
|
||||
}
|
||||
|
||||
impl GltfPrimitive {
|
||||
/// Create a primitive extracting name and index from glTF def
|
||||
pub fn new(
|
||||
gltf_mesh: &gltf::Mesh,
|
||||
gltf_primitive: &gltf::Primitive,
|
||||
mesh: Handle<Mesh>,
|
||||
material: Option<Handle<StandardMaterial>>,
|
||||
extras: Option<GltfExtras>,
|
||||
material_extras: Option<GltfExtras>,
|
||||
) -> Self {
|
||||
GltfPrimitive {
|
||||
index: gltf_primitive.index(),
|
||||
parent_mesh_index: gltf_mesh.index(),
|
||||
name: {
|
||||
let mesh_name = gltf_mesh.name().unwrap_or("Mesh");
|
||||
if gltf_mesh.primitives().len() > 1 {
|
||||
format!("{}.{}", mesh_name, gltf_primitive.index())
|
||||
} else {
|
||||
mesh_name.to_string()
|
||||
}
|
||||
},
|
||||
mesh,
|
||||
material,
|
||||
extras,
|
||||
material_extras,
|
||||
}
|
||||
}
|
||||
|
||||
/// Subasset label for this primitive within its parent [`GltfMesh`] within the gLTF parent asset.
|
||||
pub fn asset_label(&self) -> GltfAssetLabel {
|
||||
GltfAssetLabel::Primitive {
|
||||
mesh: self.parent_mesh_index,
|
||||
primitive: self.index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A glTF skin with all of its joint nodes, [`SkinnedMeshInversiveBindposes`](bevy_render::mesh::skinning::SkinnedMeshInverseBindposes)
|
||||
/// and an optional [`GltfExtras`].
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-skin).
|
||||
#[derive(Asset, Debug, Clone, TypePath)]
|
||||
pub struct GltfSkin {
|
||||
/// Index of the skin inside the scene
|
||||
pub index: usize,
|
||||
/// Computed name for a skin - either a user defined skin name from gLTF or a generated name from index
|
||||
pub name: String,
|
||||
/// All the nodes that form this skin.
|
||||
pub joints: Vec<Handle<GltfNode>>,
|
||||
/// Inverse-bind matrices of this skin.
|
||||
pub inverse_bind_matrices: Handle<SkinnedMeshInverseBindposes>,
|
||||
/// Additional data.
|
||||
pub extras: Option<GltfExtras>,
|
||||
}
|
||||
|
||||
impl GltfSkin {
|
||||
/// Create a skin extracting name and index from glTF def
|
||||
pub fn new(
|
||||
skin: &gltf::Skin,
|
||||
joints: Vec<Handle<GltfNode>>,
|
||||
inverse_bind_matrices: Handle<SkinnedMeshInverseBindposes>,
|
||||
extras: Option<GltfExtras>,
|
||||
) -> Self {
|
||||
Self {
|
||||
index: skin.index(),
|
||||
name: if let Some(name) = skin.name() {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("GltfSkin{}", skin.index())
|
||||
},
|
||||
joints,
|
||||
inverse_bind_matrices,
|
||||
extras,
|
||||
}
|
||||
}
|
||||
|
||||
/// Subasset label for this skin within the gLTF parent asset.
|
||||
pub fn asset_label(&self) -> GltfAssetLabel {
|
||||
GltfAssetLabel::Skin(self.index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional untyped data that can be present on most glTF types at the primitive level.
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
|
||||
#[derive(Clone, Debug, Reflect, Default, Component)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct GltfExtras {
|
||||
/// Content of the extra data.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl From<&serde_json::value::RawValue> for GltfExtras {
|
||||
fn from(value: &serde_json::value::RawValue) -> Self {
|
||||
GltfExtras {
|
||||
value: value.get().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional untyped data that can be present on most glTF types at the scene level.
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
|
||||
#[derive(Clone, Debug, Reflect, Default, Component)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct GltfSceneExtras {
|
||||
/// Content of the extra data.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// Additional untyped data that can be present on most glTF types at the mesh level.
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
|
||||
#[derive(Clone, Debug, Reflect, Default, Component)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct GltfMeshExtras {
|
||||
/// Content of the extra data.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// Additional untyped data that can be present on most glTF types at the material level.
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
|
||||
#[derive(Clone, Debug, Reflect, Default, Component)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct GltfMaterialExtras {
|
||||
/// Content of the extra data.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// The material name of a glTF primitive.
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-material).
|
||||
#[derive(Clone, Debug, Reflect, Default, Component)]
|
||||
#[reflect(Component)]
|
||||
pub struct GltfMaterialName(pub String);
|
127
crates/bevy_gltf/src/label.rs
Normal file
127
crates/bevy_gltf/src/label.rs
Normal file
@ -0,0 +1,127 @@
|
||||
//! Labels that can be used to load part of a glTF
|
||||
|
||||
use bevy_asset::AssetPath;
|
||||
|
||||
/// Labels that can be used to load part of a glTF
|
||||
///
|
||||
/// You can use [`GltfAssetLabel::from_asset`] to add it to an asset path
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_asset::prelude::*;
|
||||
/// # use bevy_scene::prelude::*;
|
||||
/// # use bevy_gltf::prelude::*;
|
||||
///
|
||||
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
|
||||
/// let gltf_scene: Handle<Scene> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Or when formatting a string for the path
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_asset::prelude::*;
|
||||
/// # use bevy_scene::prelude::*;
|
||||
/// # use bevy_gltf::prelude::*;
|
||||
///
|
||||
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
|
||||
/// let gltf_scene: Handle<Scene> = asset_server.load(format!("models/FlightHelmet/FlightHelmet.gltf#{}", GltfAssetLabel::Scene(0)));
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum GltfAssetLabel {
|
||||
/// `Scene{}`: glTF Scene as a Bevy [`Scene`](bevy_scene::Scene)
|
||||
Scene(usize),
|
||||
/// `Node{}`: glTF Node as a [`GltfNode`](crate::GltfNode)
|
||||
Node(usize),
|
||||
/// `Mesh{}`: glTF Mesh as a [`GltfMesh`](crate::GltfMesh)
|
||||
Mesh(usize),
|
||||
/// `Mesh{}/Primitive{}`: glTF Primitive as a Bevy [`Mesh`](bevy_render::mesh::Mesh)
|
||||
Primitive {
|
||||
/// Index of the mesh for this primitive
|
||||
mesh: usize,
|
||||
/// Index of this primitive in its parent mesh
|
||||
primitive: usize,
|
||||
},
|
||||
/// `Mesh{}/Primitive{}/MorphTargets`: Morph target animation data for a glTF Primitive
|
||||
/// as a Bevy [`Image`](bevy_image::prelude::Image)
|
||||
MorphTarget {
|
||||
/// Index of the mesh for this primitive
|
||||
mesh: usize,
|
||||
/// Index of this primitive in its parent mesh
|
||||
primitive: usize,
|
||||
},
|
||||
/// `Texture{}`: glTF Texture as a Bevy [`Image`](bevy_image::prelude::Image)
|
||||
Texture(usize),
|
||||
/// `Material{}`: glTF Material as a Bevy [`StandardMaterial`](bevy_pbr::StandardMaterial)
|
||||
Material {
|
||||
/// Index of this material
|
||||
index: usize,
|
||||
/// Used to set the [`Face`](bevy_render::render_resource::Face) of the material,
|
||||
/// useful if it is used with negative scale
|
||||
is_scale_inverted: bool,
|
||||
},
|
||||
/// `DefaultMaterial`: glTF's default Material as a
|
||||
/// Bevy [`StandardMaterial`](bevy_pbr::StandardMaterial)
|
||||
DefaultMaterial,
|
||||
/// `Animation{}`: glTF Animation as Bevy [`AnimationClip`](bevy_animation::AnimationClip)
|
||||
Animation(usize),
|
||||
/// `Skin{}`: glTF mesh skin as [`GltfSkin`](crate::GltfSkin)
|
||||
Skin(usize),
|
||||
/// `Skin{}/InverseBindMatrices`: glTF mesh skin matrices as Bevy
|
||||
/// [`SkinnedMeshInverseBindposes`](bevy_render::mesh::skinning::SkinnedMeshInverseBindposes)
|
||||
InverseBindMatrices(usize),
|
||||
}
|
||||
|
||||
impl core::fmt::Display for GltfAssetLabel {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
GltfAssetLabel::Scene(index) => f.write_str(&format!("Scene{index}")),
|
||||
GltfAssetLabel::Node(index) => f.write_str(&format!("Node{index}")),
|
||||
GltfAssetLabel::Mesh(index) => f.write_str(&format!("Mesh{index}")),
|
||||
GltfAssetLabel::Primitive { mesh, primitive } => {
|
||||
f.write_str(&format!("Mesh{mesh}/Primitive{primitive}"))
|
||||
}
|
||||
GltfAssetLabel::MorphTarget { mesh, primitive } => {
|
||||
f.write_str(&format!("Mesh{mesh}/Primitive{primitive}/MorphTargets"))
|
||||
}
|
||||
GltfAssetLabel::Texture(index) => f.write_str(&format!("Texture{index}")),
|
||||
GltfAssetLabel::Material {
|
||||
index,
|
||||
is_scale_inverted,
|
||||
} => f.write_str(&format!(
|
||||
"Material{index}{}",
|
||||
if *is_scale_inverted {
|
||||
" (inverted)"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)),
|
||||
GltfAssetLabel::DefaultMaterial => f.write_str("DefaultMaterial"),
|
||||
GltfAssetLabel::Animation(index) => f.write_str(&format!("Animation{index}")),
|
||||
GltfAssetLabel::Skin(index) => f.write_str(&format!("Skin{index}")),
|
||||
GltfAssetLabel::InverseBindMatrices(index) => {
|
||||
f.write_str(&format!("Skin{index}/InverseBindMatrices"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GltfAssetLabel {
|
||||
/// Add this label to an asset path
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_asset::prelude::*;
|
||||
/// # use bevy_scene::prelude::*;
|
||||
/// # use bevy_gltf::prelude::*;
|
||||
///
|
||||
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
|
||||
/// let gltf_scene: Handle<Scene> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn from_asset(&self, path: impl Into<AssetPath<'static>>) -> AssetPath<'static> {
|
||||
path.into().with_label(self.to_string())
|
||||
}
|
||||
}
|
@ -90,36 +90,30 @@
|
||||
//!
|
||||
//! You can use [`GltfAssetLabel`] to ensure you are using the correct label.
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
use bevy_animation::AnimationClip;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
|
||||
mod assets;
|
||||
mod label;
|
||||
mod loader;
|
||||
mod vertex_attributes;
|
||||
pub use loader::*;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{Asset, AssetApp, AssetPath, Handle};
|
||||
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
|
||||
use bevy_asset::AssetApp;
|
||||
use bevy_image::CompressedImageFormats;
|
||||
use bevy_pbr::StandardMaterial;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
|
||||
use bevy_render::{
|
||||
mesh::{skinning::SkinnedMeshInverseBindposes, Mesh, MeshVertexAttribute},
|
||||
renderer::RenderDevice,
|
||||
};
|
||||
use bevy_scene::Scene;
|
||||
use bevy_render::{mesh::MeshVertexAttribute, renderer::RenderDevice};
|
||||
|
||||
/// The glTF prelude.
|
||||
///
|
||||
/// This includes the most common types in this crate, re-exported for your convenience.
|
||||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{Gltf, GltfAssetLabel, GltfExtras};
|
||||
pub use crate::{assets::Gltf, assets::GltfExtras, label::GltfAssetLabel};
|
||||
}
|
||||
|
||||
pub use {assets::*, label::GltfAssetLabel, loader::*};
|
||||
|
||||
/// Adds support for glTF file loading to the app.
|
||||
#[derive(Default)]
|
||||
pub struct GltfPlugin {
|
||||
@ -168,418 +162,3 @@ impl Plugin for GltfPlugin {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a loaded glTF file.
|
||||
#[derive(Asset, Debug, TypePath)]
|
||||
pub struct Gltf {
|
||||
/// All scenes loaded from the glTF file.
|
||||
pub scenes: Vec<Handle<Scene>>,
|
||||
/// Named scenes loaded from the glTF file.
|
||||
pub named_scenes: HashMap<Box<str>, Handle<Scene>>,
|
||||
/// All meshes loaded from the glTF file.
|
||||
pub meshes: Vec<Handle<GltfMesh>>,
|
||||
/// Named meshes loaded from the glTF file.
|
||||
pub named_meshes: HashMap<Box<str>, Handle<GltfMesh>>,
|
||||
/// All materials loaded from the glTF file.
|
||||
pub materials: Vec<Handle<StandardMaterial>>,
|
||||
/// Named materials loaded from the glTF file.
|
||||
pub named_materials: HashMap<Box<str>, Handle<StandardMaterial>>,
|
||||
/// All nodes loaded from the glTF file.
|
||||
pub nodes: Vec<Handle<GltfNode>>,
|
||||
/// Named nodes loaded from the glTF file.
|
||||
pub named_nodes: HashMap<Box<str>, Handle<GltfNode>>,
|
||||
/// All skins loaded from the glTF file.
|
||||
pub skins: Vec<Handle<GltfSkin>>,
|
||||
/// Named skins loaded from the glTF file.
|
||||
pub named_skins: HashMap<Box<str>, Handle<GltfSkin>>,
|
||||
/// Default scene to be displayed.
|
||||
pub default_scene: Option<Handle<Scene>>,
|
||||
/// All animations loaded from the glTF file.
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
pub animations: Vec<Handle<AnimationClip>>,
|
||||
/// Named animations loaded from the glTF file.
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
pub named_animations: HashMap<Box<str>, Handle<AnimationClip>>,
|
||||
/// The gltf root of the gltf asset, see <https://docs.rs/gltf/latest/gltf/struct.Gltf.html>. Only has a value when `GltfLoaderSettings::include_source` is true.
|
||||
pub source: Option<gltf::Gltf>,
|
||||
}
|
||||
|
||||
/// A glTF node with all of its child nodes, its [`GltfMesh`],
|
||||
/// [`Transform`](bevy_transform::prelude::Transform), its optional [`GltfSkin`]
|
||||
/// and an optional [`GltfExtras`].
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-node).
|
||||
#[derive(Asset, Debug, Clone, TypePath)]
|
||||
pub struct GltfNode {
|
||||
/// Index of the node inside the scene
|
||||
pub index: usize,
|
||||
/// Computed name for a node - either a user defined node name from gLTF or a generated name from index
|
||||
pub name: String,
|
||||
/// Direct children of the node.
|
||||
pub children: Vec<Handle<GltfNode>>,
|
||||
/// Mesh of the node.
|
||||
pub mesh: Option<Handle<GltfMesh>>,
|
||||
/// Skin of the node.
|
||||
pub skin: Option<Handle<GltfSkin>>,
|
||||
/// Local transform.
|
||||
pub transform: bevy_transform::prelude::Transform,
|
||||
/// Is this node used as an animation root
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
pub is_animation_root: bool,
|
||||
/// Additional data.
|
||||
pub extras: Option<GltfExtras>,
|
||||
}
|
||||
|
||||
impl GltfNode {
|
||||
/// Create a node extracting name and index from glTF def
|
||||
pub fn new(
|
||||
node: &gltf::Node,
|
||||
children: Vec<Handle<GltfNode>>,
|
||||
mesh: Option<Handle<GltfMesh>>,
|
||||
transform: bevy_transform::prelude::Transform,
|
||||
skin: Option<Handle<GltfSkin>>,
|
||||
extras: Option<GltfExtras>,
|
||||
) -> Self {
|
||||
Self {
|
||||
index: node.index(),
|
||||
name: if let Some(name) = node.name() {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("GltfNode{}", node.index())
|
||||
},
|
||||
children,
|
||||
mesh,
|
||||
transform,
|
||||
skin,
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
is_animation_root: false,
|
||||
extras,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a node with animation root mark
|
||||
#[cfg(feature = "bevy_animation")]
|
||||
pub fn with_animation_root(self, is_animation_root: bool) -> Self {
|
||||
Self {
|
||||
is_animation_root,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Subasset label for this node within the gLTF parent asset.
|
||||
pub fn asset_label(&self) -> GltfAssetLabel {
|
||||
GltfAssetLabel::Node(self.index)
|
||||
}
|
||||
}
|
||||
|
||||
/// A glTF skin with all of its joint nodes, [`SkinnedMeshInversiveBindposes`](bevy_render::mesh::skinning::SkinnedMeshInverseBindposes)
|
||||
/// and an optional [`GltfExtras`].
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-skin).
|
||||
#[derive(Asset, Debug, Clone, TypePath)]
|
||||
pub struct GltfSkin {
|
||||
/// Index of the skin inside the scene
|
||||
pub index: usize,
|
||||
/// Computed name for a skin - either a user defined skin name from gLTF or a generated name from index
|
||||
pub name: String,
|
||||
/// All the nodes that form this skin.
|
||||
pub joints: Vec<Handle<GltfNode>>,
|
||||
/// Inverse-bind matrices of this skin.
|
||||
pub inverse_bind_matrices: Handle<SkinnedMeshInverseBindposes>,
|
||||
/// Additional data.
|
||||
pub extras: Option<GltfExtras>,
|
||||
}
|
||||
|
||||
impl GltfSkin {
|
||||
/// Create a skin extracting name and index from glTF def
|
||||
pub fn new(
|
||||
skin: &gltf::Skin,
|
||||
joints: Vec<Handle<GltfNode>>,
|
||||
inverse_bind_matrices: Handle<SkinnedMeshInverseBindposes>,
|
||||
extras: Option<GltfExtras>,
|
||||
) -> Self {
|
||||
Self {
|
||||
index: skin.index(),
|
||||
name: if let Some(name) = skin.name() {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("GltfSkin{}", skin.index())
|
||||
},
|
||||
joints,
|
||||
inverse_bind_matrices,
|
||||
extras,
|
||||
}
|
||||
}
|
||||
|
||||
/// Subasset label for this skin within the gLTF parent asset.
|
||||
pub fn asset_label(&self) -> GltfAssetLabel {
|
||||
GltfAssetLabel::Skin(self.index)
|
||||
}
|
||||
}
|
||||
|
||||
/// A glTF mesh, which may consist of multiple [`GltfPrimitives`](GltfPrimitive)
|
||||
/// and an optional [`GltfExtras`].
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh).
|
||||
#[derive(Asset, Debug, Clone, TypePath)]
|
||||
pub struct GltfMesh {
|
||||
/// Index of the mesh inside the scene
|
||||
pub index: usize,
|
||||
/// Computed name for a mesh - either a user defined mesh name from gLTF or a generated name from index
|
||||
pub name: String,
|
||||
/// Primitives of the glTF mesh.
|
||||
pub primitives: Vec<GltfPrimitive>,
|
||||
/// Additional data.
|
||||
pub extras: Option<GltfExtras>,
|
||||
}
|
||||
|
||||
impl GltfMesh {
|
||||
/// Create a mesh extracting name and index from glTF def
|
||||
pub fn new(
|
||||
mesh: &gltf::Mesh,
|
||||
primitives: Vec<GltfPrimitive>,
|
||||
extras: Option<GltfExtras>,
|
||||
) -> Self {
|
||||
Self {
|
||||
index: mesh.index(),
|
||||
name: if let Some(name) = mesh.name() {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("GltfMesh{}", mesh.index())
|
||||
},
|
||||
primitives,
|
||||
extras,
|
||||
}
|
||||
}
|
||||
|
||||
/// Subasset label for this mesh within the gLTF parent asset.
|
||||
pub fn asset_label(&self) -> GltfAssetLabel {
|
||||
GltfAssetLabel::Mesh(self.index)
|
||||
}
|
||||
}
|
||||
|
||||
/// Part of a [`GltfMesh`] that consists of a [`Mesh`], an optional [`StandardMaterial`] and [`GltfExtras`].
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-mesh-primitive).
|
||||
#[derive(Asset, Debug, Clone, TypePath)]
|
||||
pub struct GltfPrimitive {
|
||||
/// Index of the primitive inside the mesh
|
||||
pub index: usize,
|
||||
/// Index of the parent [`GltfMesh`] of this primitive
|
||||
pub parent_mesh_index: usize,
|
||||
/// Computed name for a primitive - either a user defined primitive name from gLTF or a generated name from index
|
||||
pub name: String,
|
||||
/// Topology to be rendered.
|
||||
pub mesh: Handle<Mesh>,
|
||||
/// Material to apply to the `mesh`.
|
||||
pub material: Option<Handle<StandardMaterial>>,
|
||||
/// Additional data.
|
||||
pub extras: Option<GltfExtras>,
|
||||
/// Additional data of the `material`.
|
||||
pub material_extras: Option<GltfExtras>,
|
||||
}
|
||||
|
||||
impl GltfPrimitive {
|
||||
/// Create a primitive extracting name and index from glTF def
|
||||
pub fn new(
|
||||
gltf_mesh: &gltf::Mesh,
|
||||
gltf_primitive: &gltf::Primitive,
|
||||
mesh: Handle<Mesh>,
|
||||
material: Option<Handle<StandardMaterial>>,
|
||||
extras: Option<GltfExtras>,
|
||||
material_extras: Option<GltfExtras>,
|
||||
) -> Self {
|
||||
GltfPrimitive {
|
||||
index: gltf_primitive.index(),
|
||||
parent_mesh_index: gltf_mesh.index(),
|
||||
name: {
|
||||
let mesh_name = gltf_mesh.name().unwrap_or("Mesh");
|
||||
if gltf_mesh.primitives().len() > 1 {
|
||||
format!("{}.{}", mesh_name, gltf_primitive.index())
|
||||
} else {
|
||||
mesh_name.to_string()
|
||||
}
|
||||
},
|
||||
mesh,
|
||||
material,
|
||||
extras,
|
||||
material_extras,
|
||||
}
|
||||
}
|
||||
|
||||
/// Subasset label for this primitive within its parent [`GltfMesh`] within the gLTF parent asset.
|
||||
pub fn asset_label(&self) -> GltfAssetLabel {
|
||||
GltfAssetLabel::Primitive {
|
||||
mesh: self.parent_mesh_index,
|
||||
primitive: self.index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional untyped data that can be present on most glTF types at the primitive level.
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
|
||||
#[derive(Clone, Debug, Reflect, Default, Component)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct GltfExtras {
|
||||
/// Content of the extra data.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// Additional untyped data that can be present on most glTF types at the scene level.
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
|
||||
#[derive(Clone, Debug, Reflect, Default, Component)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct GltfSceneExtras {
|
||||
/// Content of the extra data.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// Additional untyped data that can be present on most glTF types at the mesh level.
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
|
||||
#[derive(Clone, Debug, Reflect, Default, Component)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct GltfMeshExtras {
|
||||
/// Content of the extra data.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// Additional untyped data that can be present on most glTF types at the material level.
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-extras).
|
||||
#[derive(Clone, Debug, Reflect, Default, Component)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct GltfMaterialExtras {
|
||||
/// Content of the extra data.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// The material name of a glTF primitive.
|
||||
///
|
||||
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-material).
|
||||
#[derive(Clone, Debug, Reflect, Default, Component)]
|
||||
#[reflect(Component)]
|
||||
pub struct GltfMaterialName(pub String);
|
||||
|
||||
/// Labels that can be used to load part of a glTF
|
||||
///
|
||||
/// You can use [`GltfAssetLabel::from_asset`] to add it to an asset path
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_asset::prelude::*;
|
||||
/// # use bevy_scene::prelude::*;
|
||||
/// # use bevy_gltf::prelude::*;
|
||||
///
|
||||
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
|
||||
/// let gltf_scene: Handle<Scene> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Or when formatting a string for the path
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_asset::prelude::*;
|
||||
/// # use bevy_scene::prelude::*;
|
||||
/// # use bevy_gltf::prelude::*;
|
||||
///
|
||||
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
|
||||
/// let gltf_scene: Handle<Scene> = asset_server.load(format!("models/FlightHelmet/FlightHelmet.gltf#{}", GltfAssetLabel::Scene(0)));
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum GltfAssetLabel {
|
||||
/// `Scene{}`: glTF Scene as a Bevy [`Scene`]
|
||||
Scene(usize),
|
||||
/// `Node{}`: glTF Node as a [`GltfNode`]
|
||||
Node(usize),
|
||||
/// `Mesh{}`: glTF Mesh as a [`GltfMesh`]
|
||||
Mesh(usize),
|
||||
/// `Mesh{}/Primitive{}`: glTF Primitive as a Bevy [`Mesh`]
|
||||
Primitive {
|
||||
/// Index of the mesh for this primitive
|
||||
mesh: usize,
|
||||
/// Index of this primitive in its parent mesh
|
||||
primitive: usize,
|
||||
},
|
||||
/// `Mesh{}/Primitive{}/MorphTargets`: Morph target animation data for a glTF Primitive
|
||||
/// as a Bevy [`Image`](bevy_image::prelude::Image)
|
||||
MorphTarget {
|
||||
/// Index of the mesh for this primitive
|
||||
mesh: usize,
|
||||
/// Index of this primitive in its parent mesh
|
||||
primitive: usize,
|
||||
},
|
||||
/// `Texture{}`: glTF Texture as a Bevy [`Image`](bevy_image::prelude::Image)
|
||||
Texture(usize),
|
||||
/// `Material{}`: glTF Material as a Bevy [`StandardMaterial`]
|
||||
Material {
|
||||
/// Index of this material
|
||||
index: usize,
|
||||
/// Used to set the [`Face`](bevy_render::render_resource::Face) of the material, useful if it is used with negative scale
|
||||
is_scale_inverted: bool,
|
||||
},
|
||||
/// `DefaultMaterial`: glTF's default Material as a Bevy [`StandardMaterial`]
|
||||
DefaultMaterial,
|
||||
/// `Animation{}`: glTF Animation as Bevy [`AnimationClip`]
|
||||
Animation(usize),
|
||||
/// `Skin{}`: glTF mesh skin as [`GltfSkin`]
|
||||
Skin(usize),
|
||||
/// `Skin{}/InverseBindMatrices`: glTF mesh skin matrices as Bevy [`SkinnedMeshInverseBindposes`]
|
||||
InverseBindMatrices(usize),
|
||||
}
|
||||
|
||||
impl core::fmt::Display for GltfAssetLabel {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
GltfAssetLabel::Scene(index) => f.write_str(&format!("Scene{index}")),
|
||||
GltfAssetLabel::Node(index) => f.write_str(&format!("Node{index}")),
|
||||
GltfAssetLabel::Mesh(index) => f.write_str(&format!("Mesh{index}")),
|
||||
GltfAssetLabel::Primitive { mesh, primitive } => {
|
||||
f.write_str(&format!("Mesh{mesh}/Primitive{primitive}"))
|
||||
}
|
||||
GltfAssetLabel::MorphTarget { mesh, primitive } => {
|
||||
f.write_str(&format!("Mesh{mesh}/Primitive{primitive}/MorphTargets"))
|
||||
}
|
||||
GltfAssetLabel::Texture(index) => f.write_str(&format!("Texture{index}")),
|
||||
GltfAssetLabel::Material {
|
||||
index,
|
||||
is_scale_inverted,
|
||||
} => f.write_str(&format!(
|
||||
"Material{index}{}",
|
||||
if *is_scale_inverted {
|
||||
" (inverted)"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
)),
|
||||
GltfAssetLabel::DefaultMaterial => f.write_str("DefaultMaterial"),
|
||||
GltfAssetLabel::Animation(index) => f.write_str(&format!("Animation{index}")),
|
||||
GltfAssetLabel::Skin(index) => f.write_str(&format!("Skin{index}")),
|
||||
GltfAssetLabel::InverseBindMatrices(index) => {
|
||||
f.write_str(&format!("Skin{index}/InverseBindMatrices"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GltfAssetLabel {
|
||||
/// Add this label to an asset path
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_asset::prelude::*;
|
||||
/// # use bevy_scene::prelude::*;
|
||||
/// # use bevy_gltf::prelude::*;
|
||||
///
|
||||
/// fn load_gltf_scene(asset_server: Res<AssetServer>) {
|
||||
/// let gltf_scene: Handle<Scene> = asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn from_asset(&self, path: impl Into<AssetPath<'static>>) -> AssetPath<'static> {
|
||||
path.into().with_label(self.to_string())
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,71 @@
|
||||
use bevy_asset::LoadContext;
|
||||
|
||||
use gltf::{Document, Material};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
#[cfg(feature = "pbr_anisotropy_texture")]
|
||||
use {
|
||||
crate::loader::gltf_ext::{material::uv_channel, texture::texture_handle_from_info},
|
||||
bevy_asset::Handle,
|
||||
bevy_image::Image,
|
||||
bevy_pbr::UvChannel,
|
||||
gltf::json::texture::Info,
|
||||
serde_json::value,
|
||||
};
|
||||
|
||||
/// Parsed data from the `KHR_materials_anisotropy` extension.
|
||||
///
|
||||
/// See the specification:
|
||||
/// <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_anisotropy/README.md>
|
||||
#[derive(Default)]
|
||||
pub(crate) struct AnisotropyExtension {
|
||||
pub(crate) anisotropy_strength: Option<f64>,
|
||||
pub(crate) anisotropy_rotation: Option<f64>,
|
||||
#[cfg(feature = "pbr_anisotropy_texture")]
|
||||
pub(crate) anisotropy_channel: UvChannel,
|
||||
#[cfg(feature = "pbr_anisotropy_texture")]
|
||||
pub(crate) anisotropy_texture: Option<Handle<Image>>,
|
||||
}
|
||||
|
||||
impl AnisotropyExtension {
|
||||
#[expect(
|
||||
clippy::allow_attributes,
|
||||
reason = "`unused_variables` is not always linted"
|
||||
)]
|
||||
#[allow(
|
||||
unused_variables,
|
||||
reason = "Depending on what features are used to compile this crate, certain parameters may end up unused."
|
||||
)]
|
||||
pub(crate) fn parse(
|
||||
load_context: &mut LoadContext,
|
||||
document: &Document,
|
||||
material: &Material,
|
||||
) -> Option<AnisotropyExtension> {
|
||||
let extension = material
|
||||
.extensions()?
|
||||
.get("KHR_materials_anisotropy")?
|
||||
.as_object()?;
|
||||
|
||||
#[cfg(feature = "pbr_anisotropy_texture")]
|
||||
let (anisotropy_channel, anisotropy_texture) = extension
|
||||
.get("anisotropyTexture")
|
||||
.and_then(|value| value::from_value::<Info>(value.clone()).ok())
|
||||
.map(|json_info| {
|
||||
(
|
||||
uv_channel(material, "anisotropy", json_info.tex_coord),
|
||||
texture_handle_from_info(&json_info, document, load_context),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
Some(AnisotropyExtension {
|
||||
anisotropy_strength: extension.get("anisotropyStrength").and_then(Value::as_f64),
|
||||
anisotropy_rotation: extension.get("anisotropyRotation").and_then(Value::as_f64),
|
||||
#[cfg(feature = "pbr_anisotropy_texture")]
|
||||
anisotropy_channel: anisotropy_channel.unwrap_or_default(),
|
||||
#[cfg(feature = "pbr_anisotropy_texture")]
|
||||
anisotropy_texture,
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
use bevy_asset::LoadContext;
|
||||
|
||||
use gltf::{Document, Material};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
use {
|
||||
crate::loader::gltf_ext::material::parse_material_extension_texture, bevy_asset::Handle,
|
||||
bevy_image::Image, bevy_pbr::UvChannel,
|
||||
};
|
||||
|
||||
/// Parsed data from the `KHR_materials_clearcoat` extension.
|
||||
///
|
||||
/// See the specification:
|
||||
/// <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_clearcoat/README.md>
|
||||
#[derive(Default)]
|
||||
pub(crate) struct ClearcoatExtension {
|
||||
pub(crate) clearcoat_factor: Option<f64>,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
pub(crate) clearcoat_channel: UvChannel,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
pub(crate) clearcoat_texture: Option<Handle<Image>>,
|
||||
pub(crate) clearcoat_roughness_factor: Option<f64>,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
pub(crate) clearcoat_roughness_channel: UvChannel,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
pub(crate) clearcoat_roughness_texture: Option<Handle<Image>>,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
pub(crate) clearcoat_normal_channel: UvChannel,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
pub(crate) clearcoat_normal_texture: Option<Handle<Image>>,
|
||||
}
|
||||
|
||||
impl ClearcoatExtension {
|
||||
#[expect(
|
||||
clippy::allow_attributes,
|
||||
reason = "`unused_variables` is not always linted"
|
||||
)]
|
||||
#[allow(
|
||||
unused_variables,
|
||||
reason = "Depending on what features are used to compile this crate, certain parameters may end up unused."
|
||||
)]
|
||||
pub(crate) fn parse(
|
||||
load_context: &mut LoadContext,
|
||||
document: &Document,
|
||||
material: &Material,
|
||||
) -> Option<ClearcoatExtension> {
|
||||
let extension = material
|
||||
.extensions()?
|
||||
.get("KHR_materials_clearcoat")?
|
||||
.as_object()?;
|
||||
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
let (clearcoat_channel, clearcoat_texture) = parse_material_extension_texture(
|
||||
material,
|
||||
load_context,
|
||||
document,
|
||||
extension,
|
||||
"clearcoatTexture",
|
||||
"clearcoat",
|
||||
);
|
||||
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
let (clearcoat_roughness_channel, clearcoat_roughness_texture) =
|
||||
parse_material_extension_texture(
|
||||
material,
|
||||
load_context,
|
||||
document,
|
||||
extension,
|
||||
"clearcoatRoughnessTexture",
|
||||
"clearcoat roughness",
|
||||
);
|
||||
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
let (clearcoat_normal_channel, clearcoat_normal_texture) = parse_material_extension_texture(
|
||||
material,
|
||||
load_context,
|
||||
document,
|
||||
extension,
|
||||
"clearcoatNormalTexture",
|
||||
"clearcoat normal",
|
||||
);
|
||||
|
||||
Some(ClearcoatExtension {
|
||||
clearcoat_factor: extension.get("clearcoatFactor").and_then(Value::as_f64),
|
||||
clearcoat_roughness_factor: extension
|
||||
.get("clearcoatRoughnessFactor")
|
||||
.and_then(Value::as_f64),
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
clearcoat_channel,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
clearcoat_texture,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
clearcoat_roughness_channel,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
clearcoat_roughness_texture,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
clearcoat_normal_channel,
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
clearcoat_normal_texture,
|
||||
})
|
||||
}
|
||||
}
|
100
crates/bevy_gltf/src/loader/extensions/khr_materials_specular.rs
Normal file
100
crates/bevy_gltf/src/loader/extensions/khr_materials_specular.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use bevy_asset::LoadContext;
|
||||
|
||||
use gltf::{Document, Material};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
use {
|
||||
crate::loader::gltf_ext::material::parse_material_extension_texture, bevy_asset::Handle,
|
||||
bevy_image::Image, bevy_pbr::UvChannel,
|
||||
};
|
||||
|
||||
/// Parsed data from the `KHR_materials_specular` extension.
|
||||
///
|
||||
/// We currently don't parse `specularFactor` and `specularTexture`, since
|
||||
/// they're incompatible with Filament.
|
||||
///
|
||||
/// Note that the map is a *specular map*, not a *reflectance map*. In Bevy and
|
||||
/// Filament terms, the reflectance values in the specular map range from [0.0,
|
||||
/// 0.5], rather than [0.0, 1.0]. This is an unfortunate
|
||||
/// `KHR_materials_specular` specification requirement that stems from the fact
|
||||
/// that glTF is specified in terms of a specular strength model, not the
|
||||
/// reflectance model that Filament and Bevy use. A workaround, which is noted
|
||||
/// in the [`StandardMaterial`](bevy_pbr::StandardMaterial) documentation, is to set the reflectance value
|
||||
/// to 2.0, which spreads the specular map range from [0.0, 1.0] as normal.
|
||||
///
|
||||
/// See the specification:
|
||||
/// <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_specular/README.md>
|
||||
#[derive(Default)]
|
||||
pub(crate) struct SpecularExtension {
|
||||
pub(crate) specular_factor: Option<f64>,
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
pub(crate) specular_channel: UvChannel,
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
pub(crate) specular_texture: Option<Handle<Image>>,
|
||||
pub(crate) specular_color_factor: Option<[f64; 3]>,
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
pub(crate) specular_color_channel: UvChannel,
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
pub(crate) specular_color_texture: Option<Handle<Image>>,
|
||||
}
|
||||
|
||||
impl SpecularExtension {
|
||||
pub(crate) fn parse(
|
||||
_load_context: &mut LoadContext,
|
||||
_document: &Document,
|
||||
material: &Material,
|
||||
) -> Option<Self> {
|
||||
let extension = material
|
||||
.extensions()?
|
||||
.get("KHR_materials_specular")?
|
||||
.as_object()?;
|
||||
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
let (_specular_channel, _specular_texture) = parse_material_extension_texture(
|
||||
material,
|
||||
_load_context,
|
||||
_document,
|
||||
extension,
|
||||
"specularTexture",
|
||||
"specular",
|
||||
);
|
||||
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
let (_specular_color_channel, _specular_color_texture) = parse_material_extension_texture(
|
||||
material,
|
||||
_load_context,
|
||||
_document,
|
||||
extension,
|
||||
"specularColorTexture",
|
||||
"specular color",
|
||||
);
|
||||
|
||||
Some(SpecularExtension {
|
||||
specular_factor: extension.get("specularFactor").and_then(Value::as_f64),
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
specular_channel: _specular_channel,
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
specular_texture: _specular_texture,
|
||||
specular_color_factor: extension
|
||||
.get("specularColorFactor")
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|json_array| {
|
||||
if json_array.len() < 3 {
|
||||
None
|
||||
} else {
|
||||
Some([
|
||||
json_array[0].as_f64()?,
|
||||
json_array[1].as_f64()?,
|
||||
json_array[2].as_f64()?,
|
||||
])
|
||||
}
|
||||
}),
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
specular_color_channel: _specular_color_channel,
|
||||
#[cfg(feature = "pbr_specular_textures")]
|
||||
specular_color_texture: _specular_color_texture,
|
||||
})
|
||||
}
|
||||
}
|
10
crates/bevy_gltf/src/loader/extensions/mod.rs
Normal file
10
crates/bevy_gltf/src/loader/extensions/mod.rs
Normal file
@ -0,0 +1,10 @@
|
||||
//! glTF extensions defined by the Khronos Group and other vendors
|
||||
|
||||
mod khr_materials_anisotropy;
|
||||
mod khr_materials_clearcoat;
|
||||
mod khr_materials_specular;
|
||||
|
||||
pub(crate) use self::{
|
||||
khr_materials_anisotropy::AnisotropyExtension, khr_materials_clearcoat::ClearcoatExtension,
|
||||
khr_materials_specular::SpecularExtension,
|
||||
};
|
165
crates/bevy_gltf/src/loader/gltf_ext/material.rs
Normal file
165
crates/bevy_gltf/src/loader/gltf_ext/material.rs
Normal file
@ -0,0 +1,165 @@
|
||||
use bevy_math::Affine2;
|
||||
use bevy_pbr::UvChannel;
|
||||
use bevy_render::alpha::AlphaMode;
|
||||
|
||||
use gltf::{json::texture::Info, Material};
|
||||
|
||||
use serde_json::value;
|
||||
|
||||
use crate::GltfAssetLabel;
|
||||
|
||||
use super::texture::texture_transform_to_affine2;
|
||||
|
||||
#[cfg(any(
|
||||
feature = "pbr_specular_textures",
|
||||
feature = "pbr_multi_layer_material_textures"
|
||||
))]
|
||||
use {
|
||||
super::texture::texture_handle_from_info,
|
||||
bevy_asset::{Handle, LoadContext},
|
||||
bevy_image::Image,
|
||||
gltf::Document,
|
||||
serde_json::{Map, Value},
|
||||
};
|
||||
|
||||
/// Parses a texture that's part of a material extension block and returns its
|
||||
/// UV channel and image reference.
|
||||
#[cfg(any(
|
||||
feature = "pbr_specular_textures",
|
||||
feature = "pbr_multi_layer_material_textures"
|
||||
))]
|
||||
pub(crate) fn parse_material_extension_texture(
|
||||
material: &Material,
|
||||
load_context: &mut LoadContext,
|
||||
document: &Document,
|
||||
extension: &Map<String, Value>,
|
||||
texture_name: &str,
|
||||
texture_kind: &str,
|
||||
) -> (UvChannel, Option<Handle<Image>>) {
|
||||
match extension
|
||||
.get(texture_name)
|
||||
.and_then(|value| value::from_value::<Info>(value.clone()).ok())
|
||||
{
|
||||
Some(json_info) => (
|
||||
uv_channel(material, texture_kind, json_info.tex_coord),
|
||||
Some(texture_handle_from_info(&json_info, document, load_context)),
|
||||
),
|
||||
None => (UvChannel::default(), None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn uv_channel(material: &Material, texture_kind: &str, tex_coord: u32) -> UvChannel {
|
||||
match tex_coord {
|
||||
0 => UvChannel::Uv0,
|
||||
1 => UvChannel::Uv1,
|
||||
_ => {
|
||||
let material_name = material
|
||||
.name()
|
||||
.map(|n| format!("the material \"{n}\""))
|
||||
.unwrap_or_else(|| "an unnamed material".to_string());
|
||||
let material_index = material
|
||||
.index()
|
||||
.map(|i| format!("index {i}"))
|
||||
.unwrap_or_else(|| "default".to_string());
|
||||
tracing::warn!(
|
||||
"Only 2 UV Channels are supported, but {material_name} ({material_index}) \
|
||||
has the TEXCOORD attribute {} on texture kind {texture_kind}, which will fallback to 0.",
|
||||
tex_coord,
|
||||
);
|
||||
UvChannel::Uv0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn alpha_mode(material: &Material) -> AlphaMode {
|
||||
match material.alpha_mode() {
|
||||
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
|
||||
gltf::material::AlphaMode::Mask => AlphaMode::Mask(material.alpha_cutoff().unwrap_or(0.5)),
|
||||
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the index (within the `textures` array) of the texture with the
|
||||
/// given field name in the data for the material extension with the given name,
|
||||
/// if there is one.
|
||||
pub(crate) fn extension_texture_index(
|
||||
material: &Material,
|
||||
extension_name: &str,
|
||||
texture_field_name: &str,
|
||||
) -> Option<usize> {
|
||||
Some(
|
||||
value::from_value::<Info>(
|
||||
material
|
||||
.extensions()?
|
||||
.get(extension_name)?
|
||||
.as_object()?
|
||||
.get(texture_field_name)?
|
||||
.clone(),
|
||||
)
|
||||
.ok()?
|
||||
.index
|
||||
.value(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if the material needs mesh tangents in order to be successfully
|
||||
/// rendered.
|
||||
///
|
||||
/// We generate them if this function returns true.
|
||||
pub(crate) fn needs_tangents(material: &Material) -> bool {
|
||||
[
|
||||
material.normal_texture().is_some(),
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
extension_texture_index(
|
||||
material,
|
||||
"KHR_materials_clearcoat",
|
||||
"clearcoatNormalTexture",
|
||||
)
|
||||
.is_some(),
|
||||
]
|
||||
.into_iter()
|
||||
.reduce(|a, b| a || b)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(crate) fn warn_on_differing_texture_transforms(
|
||||
material: &Material,
|
||||
info: &gltf::texture::Info,
|
||||
texture_transform: Affine2,
|
||||
texture_kind: &str,
|
||||
) {
|
||||
let has_differing_texture_transform = info
|
||||
.texture_transform()
|
||||
.map(texture_transform_to_affine2)
|
||||
.is_some_and(|t| t != texture_transform);
|
||||
if has_differing_texture_transform {
|
||||
let material_name = material
|
||||
.name()
|
||||
.map(|n| format!("the material \"{n}\""))
|
||||
.unwrap_or_else(|| "an unnamed material".to_string());
|
||||
let texture_name = info
|
||||
.texture()
|
||||
.name()
|
||||
.map(|n| format!("its {texture_kind} texture \"{n}\""))
|
||||
.unwrap_or_else(|| format!("its unnamed {texture_kind} texture"));
|
||||
let material_index = material
|
||||
.index()
|
||||
.map(|i| format!("index {i}"))
|
||||
.unwrap_or_else(|| "default".to_string());
|
||||
tracing::warn!(
|
||||
"Only texture transforms on base color textures are supported, but {material_name} ({material_index}) \
|
||||
has a texture transform on {texture_name} (index {}), which will be ignored.", info.texture().index()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn material_label(material: &Material, is_scale_inverted: bool) -> GltfAssetLabel {
|
||||
if let Some(index) = material.index() {
|
||||
GltfAssetLabel::Material {
|
||||
index,
|
||||
is_scale_inverted,
|
||||
}
|
||||
} else {
|
||||
GltfAssetLabel::DefaultMaterial
|
||||
}
|
||||
}
|
30
crates/bevy_gltf/src/loader/gltf_ext/mesh.rs
Normal file
30
crates/bevy_gltf/src/loader/gltf_ext/mesh.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use bevy_render::mesh::PrimitiveTopology;
|
||||
|
||||
use gltf::mesh::{Mesh, Mode, Primitive};
|
||||
|
||||
use crate::GltfError;
|
||||
|
||||
pub(crate) fn primitive_name(mesh: &Mesh<'_>, primitive: &Primitive) -> String {
|
||||
let mesh_name = mesh.name().unwrap_or("Mesh");
|
||||
if mesh.primitives().len() > 1 {
|
||||
format!("{}.{}", mesh_name, primitive.index())
|
||||
} else {
|
||||
mesh_name.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps the `primitive_topology` from glTF to `wgpu`.
|
||||
#[expect(
|
||||
clippy::result_large_err,
|
||||
reason = "`GltfError` is only barely past the threshold for large errors."
|
||||
)]
|
||||
pub(crate) fn primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
|
||||
match mode {
|
||||
Mode::Points => Ok(PrimitiveTopology::PointList),
|
||||
Mode::Lines => Ok(PrimitiveTopology::LineList),
|
||||
Mode::LineStrip => Ok(PrimitiveTopology::LineStrip),
|
||||
Mode::Triangles => Ok(PrimitiveTopology::TriangleList),
|
||||
Mode::TriangleStrip => Ok(PrimitiveTopology::TriangleStrip),
|
||||
mode => Err(GltfError::UnsupportedPrimitive { mode }),
|
||||
}
|
||||
}
|
79
crates/bevy_gltf/src/loader/gltf_ext/mod.rs
Normal file
79
crates/bevy_gltf/src/loader/gltf_ext/mod.rs
Normal file
@ -0,0 +1,79 @@
|
||||
//! Methods to access information from [`gltf`] types
|
||||
|
||||
pub mod material;
|
||||
pub mod mesh;
|
||||
pub mod scene;
|
||||
pub mod texture;
|
||||
|
||||
use bevy_platform_support::collections::HashSet;
|
||||
|
||||
use fixedbitset::FixedBitSet;
|
||||
use gltf::{Document, Gltf};
|
||||
|
||||
use super::GltfError;
|
||||
|
||||
use self::{material::extension_texture_index, scene::check_is_part_of_cycle};
|
||||
|
||||
#[expect(
|
||||
clippy::result_large_err,
|
||||
reason = "need to be signature compatible with `load_gltf`"
|
||||
)]
|
||||
/// Checks all glTF nodes for cycles, starting at the scene root.
|
||||
pub(crate) fn check_for_cycles(gltf: &Gltf) -> Result<(), GltfError> {
|
||||
// Initialize with the scene roots.
|
||||
let mut roots = FixedBitSet::with_capacity(gltf.nodes().len());
|
||||
for root in gltf.scenes().flat_map(|scene| scene.nodes()) {
|
||||
roots.insert(root.index());
|
||||
}
|
||||
|
||||
// Check each one.
|
||||
let mut visited = FixedBitSet::with_capacity(gltf.nodes().len());
|
||||
for root in roots.ones() {
|
||||
let Some(node) = gltf.nodes().nth(root) else {
|
||||
unreachable!("Index of a root node should always exist.");
|
||||
};
|
||||
check_is_part_of_cycle(&node, &mut visited)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn get_linear_textures(document: &Document) -> HashSet<usize> {
|
||||
let mut linear_textures = HashSet::default();
|
||||
|
||||
for material in document.materials() {
|
||||
if let Some(texture) = material.normal_texture() {
|
||||
linear_textures.insert(texture.texture().index());
|
||||
}
|
||||
if let Some(texture) = material.occlusion_texture() {
|
||||
linear_textures.insert(texture.texture().index());
|
||||
}
|
||||
if let Some(texture) = material
|
||||
.pbr_metallic_roughness()
|
||||
.metallic_roughness_texture()
|
||||
{
|
||||
linear_textures.insert(texture.texture().index());
|
||||
}
|
||||
if let Some(texture_index) =
|
||||
extension_texture_index(&material, "KHR_materials_anisotropy", "anisotropyTexture")
|
||||
{
|
||||
linear_textures.insert(texture_index);
|
||||
}
|
||||
|
||||
// None of the clearcoat maps should be loaded as sRGB.
|
||||
#[cfg(feature = "pbr_multi_layer_material_textures")]
|
||||
for texture_field_name in [
|
||||
"clearcoatTexture",
|
||||
"clearcoatRoughnessTexture",
|
||||
"clearcoatNormalTexture",
|
||||
] {
|
||||
if let Some(texture_index) =
|
||||
extension_texture_index(&material, "KHR_materials_clearcoat", texture_field_name)
|
||||
{
|
||||
linear_textures.insert(texture_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
linear_textures
|
||||
}
|
91
crates/bevy_gltf/src/loader/gltf_ext/scene.rs
Normal file
91
crates/bevy_gltf/src/loader/gltf_ext/scene.rs
Normal file
@ -0,0 +1,91 @@
|
||||
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_support::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::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) -> 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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
126
crates/bevy_gltf/src/loader/gltf_ext/texture.rs
Normal file
126
crates/bevy_gltf/src/loader/gltf_ext/texture.rs
Normal file
@ -0,0 +1,126 @@
|
||||
use bevy_asset::{Handle, LoadContext};
|
||||
use bevy_image::{Image, ImageAddressMode, ImageFilterMode, ImageSamplerDescriptor};
|
||||
use bevy_math::Affine2;
|
||||
|
||||
use gltf::{
|
||||
image::Source,
|
||||
texture::{MagFilter, MinFilter, Texture, TextureTransform, WrappingMode},
|
||||
};
|
||||
|
||||
#[cfg(any(
|
||||
feature = "pbr_anisotropy_texture",
|
||||
feature = "pbr_multi_layer_material_textures",
|
||||
feature = "pbr_specular_textures"
|
||||
))]
|
||||
use gltf::{json::texture::Info, Document};
|
||||
|
||||
use crate::{loader::DataUri, GltfAssetLabel};
|
||||
|
||||
pub(crate) fn texture_handle(
|
||||
texture: &Texture<'_>,
|
||||
load_context: &mut LoadContext,
|
||||
) -> Handle<Image> {
|
||||
match texture.source().source() {
|
||||
Source::View { .. } => load_context.get_label_handle(texture_label(texture).to_string()),
|
||||
Source::Uri { uri, .. } => {
|
||||
let uri = percent_encoding::percent_decode_str(uri)
|
||||
.decode_utf8()
|
||||
.unwrap();
|
||||
let uri = uri.as_ref();
|
||||
if let Ok(_data_uri) = DataUri::parse(uri) {
|
||||
load_context.get_label_handle(texture_label(texture).to_string())
|
||||
} else {
|
||||
let parent = load_context.path().parent().unwrap();
|
||||
let image_path = parent.join(uri);
|
||||
load_context.load(image_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts the texture sampler data from the glTF [`Texture`].
|
||||
pub(crate) fn texture_sampler(texture: &Texture<'_>) -> ImageSamplerDescriptor {
|
||||
let gltf_sampler = texture.sampler();
|
||||
|
||||
ImageSamplerDescriptor {
|
||||
address_mode_u: address_mode(&gltf_sampler.wrap_s()),
|
||||
address_mode_v: address_mode(&gltf_sampler.wrap_t()),
|
||||
|
||||
mag_filter: gltf_sampler
|
||||
.mag_filter()
|
||||
.map(|mf| match mf {
|
||||
MagFilter::Nearest => ImageFilterMode::Nearest,
|
||||
MagFilter::Linear => ImageFilterMode::Linear,
|
||||
})
|
||||
.unwrap_or(ImageSamplerDescriptor::default().mag_filter),
|
||||
|
||||
min_filter: gltf_sampler
|
||||
.min_filter()
|
||||
.map(|mf| match mf {
|
||||
MinFilter::Nearest
|
||||
| MinFilter::NearestMipmapNearest
|
||||
| MinFilter::NearestMipmapLinear => ImageFilterMode::Nearest,
|
||||
MinFilter::Linear
|
||||
| MinFilter::LinearMipmapNearest
|
||||
| MinFilter::LinearMipmapLinear => ImageFilterMode::Linear,
|
||||
})
|
||||
.unwrap_or(ImageSamplerDescriptor::default().min_filter),
|
||||
|
||||
mipmap_filter: gltf_sampler
|
||||
.min_filter()
|
||||
.map(|mf| match mf {
|
||||
MinFilter::Nearest
|
||||
| MinFilter::Linear
|
||||
| MinFilter::NearestMipmapNearest
|
||||
| MinFilter::LinearMipmapNearest => ImageFilterMode::Nearest,
|
||||
MinFilter::NearestMipmapLinear | MinFilter::LinearMipmapLinear => {
|
||||
ImageFilterMode::Linear
|
||||
}
|
||||
})
|
||||
.unwrap_or(ImageSamplerDescriptor::default().mipmap_filter),
|
||||
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn texture_label(texture: &Texture<'_>) -> GltfAssetLabel {
|
||||
GltfAssetLabel::Texture(texture.index())
|
||||
}
|
||||
|
||||
pub(crate) fn address_mode(wrapping_mode: &WrappingMode) -> ImageAddressMode {
|
||||
match wrapping_mode {
|
||||
WrappingMode::ClampToEdge => ImageAddressMode::ClampToEdge,
|
||||
WrappingMode::Repeat => ImageAddressMode::Repeat,
|
||||
WrappingMode::MirroredRepeat => ImageAddressMode::MirrorRepeat,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn texture_transform_to_affine2(texture_transform: TextureTransform) -> Affine2 {
|
||||
Affine2::from_scale_angle_translation(
|
||||
texture_transform.scale().into(),
|
||||
-texture_transform.rotation(),
|
||||
texture_transform.offset().into(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "pbr_anisotropy_texture",
|
||||
feature = "pbr_multi_layer_material_textures",
|
||||
feature = "pbr_specular_textures"
|
||||
))]
|
||||
/// Given a [`Info`], returns the handle of the texture that this
|
||||
/// refers to.
|
||||
///
|
||||
/// This is a low-level function only used when the [`gltf`] crate has no support
|
||||
/// for an extension, forcing us to parse its texture references manually.
|
||||
pub(crate) fn texture_handle_from_info(
|
||||
info: &Info,
|
||||
document: &Document,
|
||||
load_context: &mut LoadContext,
|
||||
) -> Handle<Image> {
|
||||
let texture = document
|
||||
.textures()
|
||||
.nth(info.index.value())
|
||||
.expect("Texture info references a nonexistent texture");
|
||||
texture_handle(&texture, load_context)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user