diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 938da9a284b9e..c4280caf1a08e 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -39,7 +39,7 @@ use gltf::{ texture::{MagFilter, MinFilter, WrappingMode}, Material, Node, Primitive, Semantic, }; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::{ collections::VecDeque, path::{Path, PathBuf}, @@ -105,20 +105,57 @@ pub struct GltfLoader { pub custom_vertex_attributes: HashMap, } +/// Specifies optional settings for processing gltfs at load time. By default, all recognized contents of +/// the gltf will be loaded. +/// +/// # Example +/// +/// To load a gltf but exclude the cameras, replace a call to `asset_server.load("my.gltf")` with +/// ```no_run +/// # use bevy_asset::{AssetServer, Handle}; +/// # use bevy_gltf::*; +/// # let asset_server: AssetServer = panic!(); +/// let gltf_handle: Handle = asset_server.load_with_settings( +/// "my.gltf", +/// |s: &mut GltfLoaderSettings| { +/// s.load_cameras = false; +/// } +/// ); +/// ``` +#[derive(Serialize, Deserialize)] +pub struct GltfLoaderSettings { + /// If true, the loader will load mesh nodes and the associated materials. + pub load_meshes: bool, + /// If true, the loader will spawn cameras for gltf camera nodes. + pub load_cameras: bool, + /// If true, the loader will spawn lights for gltf light nodes. + pub load_lights: bool, +} + +impl Default for GltfLoaderSettings { + fn default() -> Self { + Self { + load_meshes: true, + load_cameras: true, + load_lights: true, + } + } +} + impl AssetLoader for GltfLoader { type Asset = Gltf; - type Settings = (); + type Settings = GltfLoaderSettings; type Error = GltfError; fn load<'a>( &'a self, reader: &'a mut Reader, - _settings: &'a (), + settings: &'a GltfLoaderSettings, load_context: &'a mut LoadContext, ) -> bevy_utils::BoxedFuture<'a, Result> { Box::pin(async move { let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; - load_gltf(self, &bytes, load_context).await + load_gltf(self, &bytes, load_context, settings).await }) } @@ -132,6 +169,7 @@ async fn load_gltf<'a, 'b, 'c>( loader: &GltfLoader, bytes: &'a [u8], load_context: &'b mut LoadContext<'c>, + settings: &'b GltfLoaderSettings, ) -> Result { let gltf = gltf::Gltf::from_slice(bytes)?; let buffer_data = load_buffers(&gltf, load_context).await?; @@ -555,6 +593,7 @@ async fn load_gltf<'a, 'b, 'c>( parent, load_context, &mut scene_load_context, + settings, &mut node_index_to_entity_map, &mut entity_to_skin_index_map, &mut active_camera_found, @@ -860,6 +899,7 @@ fn load_node( world_builder: &mut WorldChildBuilder, root_load_context: &LoadContext, load_context: &mut LoadContext, + settings: &GltfLoaderSettings, node_index_to_entity_map: &mut HashMap, entity_to_skin_index_map: &mut HashMap, active_camera_found: &mut bool, @@ -887,46 +927,48 @@ fn load_node( } // create camera node - if let Some(camera) = gltf_node.camera() { - let projection = match camera.projection() { - gltf::camera::Projection::Orthographic(orthographic) => { - let xmag = orthographic.xmag(); - let orthographic_projection = OrthographicProjection { - near: orthographic.znear(), - far: orthographic.zfar(), - scaling_mode: ScalingMode::FixedHorizontal(1.0), - scale: xmag, - ..Default::default() - }; + if settings.load_cameras { + if let Some(camera) = gltf_node.camera() { + let projection = match camera.projection() { + gltf::camera::Projection::Orthographic(orthographic) => { + let xmag = orthographic.xmag(); + let orthographic_projection = OrthographicProjection { + near: orthographic.znear(), + far: orthographic.zfar(), + scaling_mode: ScalingMode::FixedHorizontal(1.0), + scale: xmag, + ..Default::default() + }; - Projection::Orthographic(orthographic_projection) - } - gltf::camera::Projection::Perspective(perspective) => { - let mut perspective_projection: PerspectiveProjection = PerspectiveProjection { - fov: perspective.yfov(), - near: perspective.znear(), - ..Default::default() - }; - if let Some(zfar) = perspective.zfar() { - perspective_projection.far = zfar; + Projection::Orthographic(orthographic_projection) } - if let Some(aspect_ratio) = perspective.aspect_ratio() { - perspective_projection.aspect_ratio = aspect_ratio; + gltf::camera::Projection::Perspective(perspective) => { + let mut perspective_projection: PerspectiveProjection = PerspectiveProjection { + fov: perspective.yfov(), + near: perspective.znear(), + ..Default::default() + }; + if let Some(zfar) = perspective.zfar() { + perspective_projection.far = zfar; + } + if let Some(aspect_ratio) = perspective.aspect_ratio() { + perspective_projection.aspect_ratio = aspect_ratio; + } + Projection::Perspective(perspective_projection) } - Projection::Perspective(perspective_projection) - } - }; - node.insert(Camera3dBundle { - projection, - transform, - camera: Camera { - is_active: !*active_camera_found, + }; + node.insert(Camera3dBundle { + projection, + transform, + camera: Camera { + is_active: !*active_camera_found, + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - }); + }); - *active_camera_found = true; + *active_camera_found = true; + } } // Map node index to entity @@ -935,140 +977,144 @@ fn load_node( let mut morph_weights = None; node.with_children(|parent| { - if let Some(mesh) = gltf_node.mesh() { - // append primitives - for primitive in mesh.primitives() { - let material = primitive.material(); - let material_label = material_label(&material, is_scale_inverted); - - // This will make sure we load the default material now since it would not have been - // added when iterating over all the gltf materials (since the default material is - // not explicitly listed in the gltf). - // It also ensures an inverted scale copy is instantiated if required. - if !root_load_context.has_labeled_asset(&material_label) - && !load_context.has_labeled_asset(&material_label) - { - load_material(&material, load_context, is_scale_inverted); - } + if settings.load_meshes { + if let Some(mesh) = gltf_node.mesh() { + // append primitives + for primitive in mesh.primitives() { + let material = primitive.material(); + let material_label = material_label(&material, is_scale_inverted); + + // This will make sure we load the default material now since it would not have been + // added when iterating over all the gltf materials (since the default material is + // not explicitly listed in the gltf). + // It also ensures an inverted scale copy is instantiated if required. + if !root_load_context.has_labeled_asset(&material_label) + && !load_context.has_labeled_asset(&material_label) + { + load_material(&material, load_context, is_scale_inverted); + } - let primitive_label = primitive_label(&mesh, &primitive); - let bounds = primitive.bounding_box(); + let primitive_label = primitive_label(&mesh, &primitive); + let bounds = primitive.bounding_box(); - let mut mesh_entity = parent.spawn(PbrBundle { - // TODO: handle missing label handle errors here? - mesh: load_context.get_label_handle(&primitive_label), - material: load_context.get_label_handle(&material_label), - ..Default::default() - }); - let target_count = primitive.morph_targets().len(); - if target_count != 0 { - let weights = match mesh.weights() { - Some(weights) => weights.to_vec(), - None => vec![0.0; target_count], - }; + let mut mesh_entity = parent.spawn(PbrBundle { + // TODO: handle missing label handle errors here? + mesh: load_context.get_label_handle(&primitive_label), + material: load_context.get_label_handle(&material_label), + ..Default::default() + }); + let target_count = primitive.morph_targets().len(); + if target_count != 0 { + let weights = match mesh.weights() { + Some(weights) => weights.to_vec(), + None => vec![0.0; target_count], + }; + + if morph_weights.is_none() { + morph_weights = Some(weights.clone()); + } - if morph_weights.is_none() { - morph_weights = Some(weights.clone()); + // unwrap: the parent's call to `MeshMorphWeights::new` + // means this code doesn't run if it returns an `Err`. + // According to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#morph-targets + // they should all have the same length. + // > All morph target accessors MUST have the same count as + // > the accessors of the original primitive. + mesh_entity.insert(MeshMorphWeights::new(weights).unwrap()); } + mesh_entity.insert(Aabb::from_min_max( + Vec3::from_slice(&bounds.min), + Vec3::from_slice(&bounds.max), + )); - // unwrap: the parent's call to `MeshMorphWeights::new` - // means this code doesn't run if it returns an `Err`. - // According to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#morph-targets - // they should all have the same length. - // > All morph target accessors MUST have the same count as - // > the accessors of the original primitive. - mesh_entity.insert(MeshMorphWeights::new(weights).unwrap()); - } - mesh_entity.insert(Aabb::from_min_max( - Vec3::from_slice(&bounds.min), - Vec3::from_slice(&bounds.max), - )); - - if let Some(extras) = primitive.extras() { - mesh_entity.insert(GltfExtras { - value: extras.get().to_string(), - }); - } + if let Some(extras) = primitive.extras() { + mesh_entity.insert(GltfExtras { + value: extras.get().to_string(), + }); + } - mesh_entity.insert(Name::new(primitive_name(&mesh, &primitive))); - // Mark for adding skinned mesh - if let Some(skin) = gltf_node.skin() { - entity_to_skin_index_map.insert(mesh_entity.id(), skin.index()); + mesh_entity.insert(Name::new(primitive_name(&mesh, &primitive))); + // Mark for adding skinned mesh + if let Some(skin) = gltf_node.skin() { + entity_to_skin_index_map.insert(mesh_entity.id(), skin.index()); + } } } } - if let Some(light) = gltf_node.light() { - match light.kind() { - gltf::khr_lights_punctual::Kind::Directional => { - let mut entity = parent.spawn(DirectionalLightBundle { - directional_light: DirectionalLight { - color: Color::rgb_from_array(light.color()), - // NOTE: KHR_punctual_lights defines the intensity units for directional - // lights in lux (lm/m^2) which is what we need. - illuminance: light.intensity(), + if settings.load_lights { + if let Some(light) = gltf_node.light() { + match light.kind() { + gltf::khr_lights_punctual::Kind::Directional => { + let mut entity = parent.spawn(DirectionalLightBundle { + directional_light: DirectionalLight { + color: Color::rgb_from_array(light.color()), + // NOTE: KHR_punctual_lights defines the intensity units for directional + // lights in lux (lm/m^2) which is what we need. + illuminance: light.intensity(), + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - }); - if let Some(name) = light.name() { - entity.insert(Name::new(name.to_string())); - } - if let Some(extras) = light.extras() { - entity.insert(GltfExtras { - value: extras.get().to_string(), }); + if let Some(name) = light.name() { + entity.insert(Name::new(name.to_string())); + } + if let Some(extras) = light.extras() { + entity.insert(GltfExtras { + value: extras.get().to_string(), + }); + } } - } - gltf::khr_lights_punctual::Kind::Point => { - let mut entity = parent.spawn(PointLightBundle { - point_light: PointLight { - color: Color::rgb_from_array(light.color()), - // NOTE: KHR_punctual_lights defines the intensity units for point lights in - // candela (lm/sr) which is luminous intensity and we need luminous power. - // For a point light, luminous power = 4 * pi * luminous intensity - intensity: light.intensity() * std::f32::consts::PI * 4.0, - range: light.range().unwrap_or(20.0), - radius: 0.0, + gltf::khr_lights_punctual::Kind::Point => { + let mut entity = parent.spawn(PointLightBundle { + point_light: PointLight { + color: Color::rgb_from_array(light.color()), + // NOTE: KHR_punctual_lights defines the intensity units for point lights in + // candela (lm/sr) which is luminous intensity and we need luminous power. + // For a point light, luminous power = 4 * pi * luminous intensity + intensity: light.intensity() * std::f32::consts::PI * 4.0, + range: light.range().unwrap_or(20.0), + radius: 0.0, + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - }); - if let Some(name) = light.name() { - entity.insert(Name::new(name.to_string())); - } - if let Some(extras) = light.extras() { - entity.insert(GltfExtras { - value: extras.get().to_string(), }); + if let Some(name) = light.name() { + entity.insert(Name::new(name.to_string())); + } + if let Some(extras) = light.extras() { + entity.insert(GltfExtras { + value: extras.get().to_string(), + }); + } } - } - gltf::khr_lights_punctual::Kind::Spot { - inner_cone_angle, - outer_cone_angle, - } => { - let mut entity = parent.spawn(SpotLightBundle { - spot_light: SpotLight { - color: Color::rgb_from_array(light.color()), - // NOTE: KHR_punctual_lights defines the intensity units for spot lights in - // candela (lm/sr) which is luminous intensity and we need luminous power. - // For a spot light, we map luminous power = 4 * pi * luminous intensity - intensity: light.intensity() * std::f32::consts::PI * 4.0, - range: light.range().unwrap_or(20.0), - radius: light.range().unwrap_or(0.0), - inner_angle: inner_cone_angle, - outer_angle: outer_cone_angle, + gltf::khr_lights_punctual::Kind::Spot { + inner_cone_angle, + outer_cone_angle, + } => { + let mut entity = parent.spawn(SpotLightBundle { + spot_light: SpotLight { + color: Color::rgb_from_array(light.color()), + // NOTE: KHR_punctual_lights defines the intensity units for spot lights in + // candela (lm/sr) which is luminous intensity and we need luminous power. + // For a spot light, we map luminous power = 4 * pi * luminous intensity + intensity: light.intensity() * std::f32::consts::PI * 4.0, + range: light.range().unwrap_or(20.0), + radius: light.range().unwrap_or(0.0), + inner_angle: inner_cone_angle, + outer_angle: outer_cone_angle, + ..Default::default() + }, ..Default::default() - }, - ..Default::default() - }); - if let Some(name) = light.name() { - entity.insert(Name::new(name.to_string())); - } - if let Some(extras) = light.extras() { - entity.insert(GltfExtras { - value: extras.get().to_string(), }); + if let Some(name) = light.name() { + entity.insert(Name::new(name.to_string())); + } + if let Some(extras) = light.extras() { + entity.insert(GltfExtras { + value: extras.get().to_string(), + }); + } } } } @@ -1081,6 +1127,7 @@ fn load_node( parent, root_load_context, load_context, + settings, node_index_to_entity_map, entity_to_skin_index_map, active_camera_found, @@ -1092,10 +1139,12 @@ fn load_node( } }); - if let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights) { - let primitive_label = mesh.primitives().next().map(|p| primitive_label(&mesh, &p)); - let first_mesh = primitive_label.map(|label| load_context.get_label_handle(label)); - node.insert(MorphWeights::new(weights, first_mesh)?); + if settings.load_meshes { + if let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights) { + let primitive_label = mesh.primitives().next().map(|p| primitive_label(&mesh, &p)); + let first_mesh = primitive_label.map(|label| load_context.get_label_handle(label)); + node.insert(MorphWeights::new(weights, first_mesh)?); + } } if let Some(err) = gltf_error {