@@ -2,17 +2,13 @@ mod extensions;
22mod gltf_ext;
33
44use alloc:: sync:: Arc ;
5- use std:: {
6- io:: Error ,
7- path:: { Path , PathBuf } ,
8- sync:: Mutex ,
9- } ;
5+ use std:: { io:: Error , sync:: Mutex } ;
106
117#[ cfg( feature = "bevy_animation" ) ]
128use bevy_animation:: { prelude:: * , AnimatedBy , AnimationTargetId } ;
139use bevy_asset:: {
14- io:: Reader , AssetLoadError , AssetLoader , Handle , LoadContext , ReadAssetBytesError ,
15- RenderAssetUsages ,
10+ io:: Reader , AssetLoadError , AssetLoader , AssetPath , Handle , LoadContext , ParseAssetPathError ,
11+ ReadAssetBytesError , RenderAssetUsages ,
1612} ;
1713use bevy_camera:: {
1814 primitives:: Aabb , visibility:: Visibility , Camera , Camera3d , OrthographicProjection ,
@@ -103,13 +99,19 @@ pub enum GltfError {
10399 /// Unsupported buffer format.
104100 #[ error( "unsupported buffer format" ) ]
105101 BufferFormatUnsupported ,
102+ /// The buffer URI was unable to be resolved with respect to the asset path.
103+ #[ error( "invalid buffer uri: {0}. asset path error={1}" ) ]
104+ InvalidBufferUri ( String , ParseAssetPathError ) ,
106105 /// Invalid image mime type.
107106 #[ error( "invalid image mime type: {0}" ) ]
108107 #[ from( ignore) ]
109108 InvalidImageMimeType ( String ) ,
110109 /// Error when loading a texture. Might be due to a disabled image file format feature.
111110 #[ error( "You may need to add the feature for the file format: {0}" ) ]
112111 ImageError ( #[ from] TextureError ) ,
112+ /// The image URI was unable to be resolved with respect to the asset path.
113+ #[ error( "invalid image uri: {0}. asset path error={1}" ) ]
114+ InvalidImageUri ( String , ParseAssetPathError ) ,
113115 /// Failed to read bytes from an asset path.
114116 #[ error( "failed to read bytes from an asset path: {0}" ) ]
115117 ReadAssetBytesError ( #[ from] ReadAssetBytesError ) ,
@@ -595,12 +597,11 @@ impl GltfLoader {
595597 let mut _texture_handles = Vec :: new ( ) ;
596598 if gltf. textures ( ) . len ( ) == 1 || cfg ! ( target_arch = "wasm32" ) {
597599 for texture in gltf. textures ( ) {
598- let parent_path = load_context. path ( ) . parent ( ) . unwrap ( ) ;
599600 let image = load_image (
600601 texture,
601602 & buffer_data,
602603 & linear_textures,
603- parent_path ,
604+ load_context . asset_path ( ) ,
604605 loader. supported_compressed_formats ,
605606 default_sampler,
606607 settings,
@@ -613,15 +614,15 @@ impl GltfLoader {
613614 IoTaskPool :: get ( )
614615 . scope ( |scope| {
615616 gltf. textures ( ) . for_each ( |gltf_texture| {
616- let parent_path = load_context. path ( ) . parent ( ) . unwrap ( ) ;
617+ let asset_path = load_context. asset_path ( ) . clone ( ) ;
617618 let linear_textures = & linear_textures;
618619 let buffer_data = & buffer_data;
619620 scope. spawn ( async move {
620621 load_image (
621622 gltf_texture,
622623 buffer_data,
623624 linear_textures,
624- parent_path ,
625+ & asset_path ,
625626 loader. supported_compressed_formats ,
626627 default_sampler,
627628 settings,
@@ -1079,7 +1080,7 @@ async fn load_image<'a, 'b>(
10791080 gltf_texture : gltf:: Texture < ' a > ,
10801081 buffer_data : & [ Vec < u8 > ] ,
10811082 linear_textures : & HashSet < usize > ,
1082- parent_path : & ' b Path ,
1083+ gltf_path : & ' b AssetPath < ' b > ,
10831084 supported_compressed_formats : CompressedImageFormats ,
10841085 default_sampler : & ImageSamplerDescriptor ,
10851086 settings : & GltfLoaderSettings ,
@@ -1129,7 +1130,9 @@ async fn load_image<'a, 'b>(
11291130 label : GltfAssetLabel :: Texture ( gltf_texture. index ( ) ) ,
11301131 } )
11311132 } else {
1132- let image_path = parent_path. join ( uri) ;
1133+ let image_path = gltf_path
1134+ . resolve_embed ( uri)
1135+ . map_err ( |err| GltfError :: InvalidImageUri ( uri. to_owned ( ) , err) ) ?;
11331136 Ok ( ImageOrPath :: Path {
11341137 path : image_path,
11351138 is_srgb,
@@ -1740,7 +1743,10 @@ async fn load_buffers(
17401743 Ok ( _) => return Err ( GltfError :: BufferFormatUnsupported ) ,
17411744 Err ( ( ) ) => {
17421745 // TODO: Remove this and add dep
1743- let buffer_path = load_context. path ( ) . parent ( ) . unwrap ( ) . join ( uri) ;
1746+ let buffer_path = load_context
1747+ . asset_path ( )
1748+ . resolve_embed ( uri)
1749+ . map_err ( |err| GltfError :: InvalidBufferUri ( uri. to_owned ( ) , err) ) ?;
17441750 load_context. read_asset_bytes ( buffer_path) . await ?
17451751 }
17461752 } ;
@@ -1802,7 +1808,7 @@ enum ImageOrPath {
18021808 label : GltfAssetLabel ,
18031809 } ,
18041810 Path {
1805- path : PathBuf ,
1811+ path : AssetPath < ' static > ,
18061812 is_srgb : bool ,
18071813 sampler_descriptor : ImageSamplerDescriptor ,
18081814 } ,
@@ -1904,12 +1910,14 @@ mod test {
19041910 memory:: { Dir , MemoryAssetReader } ,
19051911 AssetSource , AssetSourceId ,
19061912 } ,
1907- AssetApp , AssetPlugin , AssetServer , Assets , Handle , LoadState ,
1913+ AssetApp , AssetLoader , AssetPlugin , AssetServer , Assets , Handle , LoadState ,
19081914 } ;
19091915 use bevy_ecs:: { resource:: Resource , world:: World } ;
1916+ use bevy_image:: { Image , ImageLoaderSettings } ;
19101917 use bevy_log:: LogPlugin ;
19111918 use bevy_mesh:: skinning:: SkinnedMeshInverseBindposes ;
19121919 use bevy_mesh:: MeshPlugin ;
1920+ use bevy_pbr:: StandardMaterial ;
19131921 use bevy_scene:: ScenePlugin ;
19141922
19151923 fn test_app ( dir : Dir ) -> App {
@@ -2327,4 +2335,157 @@ mod test {
23272335 assert_eq ! ( skinned_node. children. len( ) , 2 ) ;
23282336 assert_eq ! ( skinned_node. skin. as_ref( ) , Some ( & gltf_root. skins[ 0 ] ) ) ;
23292337 }
2338+
2339+ fn test_app_custom_asset_source ( ) -> ( App , Dir ) {
2340+ let dir = Dir :: default ( ) ;
2341+
2342+ let mut app = App :: new ( ) ;
2343+ let custom_reader = MemoryAssetReader { root : dir. clone ( ) } ;
2344+ // Create a default asset source so we definitely don't try to read from disk.
2345+ app. register_asset_source (
2346+ AssetSourceId :: Default ,
2347+ AssetSource :: build ( ) . with_reader ( move || {
2348+ Box :: new ( MemoryAssetReader {
2349+ root : Dir :: default ( ) ,
2350+ } )
2351+ } ) ,
2352+ )
2353+ . register_asset_source (
2354+ "custom" ,
2355+ AssetSource :: build ( ) . with_reader ( move || Box :: new ( custom_reader. clone ( ) ) ) ,
2356+ )
2357+ . add_plugins ( (
2358+ LogPlugin :: default ( ) ,
2359+ TaskPoolPlugin :: default ( ) ,
2360+ AssetPlugin :: default ( ) ,
2361+ ScenePlugin ,
2362+ MeshPlugin ,
2363+ crate :: GltfPlugin :: default ( ) ,
2364+ ) ) ;
2365+
2366+ app. finish ( ) ;
2367+ app. cleanup ( ) ;
2368+
2369+ ( app, dir)
2370+ }
2371+
2372+ #[ test]
2373+ fn reads_buffer_in_custom_asset_source ( ) {
2374+ let ( mut app, dir) = test_app_custom_asset_source ( ) ;
2375+
2376+ dir. insert_asset_text (
2377+ Path :: new ( "abc.gltf" ) ,
2378+ r#"
2379+ {
2380+ "asset": {
2381+ "version": "2.0"
2382+ },
2383+ "buffers": [
2384+ {
2385+ "uri": "abc.bin",
2386+ "byteLength": 3
2387+ }
2388+ ]
2389+ }
2390+ "# ,
2391+ ) ;
2392+ // We don't care that the buffer contains reasonable info since we won't actually use it.
2393+ dir. insert_asset_text ( Path :: new ( "abc.bin" ) , "Sup" ) ;
2394+
2395+ let asset_server = app. world ( ) . resource :: < AssetServer > ( ) . clone ( ) ;
2396+ let handle: Handle < Gltf > = asset_server. load ( "custom://abc.gltf" ) ;
2397+ run_app_until ( & mut app, |_world| {
2398+ let load_state = asset_server. get_load_state ( handle. id ( ) ) . unwrap ( ) ;
2399+ match load_state {
2400+ LoadState :: Loaded => Some ( ( ) ) ,
2401+ LoadState :: Failed ( err) => panic ! ( "{err}" ) ,
2402+ _ => None ,
2403+ }
2404+ } ) ;
2405+ }
2406+
2407+ #[ test]
2408+ fn reads_images_in_custom_asset_source ( ) {
2409+ let ( mut app, dir) = test_app_custom_asset_source ( ) ;
2410+
2411+ app. init_asset :: < StandardMaterial > ( ) ;
2412+
2413+ // Note: We need the material here since otherwise we don't store the texture handle, which
2414+ // can result in the image getting dropped leading to the gltf never being loaded with
2415+ // dependencies.
2416+ dir. insert_asset_text (
2417+ Path :: new ( "abc.gltf" ) ,
2418+ r#"
2419+ {
2420+ "asset": {
2421+ "version": "2.0"
2422+ },
2423+ "textures": [
2424+ {
2425+ "source": 0,
2426+ "sampler": 0
2427+ }
2428+ ],
2429+ "images": [
2430+ {
2431+ "uri": "abc.png"
2432+ }
2433+ ],
2434+ "samplers": [
2435+ {
2436+ "magFilter": 9729,
2437+ "minFilter": 9729
2438+ }
2439+ ],
2440+ "materials": [
2441+ {
2442+ "pbrMetallicRoughness": {
2443+ "baseColorTexture": {
2444+ "index": 0,
2445+ "texCoord": 0
2446+ }
2447+ }
2448+ }
2449+ ]
2450+ }
2451+ "# ,
2452+ ) ;
2453+ // We don't care that the image contains reasonable info since we won't actually use it.
2454+ dir. insert_asset_text ( Path :: new ( "abc.png" ) , "Sup" ) ;
2455+
2456+ /// A fake loader to avoid actually loading any image data and just return an image.
2457+ struct FakePngLoader ;
2458+
2459+ impl AssetLoader for FakePngLoader {
2460+ type Asset = Image ;
2461+ type Error = std:: io:: Error ;
2462+ type Settings = ImageLoaderSettings ;
2463+
2464+ async fn load (
2465+ & self ,
2466+ _reader : & mut dyn bevy_asset:: io:: Reader ,
2467+ _settings : & Self :: Settings ,
2468+ _load_context : & mut bevy_asset:: LoadContext < ' _ > ,
2469+ ) -> Result < Self :: Asset , Self :: Error > {
2470+ Ok ( Image :: default ( ) )
2471+ }
2472+
2473+ fn extensions ( & self ) -> & [ & str ] {
2474+ & [ "png" ]
2475+ }
2476+ }
2477+
2478+ app. init_asset :: < Image > ( )
2479+ . register_asset_loader ( FakePngLoader ) ;
2480+
2481+ let asset_server = app. world ( ) . resource :: < AssetServer > ( ) . clone ( ) ;
2482+ let handle: Handle < Gltf > = asset_server. load ( "custom://abc.gltf" ) ;
2483+ run_app_until ( & mut app, |_world| {
2484+ // Note: we can't assert for failure since it's the nested load that fails, not the GLTF
2485+ // load.
2486+ asset_server
2487+ . is_loaded_with_dependencies ( & handle)
2488+ . then_some ( ( ) )
2489+ } ) ;
2490+ }
23302491}
0 commit comments