use core::fmt; use bevy::{ asset::LoadState, core_pipeline::Skybox, prelude::*, render::render_resource::{TextureViewDescriptor, TextureViewDimension}, }; use crate::MainCamera; pub struct SkyboxPlugin; impl Plugin for SkyboxPlugin { fn build(&self, app: &mut App) { app.add_systems( Update, ( load_skybox.run_if(resource_exists_and_changed::), spawn_skybox .run_if(resource_exists::) .run_if(skybox_assets_loaded), ), ); #[cfg(feature = "wasm")] app.add_systems(Update, wasm::set_skybox_from_frontend); } } #[derive(Resource, Deref, DerefMut)] pub struct SkyboxId(pub String); #[derive(Resource)] struct SkyboxAssets { cubemap: Handle, env_diffuse: Handle, env_specular: Handle, } fn load_skybox(mut cmd: Commands, assets: Res, id: Res) { let id = &**id; if let Ok(color) = Color::hex(id) { cmd.insert_resource(ClearColor(color)); cmd.insert_resource(AmbientLight { color, brightness: 1.0, }) } else { let color = Color::hex("AAAAAA").unwrap(); cmd.insert_resource(ClearColor(color)); cmd.insert_resource(AmbientLight { color, brightness: 1.0, }); // let paths = SkyboxPaths::new(id); // cmd.insert_resource(SkyboxAssets { // cubemap: assets.load(paths.cubemap), // env_diffuse: assets.load(paths.env_diffuse), // env_specular: assets.load(paths.env_specular), // }); } } fn skybox_assets_loaded( assets: Res, skybox_assets: Option>, ) -> bool { let Some(skybox_assets) = skybox_assets else { return false; }; let load_states = ( assets.get_load_state(skybox_assets.cubemap.clone_weak()), assets.get_load_state(skybox_assets.env_diffuse.clone_weak()), assets.get_load_state(skybox_assets.env_specular.clone_weak()), ); matches!( load_states, ( Some(LoadState::Loaded), Some(LoadState::Loaded), Some(LoadState::Loaded), ) ) } fn spawn_skybox( mut cmd: Commands, mut images: ResMut>, skybox_assets: Res, q_camera: Query>, ) { // // Configure the skybox texture as a cubemap // let image = images.get_mut(&skybox_assets.cubemap).unwrap(); // if image.texture_descriptor.array_layer_count() == 1 { // image.reinterpret_stacked_2d_as_array(6); // image.texture_view_descriptor = Some(TextureViewDescriptor { // dimension: Some(TextureViewDimension::Cube), // ..default() // }); // } // // Add components to the scene camera // cmd.entity(q_camera.single()).insert(( // Skybox { // image: skybox_assets.cubemap.clone(), // brightness: 10.0, // }, // EnvironmentMapLight { // diffuse_map: skybox_assets.env_diffuse.clone(), // specular_map: skybox_assets.env_specular.clone(), // intensity: 10.0, // }, // )); // // Remove the intermediate resource // cmd.remove_resource::(); } struct SkyboxPaths { cubemap: String, env_diffuse: String, env_specular: String, } impl SkyboxPaths { fn new(name: impl fmt::Display) -> Self { Self { cubemap: format!("skyboxes/{name}.hdr"), env_diffuse: format!("skyboxes/{name}_diffuse.zstd.ktx2"), env_specular: format!("skyboxes/{name}_specular.zstd.ktx2"), } } } #[cfg(feature = "wasm")] mod wasm { use bevy::prelude::*; use wasm_bindgen::prelude::*; use crate::wasm::{StaticBuffer, WasmMessageBuffer}; use super::SkyboxId; static SKYBOX_REQUEST: WasmMessageBuffer = WasmMessageBuffer::new(); #[wasm_bindgen(js_name = setSkybox)] pub fn set_skybox(id: &str) { SKYBOX_REQUEST.write_message(id.to_string()); } pub(super) fn set_skybox_from_frontend(mut cmd: Commands) { if let Some(id) = SKYBOX_REQUEST.take_message() { cmd.insert_resource(SkyboxId(id)); } } }