Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -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,
})
}
}

View File

@@ -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,
})
}
}

View 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,
})
}
}

View 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,
};

View 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
}
}

View File

@@ -0,0 +1,33 @@
use bevy_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`.
#[cfg_attr(
not(target_arch = "wasm32"),
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 }),
}
}

View File

@@ -0,0 +1,82 @@
//! Methods to access information from [`gltf`] types
pub mod material;
pub mod mesh;
pub mod scene;
pub mod texture;
use bevy_platform::collections::HashSet;
use fixedbitset::FixedBitSet;
use gltf::{Document, Gltf};
use super::GltfError;
use self::{material::extension_texture_index, scene::check_is_part_of_cycle};
#[cfg_attr(
not(target_arch = "wasm32"),
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
}

View File

@@ -0,0 +1,94 @@
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::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),
},
}
}
#[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));
}

View 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)
}

2176
vendor/bevy_gltf/src/loader/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff