diff --git a/assets/textures/stars_cubemap.png b/assets/textures/stars_cubemap.png new file mode 100644 index 0000000..a6420aa Binary files /dev/null and b/assets/textures/stars_cubemap.png differ diff --git a/src/main.rs b/src/main.rs index 1230455..ead12df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,114 @@ -use bevy::prelude::*; +use bevy::{ + asset::LoadState, + core_pipeline::Skybox, + prelude::*, + render::{ + render_resource::{TextureViewDescriptor, TextureViewDimension}, + renderer::RenderDevice, + texture::CompressedImageFormats, + }, +}; fn main() { App::new() - .add_systems(Update, handle_input) + .add_systems(Startup, setup) + .add_systems(Update, ( + asset_loaded.after(load_cubemap_asset), + handle_input + )) .add_plugins((DefaultPlugins, )) .run(); } +#[derive(Resource)] +struct Cubemap { + is_loaded: bool, + index: usize, + image_handle: Handle, +} + +const CUBEMAPS: &[(&str, CompressedImageFormats)] = &[ + ( + "textures/stars_cubemap.png", + CompressedImageFormats::NONE, + ), +]; + +fn setup(mut commands: Commands, asset_server: Res) { + let skybox_handle = asset_server.load(CUBEMAPS[0].0); + // camera + commands.spawn(( + Camera3dBundle { + transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }, + Skybox { + image: skybox_handle.clone(), + brightness: 150.0, + }, + )); + commands.insert_resource(Cubemap { + is_loaded: false, + index: 0, + image_handle: skybox_handle, + }); +} + +fn load_cubemap_asset( + mut cubemap: ResMut, + asset_server: Res, + render_device: Res, +) { + let supported_compressed_formats = + CompressedImageFormats::from_features(render_device.features()); + + let mut new_index = cubemap.index; + for _ in 0..CUBEMAPS.len() { + new_index = (new_index + 1) % CUBEMAPS.len(); + if supported_compressed_formats.contains(CUBEMAPS[new_index].1) { + break; + } + info!("Skipping unsupported format: {:?}", CUBEMAPS[new_index]); + } + + // Skip swapping to the same texture. Useful for when ktx2, zstd, or compressed texture support + // is missing + if new_index == cubemap.index { + return; + } + + cubemap.index = new_index; + cubemap.image_handle = asset_server.load(CUBEMAPS[cubemap.index].0); + cubemap.is_loaded = false; +} + +fn asset_loaded( + asset_server: Res, + mut images: ResMut>, + mut cubemap: ResMut, + mut skyboxes: Query<&mut Skybox>, +) { + if !cubemap.is_loaded && asset_server.load_state(&cubemap.image_handle) == LoadState::Loaded { + info!("Swapping to {}...", CUBEMAPS[cubemap.index].0); + let image = images.get_mut(&cubemap.image_handle).unwrap(); + // NOTE: PNGs do not have any metadata that could indicate they contain a cubemap texture, + // so they appear as one texture. The following code reconfigures the texture as necessary. + if image.texture_descriptor.array_layer_count() == 1 { + image.reinterpret_stacked_2d_as_array(image.height() / image.width()); + image.texture_view_descriptor = Some(TextureViewDescriptor { + dimension: Some(TextureViewDimension::Cube), + ..default() + }); + } + + for mut skybox in &mut skyboxes { + skybox.image = cubemap.image_handle.clone(); + } + + cubemap.is_loaded = true; + } +} + fn handle_input( keyboard_input: Res>, mut app_exit_events: ResMut>