Make adding a subasset label return a result for if there is a duplicate label. (#18013)

# Objective

- Makes #18010 more easily debuggable. This doesn't solve that issue,
but protects us from it in the future.

## Solution

- Make `LoadContext::add_labeled_asset` and friends return an error if
it finds a duplicate asset.

## Testing

- Added a test - it fails before the fix.

---

## Migration Guide

- `AssetLoader`s must now handle the case of a duplicate subasset label
when using `LoadContext::add_labeled_asset` and its variants. If you
know your subasset labels are unique by construction (e.g., they include
an index number), you can simply unwrap this result.
This commit is contained in:
andriyDev 2025-02-24 13:51:40 -08:00 committed by GitHub
parent 6bae04ab36
commit ed1143b26b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 308 additions and 226 deletions

View File

@ -639,7 +639,7 @@ mod tests {
}, },
loader::{AssetLoader, LoadContext}, loader::{AssetLoader, LoadContext},
Asset, AssetApp, AssetEvent, AssetId, AssetLoadError, AssetLoadFailedEvent, AssetPath, Asset, AssetApp, AssetEvent, AssetId, AssetLoadError, AssetLoadFailedEvent, AssetPath,
AssetPlugin, AssetServer, Assets, AssetPlugin, AssetServer, Assets, DuplicateLabelAssetError, LoadState,
}; };
use alloc::{ use alloc::{
boxed::Box, boxed::Box,
@ -695,6 +695,8 @@ mod tests {
CannotLoadDependency { dependency: AssetPath<'static> }, CannotLoadDependency { dependency: AssetPath<'static> },
#[error("A RON error occurred during loading")] #[error("A RON error occurred during loading")]
RonSpannedError(#[from] ron::error::SpannedError), RonSpannedError(#[from] ron::error::SpannedError),
#[error(transparent)]
DuplicateLabelAssetError(#[from] DuplicateLabelAssetError),
#[error("An IO error occurred during loading")] #[error("An IO error occurred during loading")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
} }
@ -740,7 +742,7 @@ mod tests {
.sub_texts .sub_texts
.drain(..) .drain(..)
.map(|text| load_context.add_labeled_asset(text.clone(), SubText { text })) .map(|text| load_context.add_labeled_asset(text.clone(), SubText { text }))
.collect(), .collect::<Result<Vec<_>, _>>()?,
}) })
} }
@ -1778,6 +1780,49 @@ mod tests {
app.world_mut().run_schedule(Update); app.world_mut().run_schedule(Update);
} }
#[test]
fn fails_to_load_for_duplicate_subasset_labels() {
let mut app = App::new();
let dir = Dir::default();
dir.insert_asset_text(
Path::new("a.ron"),
r#"(
text: "b",
dependencies: [],
embedded_dependencies: [],
sub_texts: ["A", "A"],
)"#,
);
app.register_asset_source(
AssetSourceId::Default,
AssetSource::build()
.with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() })),
)
.add_plugins((
TaskPoolPlugin::default(),
LogPlugin::default(),
AssetPlugin::default(),
));
app.init_asset::<CoolText>()
.init_asset::<SubText>()
.register_asset_loader(CoolTextLoader);
let asset_server = app.world().resource::<AssetServer>().clone();
let handle = asset_server.load::<CoolText>("a.ron");
run_app_until(&mut app, |_world| match asset_server.load_state(&handle) {
LoadState::Loading => None,
LoadState::Failed(err) => {
assert!(matches!(*err, AssetLoadError::AssetLoaderError(_)));
Some(())
}
state => panic!("Unexpected asset state: {state:?}"),
});
}
// validate the Asset derive macro for various asset types // validate the Asset derive macro for various asset types
#[derive(Asset, TypePath)] #[derive(Asset, TypePath)]
pub struct TestAsset; pub struct TestAsset;

View File

@ -13,7 +13,6 @@ use alloc::{
}; };
use atomicow::CowArc; use atomicow::CowArc;
use bevy_ecs::world::World; use bevy_ecs::world::World;
use bevy_log::warn;
use bevy_platform_support::collections::{HashMap, HashSet}; use bevy_platform_support::collections::{HashMap, HashSet};
use bevy_tasks::{BoxedFuture, ConditionalSendFuture}; use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use core::any::{Any, TypeId}; use core::any::{Any, TypeId};
@ -458,7 +457,7 @@ impl<'a> LoadContext<'a> {
&mut self, &mut self,
label: String, label: String,
load: impl FnOnce(&mut LoadContext) -> A, load: impl FnOnce(&mut LoadContext) -> A,
) -> Handle<A> { ) -> Result<Handle<A>, DuplicateLabelAssetError> {
let mut context = self.begin_labeled_asset(); let mut context = self.begin_labeled_asset();
let asset = load(&mut context); let asset = load(&mut context);
let complete_asset = context.finish(asset); let complete_asset = context.finish(asset);
@ -475,7 +474,11 @@ impl<'a> LoadContext<'a> {
/// new [`LoadContext`] to track the dependencies for the labeled asset. /// new [`LoadContext`] to track the dependencies for the labeled asset.
/// ///
/// See [`AssetPath`] for more on labeled assets. /// See [`AssetPath`] for more on labeled assets.
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> { pub fn add_labeled_asset<A: Asset>(
&mut self,
label: String,
asset: A,
) -> Result<Handle<A>, DuplicateLabelAssetError> {
self.labeled_asset_scope(label, |_| asset) self.labeled_asset_scope(label, |_| asset)
} }
@ -488,7 +491,7 @@ impl<'a> LoadContext<'a> {
&mut self, &mut self,
label: impl Into<CowArc<'static, str>>, label: impl Into<CowArc<'static, str>>,
loaded_asset: CompleteLoadedAsset<A>, loaded_asset: CompleteLoadedAsset<A>,
) -> Handle<A> { ) -> Result<Handle<A>, DuplicateLabelAssetError> {
let label = label.into(); let label = label.into();
let CompleteLoadedAsset { let CompleteLoadedAsset {
asset, asset,
@ -499,19 +502,25 @@ impl<'a> LoadContext<'a> {
let handle = self let handle = self
.asset_server .asset_server
.get_or_create_path_handle(labeled_path, None); .get_or_create_path_handle(labeled_path, None);
self.labeled_assets.insert( let has_duplicate = self
label, .labeled_assets
LabeledAsset { .insert(
asset: loaded_asset, label.clone(),
handle: handle.clone().untyped(), LabeledAsset {
}, asset: loaded_asset,
); handle: handle.clone().untyped(),
},
)
.is_some();
if has_duplicate {
return Err(DuplicateLabelAssetError(label.to_string()));
}
for (label, asset) in labeled_assets { for (label, asset) in labeled_assets {
if self.labeled_assets.insert(label.clone(), asset).is_some() { if self.labeled_assets.insert(label.clone(), asset).is_some() {
warn!("A labeled asset with the label \"{label}\" already exists. Replacing with the new asset."); return Err(DuplicateLabelAssetError(label.to_string()));
} }
} }
handle Ok(handle)
} }
/// Returns `true` if an asset with the label `label` exists in this context. /// Returns `true` if an asset with the label `label` exists in this context.
@ -661,3 +670,8 @@ pub enum ReadAssetBytesError {
#[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")] #[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
MissingAssetHash, MissingAssetHash,
} }
/// An error when labeled assets have the same label, containing the duplicate label.
#[derive(Error, Debug)]
#[error("Encountered a duplicate label while loading an asset: \"{0}\"")]
pub struct DuplicateLabelAssetError(pub String);

View File

@ -517,10 +517,12 @@ async fn load_gltf<'a, 'b, 'c>(
); );
} }
} }
let handle = load_context.add_labeled_asset( let handle = load_context
GltfAssetLabel::Animation(animation.index()).to_string(), .add_labeled_asset(
animation_clip, GltfAssetLabel::Animation(animation.index()).to_string(),
); animation_clip,
)
.expect("animation indices are unique, so the label is unique");
if let Some(name) = animation.name() { if let Some(name) = animation.name() {
named_animations.insert(name.into(), handle.clone()); named_animations.insert(name.into(), handle.clone());
} }
@ -540,9 +542,9 @@ async fn load_gltf<'a, 'b, 'c>(
texture: ImageOrPath, texture: ImageOrPath,
) { ) {
let handle = match texture { let handle = match texture {
ImageOrPath::Image { label, image } => { ImageOrPath::Image { label, image } => load_context
load_context.add_labeled_asset(label.to_string(), image) .add_labeled_asset(label.to_string(), image)
} .expect("texture indices are unique, so the label is unique"),
ImageOrPath::Path { ImageOrPath::Path {
path, path,
is_srgb, is_srgb,
@ -696,7 +698,8 @@ async fn load_gltf<'a, 'b, 'c>(
RenderAssetUsages::default(), RenderAssetUsages::default(),
)?; )?;
let handle = load_context let handle = load_context
.add_labeled_asset(morph_targets_label.to_string(), morph_target_image.0); .add_labeled_asset(morph_targets_label.to_string(), morph_target_image.0)
.expect("morph target indices are unique, so the label is unique");
mesh.set_morph_targets(handle); mesh.set_morph_targets(handle);
let extras = gltf_mesh.extras().as_ref(); let extras = gltf_mesh.extras().as_ref();
@ -749,7 +752,9 @@ async fn load_gltf<'a, 'b, 'c>(
}); });
} }
let mesh_handle = load_context.add_labeled_asset(primitive_label.to_string(), mesh); let mesh_handle = load_context
.add_labeled_asset(primitive_label.to_string(), mesh)
.expect("primitive indices are unique, so the label is unique");
primitives.push(super::GltfPrimitive::new( primitives.push(super::GltfPrimitive::new(
&gltf_mesh, &gltf_mesh,
&primitive, &primitive,
@ -766,7 +771,9 @@ async fn load_gltf<'a, 'b, 'c>(
let mesh = let mesh =
super::GltfMesh::new(&gltf_mesh, primitives, get_gltf_extras(gltf_mesh.extras())); super::GltfMesh::new(&gltf_mesh, primitives, get_gltf_extras(gltf_mesh.extras()));
let handle = load_context.add_labeled_asset(mesh.asset_label().to_string(), mesh); let handle = load_context
.add_labeled_asset(mesh.asset_label().to_string(), mesh)
.expect("mesh indices are unique, so the label is unique");
if let Some(name) = gltf_mesh.name() { if let Some(name) = gltf_mesh.name() {
named_meshes.insert(name.into(), handle.clone()); named_meshes.insert(name.into(), handle.clone());
} }
@ -783,10 +790,12 @@ async fn load_gltf<'a, 'b, 'c>(
.map(|mat| Mat4::from_cols_array_2d(&mat)) .map(|mat| Mat4::from_cols_array_2d(&mat))
.collect(); .collect();
load_context.add_labeled_asset( load_context
inverse_bind_matrices_label(&gltf_skin), .add_labeled_asset(
SkinnedMeshInverseBindposes::from(local_to_bone_bind_matrices), inverse_bind_matrices_label(&gltf_skin),
) SkinnedMeshInverseBindposes::from(local_to_bone_bind_matrices),
)
.expect("inverse bind matrix indices are unique, so the label is unique")
}) })
.collect(); .collect();
@ -831,7 +840,9 @@ async fn load_gltf<'a, 'b, 'c>(
get_gltf_extras(skin.extras()), get_gltf_extras(skin.extras()),
); );
let handle = load_context.add_labeled_asset(skin_label(&skin), gltf_skin); let handle = load_context
.add_labeled_asset(skin_label(&skin), gltf_skin)
.expect("skin indices are unique, so the label is unique");
skins.push(handle.clone()); skins.push(handle.clone());
if let Some(name) = skin.name() { if let Some(name) = skin.name() {
@ -863,7 +874,9 @@ async fn load_gltf<'a, 'b, 'c>(
#[cfg(feature = "bevy_animation")] #[cfg(feature = "bevy_animation")]
let gltf_node = gltf_node.with_animation_root(animation_roots.contains(&node.index())); let gltf_node = gltf_node.with_animation_root(animation_roots.contains(&node.index()));
let handle = load_context.add_labeled_asset(gltf_node.asset_label().to_string(), gltf_node); let handle = load_context
.add_labeled_asset(gltf_node.asset_label().to_string(), gltf_node)
.expect("node indices are unique, so the label is unique");
nodes.insert(node.index(), handle.clone()); nodes.insert(node.index(), handle.clone());
if let Some(name) = node.name() { if let Some(name) = node.name() {
named_nodes.insert(name.into(), handle); named_nodes.insert(name.into(), handle);
@ -952,7 +965,9 @@ async fn load_gltf<'a, 'b, 'c>(
}); });
} }
let loaded_scene = scene_load_context.finish(Scene::new(world)); let loaded_scene = scene_load_context.finish(Scene::new(world));
let scene_handle = load_context.add_loaded_labeled_asset(scene_label(&scene), loaded_scene); let scene_handle = load_context
.add_loaded_labeled_asset(scene_label(&scene), loaded_scene)
.expect("scene indices are unique, so the label is unique");
if let Some(name) = scene.name() { if let Some(name) = scene.name() {
named_scenes.insert(name.into(), scene_handle.clone()); named_scenes.insert(name.into(), scene_handle.clone());
@ -1119,74 +1134,78 @@ fn load_material(
is_scale_inverted: bool, is_scale_inverted: bool,
) -> Handle<StandardMaterial> { ) -> Handle<StandardMaterial> {
let material_label = material_label(material, is_scale_inverted); let material_label = material_label(material, is_scale_inverted);
load_context.labeled_asset_scope(material_label, |load_context| { load_context
let pbr = material.pbr_metallic_roughness(); .labeled_asset_scope(material_label, |load_context| {
let pbr = material.pbr_metallic_roughness();
// TODO: handle missing label handle errors here? // TODO: handle missing label handle errors here?
let color = pbr.base_color_factor(); let color = pbr.base_color_factor();
let base_color_channel = pbr let base_color_channel = pbr
.base_color_texture() .base_color_texture()
.map(|info| get_uv_channel(material, "base color", info.tex_coord())) .map(|info| get_uv_channel(material, "base color", info.tex_coord()))
.unwrap_or_default(); .unwrap_or_default();
let base_color_texture = pbr let base_color_texture = pbr
.base_color_texture() .base_color_texture()
.map(|info| texture_handle(load_context, &info.texture())); .map(|info| texture_handle(load_context, &info.texture()));
let uv_transform = pbr let uv_transform = pbr
.base_color_texture() .base_color_texture()
.and_then(|info| { .and_then(|info| {
info.texture_transform() info.texture_transform()
.map(convert_texture_transform_to_affine2) .map(convert_texture_transform_to_affine2)
}) })
.unwrap_or_default(); .unwrap_or_default();
let normal_map_channel = material let normal_map_channel = material
.normal_texture() .normal_texture()
.map(|info| get_uv_channel(material, "normal map", info.tex_coord())) .map(|info| get_uv_channel(material, "normal map", info.tex_coord()))
.unwrap_or_default(); .unwrap_or_default();
let normal_map_texture: Option<Handle<Image>> = let normal_map_texture: Option<Handle<Image>> =
material.normal_texture().map(|normal_texture| { material.normal_texture().map(|normal_texture| {
// TODO: handle normal_texture.scale // TODO: handle normal_texture.scale
texture_handle(load_context, &normal_texture.texture()) texture_handle(load_context, &normal_texture.texture())
});
let metallic_roughness_channel = pbr
.metallic_roughness_texture()
.map(|info| get_uv_channel(material, "metallic/roughness", info.tex_coord()))
.unwrap_or_default();
let metallic_roughness_texture = pbr.metallic_roughness_texture().map(|info| {
warn_on_differing_texture_transforms(
material,
&info,
uv_transform,
"metallic/roughness",
);
texture_handle(load_context, &info.texture())
}); });
let metallic_roughness_channel = pbr let occlusion_channel = material
.metallic_roughness_texture() .occlusion_texture()
.map(|info| get_uv_channel(material, "metallic/roughness", info.tex_coord())) .map(|info| get_uv_channel(material, "occlusion", info.tex_coord()))
.unwrap_or_default(); .unwrap_or_default();
let metallic_roughness_texture = pbr.metallic_roughness_texture().map(|info| { let occlusion_texture = material.occlusion_texture().map(|occlusion_texture| {
warn_on_differing_texture_transforms( // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength)
material, texture_handle(load_context, &occlusion_texture.texture())
&info, });
uv_transform,
"metallic/roughness",
);
texture_handle(load_context, &info.texture())
});
let occlusion_channel = material let emissive = material.emissive_factor();
.occlusion_texture() let emissive_channel = material
.map(|info| get_uv_channel(material, "occlusion", info.tex_coord())) .emissive_texture()
.unwrap_or_default(); .map(|info| get_uv_channel(material, "emissive", info.tex_coord()))
let occlusion_texture = material.occlusion_texture().map(|occlusion_texture| { .unwrap_or_default();
// TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength) let emissive_texture = material.emissive_texture().map(|info| {
texture_handle(load_context, &occlusion_texture.texture()) // TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength)
}); warn_on_differing_texture_transforms(material, &info, uv_transform, "emissive");
texture_handle(load_context, &info.texture())
});
let emissive = material.emissive_factor(); #[cfg(feature = "pbr_transmission_textures")]
let emissive_channel = material let (
.emissive_texture() specular_transmission,
.map(|info| get_uv_channel(material, "emissive", info.tex_coord())) specular_transmission_channel,
.unwrap_or_default(); specular_transmission_texture,
let emissive_texture = material.emissive_texture().map(|info| { ) = material
// TODO: handle occlusion_texture.strength() (a scalar multiplier for occlusion strength)
warn_on_differing_texture_transforms(material, &info, uv_transform, "emissive");
texture_handle(load_context, &info.texture())
});
#[cfg(feature = "pbr_transmission_textures")]
let (specular_transmission, specular_transmission_channel, specular_transmission_texture) =
material
.transmission() .transmission()
.map_or((0.0, UvChannel::Uv0, None), |transmission| { .map_or((0.0, UvChannel::Uv0, None), |transmission| {
let specular_transmission_channel = transmission let specular_transmission_channel = transmission
@ -1208,152 +1227,156 @@ fn load_material(
) )
}); });
#[cfg(not(feature = "pbr_transmission_textures"))] #[cfg(not(feature = "pbr_transmission_textures"))]
let specular_transmission = material let specular_transmission = material
.transmission() .transmission()
.map_or(0.0, |transmission| transmission.transmission_factor()); .map_or(0.0, |transmission| transmission.transmission_factor());
#[cfg(feature = "pbr_transmission_textures")] #[cfg(feature = "pbr_transmission_textures")]
let ( let (
thickness, thickness,
thickness_channel, thickness_channel,
thickness_texture, thickness_texture,
attenuation_distance, attenuation_distance,
attenuation_color, attenuation_color,
) = material.volume().map_or( ) = material.volume().map_or(
(0.0, UvChannel::Uv0, None, f32::INFINITY, [1.0, 1.0, 1.0]), (0.0, UvChannel::Uv0, None, f32::INFINITY, [1.0, 1.0, 1.0]),
|volume| { |volume| {
let thickness_channel = volume let thickness_channel = volume
.thickness_texture() .thickness_texture()
.map(|info| get_uv_channel(material, "thickness", info.tex_coord())) .map(|info| get_uv_channel(material, "thickness", info.tex_coord()))
.unwrap_or_default(); .unwrap_or_default();
let thickness_texture: Option<Handle<Image>> = let thickness_texture: Option<Handle<Image>> =
volume.thickness_texture().map(|thickness_texture| { volume.thickness_texture().map(|thickness_texture| {
texture_handle(load_context, &thickness_texture.texture()) texture_handle(load_context, &thickness_texture.texture())
}); });
(
volume.thickness_factor(),
thickness_channel,
thickness_texture,
volume.attenuation_distance(),
volume.attenuation_color(),
)
},
);
#[cfg(not(feature = "pbr_transmission_textures"))]
let (thickness, attenuation_distance, attenuation_color) =
material
.volume()
.map_or((0.0, f32::INFINITY, [1.0, 1.0, 1.0]), |volume| {
( (
volume.thickness_factor(), volume.thickness_factor(),
thickness_channel,
thickness_texture,
volume.attenuation_distance(), volume.attenuation_distance(),
volume.attenuation_color(), volume.attenuation_color(),
) )
}); },
);
let ior = material.ior().unwrap_or(1.5); #[cfg(not(feature = "pbr_transmission_textures"))]
let (thickness, attenuation_distance, attenuation_color) =
material
.volume()
.map_or((0.0, f32::INFINITY, [1.0, 1.0, 1.0]), |volume| {
(
volume.thickness_factor(),
volume.attenuation_distance(),
volume.attenuation_color(),
)
});
// Parse the `KHR_materials_clearcoat` extension data if necessary. let ior = material.ior().unwrap_or(1.5);
let clearcoat =
ClearcoatExtension::parse(load_context, document, material).unwrap_or_default();
// Parse the `KHR_materials_anisotropy` extension data if necessary. // Parse the `KHR_materials_clearcoat` extension data if necessary.
let anisotropy = let clearcoat =
AnisotropyExtension::parse(load_context, document, material).unwrap_or_default(); ClearcoatExtension::parse(load_context, document, material).unwrap_or_default();
// Parse the `KHR_materials_specular` extension data if necessary. // Parse the `KHR_materials_anisotropy` extension data if necessary.
let specular = let anisotropy =
SpecularExtension::parse(load_context, document, material).unwrap_or_default(); AnisotropyExtension::parse(load_context, document, material).unwrap_or_default();
// We need to operate in the Linear color space and be willing to exceed 1.0 in our channels // Parse the `KHR_materials_specular` extension data if necessary.
let base_emissive = LinearRgba::rgb(emissive[0], emissive[1], emissive[2]); let specular =
let emissive = base_emissive * material.emissive_strength().unwrap_or(1.0); SpecularExtension::parse(load_context, document, material).unwrap_or_default();
StandardMaterial { // We need to operate in the Linear color space and be willing to exceed 1.0 in our channels
base_color: Color::linear_rgba(color[0], color[1], color[2], color[3]), let base_emissive = LinearRgba::rgb(emissive[0], emissive[1], emissive[2]);
base_color_channel, let emissive = base_emissive * material.emissive_strength().unwrap_or(1.0);
base_color_texture,
perceptual_roughness: pbr.roughness_factor(), StandardMaterial {
metallic: pbr.metallic_factor(), base_color: Color::linear_rgba(color[0], color[1], color[2], color[3]),
metallic_roughness_channel, base_color_channel,
metallic_roughness_texture, base_color_texture,
normal_map_channel, perceptual_roughness: pbr.roughness_factor(),
normal_map_texture, metallic: pbr.metallic_factor(),
double_sided: material.double_sided(), metallic_roughness_channel,
cull_mode: if material.double_sided() { metallic_roughness_texture,
None normal_map_channel,
} else if is_scale_inverted { normal_map_texture,
Some(Face::Front) double_sided: material.double_sided(),
} else { cull_mode: if material.double_sided() {
Some(Face::Back) None
}, } else if is_scale_inverted {
occlusion_channel, Some(Face::Front)
occlusion_texture, } else {
emissive, Some(Face::Back)
emissive_channel, },
emissive_texture, occlusion_channel,
specular_transmission, occlusion_texture,
#[cfg(feature = "pbr_transmission_textures")] emissive,
specular_transmission_channel, emissive_channel,
#[cfg(feature = "pbr_transmission_textures")] emissive_texture,
specular_transmission_texture, specular_transmission,
thickness, #[cfg(feature = "pbr_transmission_textures")]
#[cfg(feature = "pbr_transmission_textures")] specular_transmission_channel,
thickness_channel, #[cfg(feature = "pbr_transmission_textures")]
#[cfg(feature = "pbr_transmission_textures")] specular_transmission_texture,
thickness_texture, thickness,
ior, #[cfg(feature = "pbr_transmission_textures")]
attenuation_distance, thickness_channel,
attenuation_color: Color::linear_rgb( #[cfg(feature = "pbr_transmission_textures")]
attenuation_color[0], thickness_texture,
attenuation_color[1], ior,
attenuation_color[2], attenuation_distance,
), attenuation_color: Color::linear_rgb(
unlit: material.unlit(), attenuation_color[0],
alpha_mode: alpha_mode(material), attenuation_color[1],
uv_transform, attenuation_color[2],
clearcoat: clearcoat.clearcoat_factor.unwrap_or_default() as f32, ),
clearcoat_perceptual_roughness: clearcoat.clearcoat_roughness_factor.unwrap_or_default() unlit: material.unlit(),
as f32, alpha_mode: alpha_mode(material),
#[cfg(feature = "pbr_multi_layer_material_textures")] uv_transform,
clearcoat_channel: clearcoat.clearcoat_channel, clearcoat: clearcoat.clearcoat_factor.unwrap_or_default() as f32,
#[cfg(feature = "pbr_multi_layer_material_textures")] clearcoat_perceptual_roughness: clearcoat
clearcoat_texture: clearcoat.clearcoat_texture, .clearcoat_roughness_factor
#[cfg(feature = "pbr_multi_layer_material_textures")] .unwrap_or_default() as f32,
clearcoat_roughness_channel: clearcoat.clearcoat_roughness_channel, #[cfg(feature = "pbr_multi_layer_material_textures")]
#[cfg(feature = "pbr_multi_layer_material_textures")] clearcoat_channel: clearcoat.clearcoat_channel,
clearcoat_roughness_texture: clearcoat.clearcoat_roughness_texture, #[cfg(feature = "pbr_multi_layer_material_textures")]
#[cfg(feature = "pbr_multi_layer_material_textures")] clearcoat_texture: clearcoat.clearcoat_texture,
clearcoat_normal_channel: clearcoat.clearcoat_normal_channel, #[cfg(feature = "pbr_multi_layer_material_textures")]
#[cfg(feature = "pbr_multi_layer_material_textures")] clearcoat_roughness_channel: clearcoat.clearcoat_roughness_channel,
clearcoat_normal_texture: clearcoat.clearcoat_normal_texture, #[cfg(feature = "pbr_multi_layer_material_textures")]
anisotropy_strength: anisotropy.anisotropy_strength.unwrap_or_default() as f32, clearcoat_roughness_texture: clearcoat.clearcoat_roughness_texture,
anisotropy_rotation: anisotropy.anisotropy_rotation.unwrap_or_default() as f32, #[cfg(feature = "pbr_multi_layer_material_textures")]
#[cfg(feature = "pbr_anisotropy_texture")] clearcoat_normal_channel: clearcoat.clearcoat_normal_channel,
anisotropy_channel: anisotropy.anisotropy_channel, #[cfg(feature = "pbr_multi_layer_material_textures")]
#[cfg(feature = "pbr_anisotropy_texture")] clearcoat_normal_texture: clearcoat.clearcoat_normal_texture,
anisotropy_texture: anisotropy.anisotropy_texture, anisotropy_strength: anisotropy.anisotropy_strength.unwrap_or_default() as f32,
// From the `KHR_materials_specular` spec: anisotropy_rotation: anisotropy.anisotropy_rotation.unwrap_or_default() as f32,
// <https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_specular#materials-with-reflectance-parameter> #[cfg(feature = "pbr_anisotropy_texture")]
reflectance: specular.specular_factor.unwrap_or(1.0) as f32 * 0.5, anisotropy_channel: anisotropy.anisotropy_channel,
#[cfg(feature = "pbr_specular_textures")] #[cfg(feature = "pbr_anisotropy_texture")]
specular_channel: specular.specular_channel, anisotropy_texture: anisotropy.anisotropy_texture,
#[cfg(feature = "pbr_specular_textures")] // From the `KHR_materials_specular` spec:
specular_texture: specular.specular_texture, // <https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_specular#materials-with-reflectance-parameter>
specular_tint: match specular.specular_color_factor { reflectance: specular.specular_factor.unwrap_or(1.0) as f32 * 0.5,
Some(color) => Color::linear_rgb(color[0] as f32, color[1] as f32, color[2] as f32), #[cfg(feature = "pbr_specular_textures")]
None => Color::WHITE, specular_channel: specular.specular_channel,
}, #[cfg(feature = "pbr_specular_textures")]
#[cfg(feature = "pbr_specular_textures")] specular_texture: specular.specular_texture,
specular_tint_channel: specular.specular_color_channel, specular_tint: match specular.specular_color_factor {
#[cfg(feature = "pbr_specular_textures")] Some(color) => {
specular_tint_texture: specular.specular_color_texture, Color::linear_rgb(color[0] as f32, color[1] as f32, color[2] as f32)
..Default::default() }
} None => Color::WHITE,
}) },
#[cfg(feature = "pbr_specular_textures")]
specular_tint_channel: specular.specular_color_channel,
#[cfg(feature = "pbr_specular_textures")]
specular_tint_texture: specular.specular_color_texture,
..Default::default()
}
})
.expect("material indices are unique, so the label is unique")
} }
fn get_uv_channel(material: &Material, texture_kind: &str, tex_coord: u32) -> UvChannel { fn get_uv_channel(material: &Material, texture_kind: &str, tex_coord: u32) -> UvChannel {