use std::path::Path; use crate::{ game::{Board, BoardIndex, Piece, Side}, prelude::*, tweak::Tweakfile, }; use bevy::{ animation::RepeatAnimation, core_pipeline::{ experimental::taa::{TemporalAntiAliasPlugin, TemporalAntiAliasSettings}, prepass::MotionVectorPrepass, tonemapping::{DebandDither, Tonemapping}, Skybox, }, input::mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, pbr::{ ScreenSpaceAmbientOcclusionBundle, ScreenSpaceAmbientOcclusionQualityLevel, ScreenSpaceAmbientOcclusionSettings, }, render::{ render_resource::{TextureViewDescriptor, TextureViewDimension}, view::ColorGrading, }, window::PrimaryWindow, asset::AssetPath, }; use serde::Deserialize; pub(crate) struct Display3dPlugin; impl Plugin for Display3dPlugin { fn build(&self, app: &mut App) { app.add_plugins(TemporalAntiAliasPlugin) .insert_resource(Msaa::Off) .add_systems( OnExit(GameState::Loading), (initialize, fix_skybox.before(initialize)), ) .add_systems( Update, ( load_assets .run_if(in_state(GameState::Loading)) .run_if(on_event::>()), hydrate_camera.run_if(any_component_added::), set_piece_model.run_if(any_component_added::), set_board_model.run_if(any_component_added::), set_board_model.run_if(any_component_added::), set_tile_hitbox.run_if(any_component_added::), set_piece_position.run_if(any_component_changed::), set_piece_texture.run_if(any_component_changed::), select .run_if(in_state(GameState::Play)) .run_if(in_state(DisplayState::Display3d)) .run_if(on_event::()), pick_up.run_if(any_component_added::), put_down.run_if(any_component_removed::()), switch_sides .run_if(in_state(GameState::Play)) .run_if(state_changed::()), create_valid_move_entity.run_if(any_component_added::), remove_valid_move_entity.run_if(any_component_removed::()), set_valid_move_model.run_if(any_component_added::), update_tweaks.run_if(on_event::>()), ), ) .add_systems( Update, ( move_camera.run_if(on_event::()), mouse_zoom.run_if(on_event::()), gizmo_system, selected_gizmo, moves_gizmo, ) .run_if(resource_exists::()) .run_if(in_state(GameState::Play)) .run_if(in_state(DisplayState::Display3d)), ) .add_systems( OnEnter(DisplayState::Display3d), ( activate::, set_piece_texture, opening_animation .run_if(run_once()) .run_if(in_state(GameState::Play)), update_tweaks, ), ) .add_systems(OnExit(DisplayState::Display3d), deactivate::) .add_systems( OnEnter(GameState::Play), ( activate::.run_if(in_state(DisplayState::Display3d)), set_piece_texture, opening_animation .run_if(run_once()) .run_if(in_state(DisplayState::Display3d)), ), ); } } #[derive(Debug, Component)] pub(crate) struct Display3d; #[derive(Debug, Component)] struct TilesComponent; #[derive(Debug, Resource, Clone)] struct AssetsMap { models: Handle, skybox: Handle, hitbox_shape: Handle, hitbox_material: Handle, } /// Load 3d models /// This is kind of pulling double duty. /// Both loads the GLTF file _and_ populates the ModelMap once that is loaded. fn load_assets( server: Res, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, tweak_file: Res, tweak_files: Res>, ) { let hitbox_shape = meshes.add(shape::Box::new(1.0, 0.1, 1.0).into()); let hitbox_material = materials.add(StandardMaterial { base_color: Color::NONE, perceptual_roughness: 0.0, reflectance: 0.0, alpha_mode: AlphaMode::Blend, ..default() }); let handle = server.load("martian.tweak.toml"); let tweak_file = tweak_files.get(handle).unwrap(); let asset_map = AssetsMap { models: tweak_file.display3d.models.assets_handle.clone(), skybox: tweak_file.display3d.models.skybox_handle.clone(), hitbox_shape, hitbox_material, }; commands.insert_resource(asset_map.clone()); } /// Initialize the 3d board fn initialize(mut commands: Commands, board: Res, assets: Res) { info!("Initializing root"); commands .spawn(( SpatialBundle { visibility: Visibility::Hidden, ..default() }, Display3d, )) .with_children(|parent| { info!("Intializing 3D Board!"); parent .spawn((Display3d, game::BoardComponent, SceneBundle { ..default() })) .with_children(|parent| { // TEMP: Tiles parent.spawn((Display3d, TilesComponent, SceneBundle { ..default() })); // Hitboxes game::tiles().for_each(|(index, tile)| { parent.spawn(( Display3d, index, tile, PbrBundle { mesh: assets.hitbox_shape.clone(), material: assets.hitbox_material.clone(), visibility: Visibility::Hidden, ..default() }, game::Selectable, )); }); // Pieces board.pieces().iter().for_each(|(index, piece)| { let side = Board::side(*index).expect("Spawn valid side"); parent.spawn(( side, Display3d, piece.clone(), index.clone(), SceneBundle { ..default() }, game::Selectable, )); }); }); }); } fn hydrate_camera( events: Query<(&Name, Entity), Added>, assets: Res, assets_map: Res, gltfs: Res>, state: Res>, mut players: Query<&mut AnimationPlayer>, mut commands: Commands, tweaks: Res>, server: Res, ) { let handle: Handle = server.load("martian.tweak.toml"); let tweak = tweaks.get(&handle).expect("Load tweakfile"); events .iter() .filter(|(name, _)| name.as_str() == "GameCam") .for_each(|(_, entity)| { info!("Initialize 3d camera"); // Populate the components for the camera commands.entity(entity).insert(( Display3d, DisplayState::Display3d, Camera3dBundle { camera: Camera { is_active: false, hdr: tweak.display3d.hdr, ..default() }, dither: DebandDither::Enabled, color_grading: ColorGrading { ..default() }, ..default() }, Skybox(assets.skybox.clone()), EnvironmentMapLight { diffuse_map: assets.skybox.clone(), specular_map: assets.skybox.clone(), }, UiCameraConfig { show_ui: true }, FogSettings { ..default() }, ScreenSpaceAmbientOcclusionBundle { ..default() }, TemporalAntiAliasSettings { ..default() }, MotionVectorPrepass { ..default() }, Name::new("3D Camera"), )); let gltf = gltfs.get(&assets_map.models).expect("Load GLTF content"); // Set it to the default position by starting the initial animation if let Ok(mut player) = players.get_mut(entity) { debug!("Animations: {:?}", gltf.named_animations.keys()); // GameCamIntro1, GameCamIntro2, GameCamSide1>2, GameCamSide2>1 let animation = match state.get() { game::TurnState::SideA => gltf .named_animations .get(tweak.display3d.models.animations.intro_a.as_str()), game::TurnState::SideB => gltf .named_animations .get(tweak.display3d.models.animations.intro_b.as_str()), }; player .play(animation.expect("Camera Startup").clone()) .pause(); } }); } /// Update display3d tweaks in the game /// Triggered on entering 3d state and when the tweakfile is updated. fn update_tweaks( mut camera_settings: Query< ( Entity, &mut FogSettings, &mut ColorGrading, &mut Tonemapping, ), With, >, tweaks: Res>, // mut lights: Query< // ( // Option<&mut SpotLight>, // Option<&mut PointLight>, // Option<&mut DirectionalLight>, // ), // Or<(With, With, With)>, // >, mut commands: Commands, server: Res, ) { let handle: Handle = server.load("martian.tweak.toml"); if let Some(tweak) = tweaks.get(&handle) { camera_settings.iter_mut().for_each( |(entity, mut fog, mut color_grading, mut tonemapping)| { *fog = tweak.display3d.fog.clone().into(); *color_grading = tweak.display3d.color.grading.clone().into(); *tonemapping = tweak.display3d.color.tonemapping.clone().into(); let quality_level: Option = tweak.display3d.ssao.quality_level.clone().into(); match quality_level { Some(quality_level) => { commands .entity(entity) .insert(ScreenSpaceAmbientOcclusionSettings { quality_level }); commands.insert_resource(Msaa::Off); } None => { commands .entity(entity) .remove::(); let msaa: Msaa = tweak.display3d.msaa.clone().into(); commands.insert_resource(msaa); } } }, ); // lights // .iter_mut() // .for_each(|(mut spot, mut point, mut direction)| { // // Depending on the light, set the scalar tweak // }); } } fn fix_skybox(mut images: ResMut>, assets: Res) { let image = images.get_mut(&assets.skybox).unwrap(); info!("Loaded skybox image"); // 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.texture_descriptor.size.height / image.texture_descriptor.size.width, ); image.texture_view_descriptor = Some(TextureViewDescriptor { dimension: Some(TextureViewDimension::Cube), ..default() }); } } /// Set the model for each piece based on the game::Piece::* marker fn set_piece_model( mut events: Query<(&mut Handle, &Piece), (Added, With)>, assets_map: Res, gltfs: Res>, tweaks: Res>, server: Res, ) { let handle: Handle = server.load("martian.tweak.toml"); let tweak = tweaks.get(&handle).expect("Load tweakfile"); events.iter_mut().for_each(|(mut handle, piece)| { let gltf = gltfs.get(&assets_map.models).expect("Load GLTF content"); *handle = match piece { game::Piece::Pawn => gltf .named_scenes .get(tweak.display3d.models.scenes.pawn.as_str()), game::Piece::Drone => gltf .named_scenes .get(tweak.display3d.models.scenes.drone.as_str()), game::Piece::Queen => gltf .named_scenes .get(tweak.display3d.models.scenes.queen.as_str()), } .expect("Game board model") .clone(); }); } fn set_board_model( mut boards: Query< &mut Handle, ( With, Without, With, ), >, mut tiles: Query< &mut Handle, ( With, Without, With, ), >, assets_map: Res, gltfs: Res>, tweaks: Res>, server: Res, ) { let handle: Handle = server.load("martian.tweak.toml"); let tweak = tweaks.get(&handle).expect("Load tweakfile"); boards.iter_mut().for_each(|mut handle| { let gltf = gltfs.get(&assets_map.models).expect("Load GLTF content"); *handle = gltf .named_scenes .get(tweak.display3d.models.scenes.board.as_str()) .expect("Game board model") .clone(); }); // TODO: Get rid of this -- upstream asset has merged tiles back into gameboard scene tiles.iter_mut().for_each(|mut handle| { let gltf = gltfs.get(&assets_map.models).expect("Load GLTF content"); *handle = gltf .named_scenes .get("Tiles") .expect("Game tiles model") .clone(); }); } /// Sets a piece location given it's board index fn set_piece_position( mut events: Query< (&mut Transform, &BoardIndex), (With, With, Changed), >, ) { events.iter_mut().for_each(|(mut t, i)| { t.translation = board_translation(i); }); } /// Given a board index returns the Vec3 location in space fn board_translation(&BoardIndex { x, y }: &BoardIndex) -> Vec3 { // Scale x down by 4 to account for -4..4 scaling let x = x as i8 - 4; // Mirror y axis because our board index is inverted... let y = -(y as i8) + 1; let x = if x < 0 { x as f32 * 1.3 + 0.275 // 0.325 } else { x as f32 * 1.3 + 1.05 // 1.0 }; let y = y as f32 * 1.3 + 0.65; Vec3::new(x, 0.0, y) } fn gizmo_system(mut gizmos: Gizmos) { for y in 0..4 { for x in 0..8 { gizmos.cuboid( Transform::from_translation(board_translation(&BoardIndex { x, y })) .with_scale(Vec3::new(1.0, 0.1, 1.0)), Color::PURPLE, ) } } } /// TODO: This has bad feel, needs to be tuned fn move_camera( buttons: Res>, mut events: EventReader, mut camera: Query<&mut Transform, (With, With)>, ) { events.read().for_each(|MouseMotion { delta }| { if buttons.pressed(MouseButton::Left) { camera.iter_mut().for_each(|mut t| { t.rotate_around(Vec3::ZERO, Quat::from_rotation_y(delta.x / 256.0)); t.rotate_around(Vec3::ZERO, Quat::from_rotation_x(delta.y / 256.0)); t.look_at(Vec3::ZERO, Vec3::Y); }); } }); } fn mouse_zoom( mut events: EventReader, mut camera: Query<&mut Transform, (With, With)>, ) { events.read().for_each(|MouseWheel { unit, y, .. }| { camera.iter_mut().for_each(|mut t| { match unit { MouseScrollUnit::Line => { t.translation *= 1.0 - (*y / 4.0); } MouseScrollUnit::Pixel => { t.translation *= 1.0 - (*y / 64.0); } } t.look_at(Vec3::ZERO, Vec3::Y); }); }); } /// Set the Texture for a piece given it's position (left or right) on the bord. /// Executed when Side is changed or upon entry to Display3d state /// Getting this to run _after_ GLTF is loaded is a pain. /// PERF: We are saving what to work on in a Vector which is bad. /// CAVEAT: We are only exeucting this when a piece changes or state is changed. fn set_piece_texture( events: Query< (Entity, &Piece, &Side), ( With, With, Or<(Changed, Added)>, ), >, gltfs: Res>, assets_map: Res, children: Query<&Children>, mut models: Query<(&Name, &mut Handle)>, tweaks: Res>, server: Res, ) { let handle: Handle = server.load("martian.tweak.toml"); let tweak = tweaks.get(&handle).expect("Load tweakfile"); events.iter().for_each(|(entity, piece, side)| { debug!("Checking piece texture for {:?}", entity); if let Some(gltf) = gltfs.get(&assets_map.models) { children.iter_descendants(entity).for_each(|child| { if let Ok((n, mut m)) = models.get_mut(child) { debug!("Setting piece texture for {:?}", child); match (*piece, side, n.as_str()) { (Piece::Queen, Side::A, "Queen.0") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.queen_red.as_str()) .expect("Load Red Queen texture") .clone() } (Piece::Queen, Side::A, "Queen.1") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.dots_red.as_str()) .expect("Load Red Dots texture") .clone() } (Piece::Queen, Side::B, "Queen.0") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.queen_blue.as_str()) .expect("Load Blue Queen texture") .clone() } (Piece::Queen, Side::B, "Queen.1") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.dots_blue.as_str()) .expect("Load Red Dots texture") .clone() } (Piece::Drone, Side::A, "Drone.0") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.drone_red.as_str()) .expect("Load Red Drone texture") .clone() } (Piece::Drone, Side::A, "Drone.1") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.dots_red.as_str()) .expect("Load Red Dots texture") .clone() } (Piece::Drone, Side::B, "Drone.0") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.drone_blue.as_str()) .expect("Load Blue Drone texture") .clone() } (Piece::Drone, Side::B, "Drone.1") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.dots_blue.as_str()) .expect("Load Blue Dots texture") .clone() } (Piece::Pawn, Side::A, "Pawn.0") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.pawn_red.as_str()) .expect("Load Red Pawn texture") .clone() } (Piece::Pawn, Side::A, "Pawn.1") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.dots_red.as_str()) .expect("Load Red Dots texture") .clone() } (Piece::Pawn, Side::B, "Pawn.0") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.pawn_blue.as_str()) .expect("Load Blue Pawn texture") .clone() } (Piece::Pawn, Side::B, "Pawn.1") => { *m = gltf .named_materials .get(tweak.display3d.models.materials.dots_blue.as_str()) .expect("Load Blue Dots texture") .clone() } _ => warn!("???"), } } }); } }) } /// Select tiles and pieces in 3d /// There is a bug where we are selecting multiple entities... /// TODO: Selectable generalize picking pieces **and** hitboxes fn select( mut events: EventReader, query: Query<(Entity, &Handle, &GlobalTransform)>, meshes: Res>, cameras: Query<(&Camera, &GlobalTransform)>, windows: Query<&Window, With>, selectable: Query<(Entity, &BoardIndex), (With, With)>, children: Query<&Children>, mut selections: EventWriter, ) { events .read() .filter(|ev| ev.state == ButtonState::Pressed) .for_each(|_| { windows.iter().for_each(|window| { window.cursor_position().and_then(|pos| { cameras.iter().for_each(|(camera, gt)| { camera.viewport_to_world(gt, pos).and_then(|ray| { query .iter() .filter_map(|(entity, handle, gt)| { meshes.get(handle).map(|mesh| (entity, mesh, gt)) }) .for_each(|(entity, mesh, gt)| { hit::intersects3d(&ray, mesh, >).and_then(|_hit| { selectable .iter() .find_map(|(e, &board_index)| { // A child was hit (pieces) let primary = entity == e; // This entity was hit (tile hitboxes) let secondary = children .iter_descendants(e) .any(|child| child == entity); (primary || secondary).then_some(board_index) }) .iter() .for_each(|&board_index| { selections .send(game::Selection(board_index.clone())); }); Some(()) }); }); Some(()) }); }); Some(()) }); }); }); } fn selected_gizmo( selected: Query<&Transform, (With, With)>, mut gizmos: Gizmos, ) { selected.iter().for_each(|transform| { gizmos.cuboid(transform.clone(), Color::GREEN); }) } fn moves_gizmo( selected: Query<&BoardIndex, (With, With, With)>, board: Res, mut gizmos: Gizmos, ) { selected.iter().for_each(|idx| { board .valid_moves(*idx) .iter() .map(|i| Transform::from_translation(board_translation(i))) .for_each(|t| gizmos.cuboid(t, Color::WHITE)) }); } /// Spawn 3d "Valid move" indicators when a piece is selected /// Another system registers these new entities and associates the correct models and plays animations. fn create_valid_move_entity( events: Query<&BoardIndex, (With, Added, With)>, board: Res, root: Query>, mut commands: Commands, ) { events.iter().for_each(|idx| { if let Ok(board_entity) = root.get_single() { board .valid_moves(*idx) .iter() .map(|i| Transform::from_translation(board_translation(i))) .for_each(|t| { commands.entity(board_entity).with_children(|parent| { parent .spawn((Display3d, game::ValidMove, SceneBundle { ..default() })) .insert(t); }); }); } }); } fn set_valid_move_model( mut events: Query<&mut Handle, (With, Added)>, gltfs: Res>, assets_map: Res, tweaks: Res>, server: Res, ) { let handle: Handle = server.load("martian.tweak.toml"); let tweak = tweaks.get(&handle).expect("Load tweakfile"); if let Some(gltf) = gltfs.get(&assets_map.models) { events.iter_mut().for_each(|mut handle| { *handle = gltf .named_scenes .get(tweak.display3d.models.scenes.valid_move.as_str()) .unwrap() .clone() }) } } fn _play_valid_move_animation(_players: Query<&AnimationPlayer>) { todo!(); } // TODO: Move this to game.rs /// Remove "Valid Move" indicators when a piece is de-selected fn remove_valid_move_entity( mut events: RemovedComponents, valid_moves: Query>, mut commands: Commands, ) { events.read().for_each(|_| { valid_moves.iter().for_each(|entity| { commands.entity(entity).despawn_recursive(); }); }); } fn pick_up( mut events: Query< (Entity, &game::Piece), (With, With, Added), >, assets_map: Res, gltfs: Res>, children: Query<&Children>, mut players: Query<&mut AnimationPlayer>, tweaks: Res>, server: Res, ) { let handle: Handle = server.load("martian.tweak.toml"); let tweak = tweaks.get(&handle).expect("Load tweakfile"); events.iter_mut().for_each(|(entity, piece)| { let gltf = gltfs.get(&assets_map.models).expect("Load GLTF content"); children.iter_descendants(entity).for_each(|child| { if let Ok(mut player) = players.get_mut(child) { info!("Picking up {:?} {:?}", entity, piece); let animation = gltf .named_animations .get(tweak.display3d.models.animations.pick_up.as_str()); let idle = gltf .named_animations .get(tweak.display3d.models.animations.idle.as_str()); player .start_with_transition( animation.expect("Pickup Animation").clone(), Duration::from_secs_f32(0.75), ) .start_with_transition( idle.expect("Idle animation").clone(), Duration::from_secs_f32(1.5), ) .set_repeat(RepeatAnimation::Forever); } }) }); } fn put_down( mut events: RemovedComponents, mut query: Query<&game::Piece, (With, With, With)>, assets_map: Res, gltfs: Res>, children: Query<&Children>, mut players: Query<&mut AnimationPlayer>, tweaks: Res>, server: Res, ) { let handle: Handle = server.load("martian.tweak.toml"); let tweak = tweaks.get(&handle).expect("Load tweakfile"); events.read().for_each(|entity| { if let Ok(_piece) = query.get_mut(entity) { let gltf = gltfs.get(&assets_map.models).expect("Load GLTF content"); children.iter_descendants(entity).for_each(|child| { if let Ok(mut player) = players.get_mut(child) { info!("Putting down {:?}", entity); let animation = gltf .named_animations .get(tweak.display3d.models.animations.put_down.as_str()); player .start_with_transition( animation.expect("PutDown Animation").clone(), Duration::from_secs_f32(0.75), ) .set_repeat(RepeatAnimation::Never); } }) } }) } fn set_tile_hitbox( mut events: Query<(&mut Transform, &BoardIndex), (With, Added)>, ) { events.iter_mut().for_each(|(mut transform, index)| { *transform = Transform::from_translation(board_translation(index)); }); } fn opening_animation(mut players: Query<&mut AnimationPlayer, (With, With)>) { players.iter_mut().for_each(|mut player| { info!("Playing intro camera animation"); player.resume() }); } fn switch_sides( mut players: Query<&mut AnimationPlayer, (With, With)>, assets_map: Res, gltfs: Res>, state: Res>, tweaks: Res>, server: Res, ) { let handle: Handle = server.load("martian.tweak.toml"); let tweak = tweaks.get(&handle).expect("Load tweakfile"); let gltf = gltfs.get(&assets_map.models).expect("Load GLTF content"); players.iter_mut().for_each(|mut player| { let animation = match state.get() { game::TurnState::SideA => gltf .named_animations .get(tweak.display3d.models.animations.turn_a.as_str()), game::TurnState::SideB => gltf .named_animations .get(tweak.display3d.models.animations.turn_b.as_str()), }; player.start_with_transition( animation.expect("Camera Transition Animation").clone(), Duration::from_secs_f32(1.00), ); }); } pub(crate) mod tweaks { use bevy::{ core_pipeline::tonemapping::Tonemapping, pbr::ScreenSpaceAmbientOcclusionQualityLevel, asset::LoadContext, }; use super::*; #[derive(Debug, Deserialize, Default)] pub(crate) struct Display3dTweaks { #[serde(default)] pub hdr: bool, #[serde(default)] pub fog: FogTweaks, #[serde(default)] pub color: ColorTweaks, #[serde(default)] pub ssao: SsaoTweaks, #[serde(default)] pub msaa: MsaaTweaks, #[serde(default)] pub models: ModelTweaks, #[serde(default)] pub lights: LightTweaks, } impl Display3dTweaks { pub fn load_dependencies(&mut self, load_context: &mut LoadContext) { self.models.assets_handle = load_context.load(self.models.assets_file.clone()); self.models.skybox_handle = load_context.load(self.models.skybox_file.clone()); // self.models.scenes.queen_handle = load_context.load(self.models.scenes.queen.clone()); // self.models.scenes.drone_handle = load_context.load(self.models.scenes.drone.clone()); // self.models.scenes.pawn_handle = load_context.load(self.models.scenes.pawn.clone()); // self.models.scenes.board_handle = load_context.load(self.models.scenes.board.clone()); // self.models.scenes.valid_move_handle = load_context.load(self.models.scenes.valid_move.clone()); // self.models.animations.intro_a_handle = load_context.load(self.models.animations.intro_a.clone()); // self.models.animations.intro_b_handle = load_context.load(self.models.animations.xxx.clone()); // self.models.animations.turn_a_handle = load_context.load(self.models.animations.turn_a.clone()); // self.models.animations.turn_b_handle = load_context.load(self.models.animations.turn_b.clone()); // self.models.animations.pick_up_handle = load_context.load(self.models.animations.pick_up.clone()); // self.models.animations.put_down_handle = load_context.load(self.models.animations.put_down.clone()); // self.models.animations.idle_handle = load_context.load(self.models.animations.idle.clone()); // self.models.materials.queen_red_handle = load_context.load(self.models.materials.queen_red.clone()); // self.models.materials.queen_blue_handle = load_context.load(self.models.materials.queen_blue.clone()); // self.models.materials.drone_red_handle = load_context.load(self.models.materials.drone_red.clone()); // self.models.materials.drone_blue_handle = load_context.load(self.models.materials.drone_blue.clone()); // self.models.materials.pawn_red_handle = load_context.load(self.models.materials.xxx.clone()); // self.models.materials.pawn_blue_handle = load_context.load(self.models.materials.pawn_blue.clone()); // self.models.materials.dots_red_handle = load_context.load(self.models.materials.dots_red.clone()); // self.models.materials.dots_blue_handle = load_context.load(self.models.materials.dots_blue.clone()); } } #[derive(Debug, Deserialize, Default)] pub(crate) struct ColorTweaks { #[serde(default)] pub grading: ColorGradingTweaks, #[serde(default)] pub tonemapping: TonemappingTweak, } #[derive(Debug, Deserialize, Default, Clone)] pub(crate) struct ColorGradingTweaks { pub exposure: f32, pub gamma: f32, pub pre_saturation: f32, pub post_saturation: f32, } impl Into for ColorGradingTweaks { fn into(self) -> ColorGrading { ColorGrading { exposure: self.exposure, gamma: self.gamma, pre_saturation: self.pre_saturation, post_saturation: self.post_saturation, } } } #[derive(Debug, Deserialize, Default, Clone)] pub(crate) enum TonemappingTweak { None, Reinhard, ReinhardLuminance, AcesFitted, AgX, SomewhatBoringDisplayTransform, #[default] TonyMcMapface, BlenderFilmic, } impl Into for TonemappingTweak { fn into(self) -> Tonemapping { match self { TonemappingTweak::None => Tonemapping::None, TonemappingTweak::Reinhard => Tonemapping::Reinhard, TonemappingTweak::ReinhardLuminance => Tonemapping::ReinhardLuminance, TonemappingTweak::AcesFitted => Tonemapping::AcesFitted, TonemappingTweak::AgX => Tonemapping::AgX, TonemappingTweak::SomewhatBoringDisplayTransform => { Tonemapping::SomewhatBoringDisplayTransform } TonemappingTweak::TonyMcMapface => Tonemapping::TonyMcMapface, TonemappingTweak::BlenderFilmic => Tonemapping::BlenderFilmic, } } } #[derive(Debug, Clone, Deserialize)] pub(crate) struct FogTweaks { #[serde(default)] color: Color, #[serde(default)] light_color: Color, #[serde(default)] exponent: f32, #[serde(default)] falloff: FogFalloffTweak, } impl Default for FogTweaks { fn default() -> Self { FogTweaks { color: Color::WHITE, light_color: Color::WHITE, exponent: 1.0, falloff: FogFalloffTweak::Exponential { density: 1.0 }, } } } impl Into for FogTweaks { fn into(self) -> FogSettings { FogSettings { color: self.color, directional_light_color: self.light_color, directional_light_exponent: self.exponent, falloff: self.falloff.clone().into(), } } } #[derive(Debug, Deserialize, Clone)] pub(crate) enum FogFalloffTweak { Linear { start: f32, end: f32, }, Exponential { density: f32, }, ExponentialSquared { density: f32, }, Atmospheric { extinction: Vec3, inscattering: Vec3, }, } impl Default for FogFalloffTweak { fn default() -> FogFalloffTweak { FogFalloffTweak::Exponential { density: 1.0 } } } impl Into for FogFalloffTweak { fn into(self) -> FogFalloff { match self { FogFalloffTweak::Linear { start, end } => FogFalloff::Linear { start, end }, FogFalloffTweak::Exponential { density } => FogFalloff::Exponential { density }, FogFalloffTweak::ExponentialSquared { density } => { FogFalloff::ExponentialSquared { density } } FogFalloffTweak::Atmospheric { extinction, inscattering, } => FogFalloff::Atmospheric { extinction, inscattering, }, } } } #[derive(Debug, Clone, Default, Deserialize)] pub(crate) struct SsaoTweaks { #[serde(default)] pub quality_level: SsaoQualityLevelTweak, } #[derive(Debug, Deserialize, Default, Clone)] pub(crate) enum SsaoQualityLevelTweak { #[default] Off, Low, Medium, High, Ultra, Custom { slice_count: u32, samples_per_slice_side: u32, }, } impl Into> for SsaoQualityLevelTweak { fn into(self) -> Option { match self { SsaoQualityLevelTweak::Off => None, SsaoQualityLevelTweak::Low => Some(ScreenSpaceAmbientOcclusionQualityLevel::Low), SsaoQualityLevelTweak::Medium => { Some(ScreenSpaceAmbientOcclusionQualityLevel::Medium) } SsaoQualityLevelTweak::High => Some(ScreenSpaceAmbientOcclusionQualityLevel::High), SsaoQualityLevelTweak::Ultra => { Some(ScreenSpaceAmbientOcclusionQualityLevel::Ultra) } SsaoQualityLevelTweak::Custom { slice_count, samples_per_slice_side, } => Some(ScreenSpaceAmbientOcclusionQualityLevel::Custom { slice_count, samples_per_slice_side, }), } } } #[derive(Debug, Deserialize, Default, Clone)] pub(crate) enum MsaaTweaks { #[default] Off, Sample2, Sample4, Sample8, } impl Into for MsaaTweaks { fn into(self) -> Msaa { match self { MsaaTweaks::Off => Msaa::Off, MsaaTweaks::Sample2 => Msaa::Sample2, MsaaTweaks::Sample4 => Msaa::Sample4, MsaaTweaks::Sample8 => Msaa::Sample4, } } } #[derive(Debug, Deserialize, Default, Clone)] pub(crate) struct ModelTweaks { pub assets_file: String, #[serde(skip)] pub assets_handle: Handle, pub skybox_file: String, #[serde(skip)] pub skybox_handle: Handle, pub scenes: SceneTweaks, pub animations: AnimationTweaks, pub materials: MaterialTweaks, } #[derive(Debug, Deserialize, Default, Clone)] pub(crate) struct SceneTweaks { pub queen: String, // #[serde(skip)] // pub queen_handle: Handle, pub drone: String, // #[serde(skip)] // pub drone_handle: Handle, pub pawn: String, // #[serde(skip)] // pub pawn_handle: Handle, pub board: String, // #[serde(skip)] // pub board_handle: Handle, pub valid_move: String, // #[serde(skip)] // pub valid_move_handle: Handle, } #[derive(Debug, Deserialize, Default, Clone)] pub(crate) struct AnimationTweaks { pub intro_a: String, // #[serde(skip)] // pub intro_a_handle: Handle, pub intro_b: String, // #[serde(skip)] // pub intro_b_handle: Handle, pub turn_a: String, // #[serde(skip)] // pub turn_a_handle: Handle, pub turn_b: String, // #[serde(skip)] // pub turn_b_handle: Handle, pub pick_up: String, // #[serde(skip)] // pub pick_up_handle: Handle, pub put_down: String, // #[serde(skip)] // pub put_down_handle: Handle, pub idle: String, // #[serde(skip)] // pub idle_handle: Handle, } #[derive(Debug, Deserialize, Default, Clone)] pub(crate) struct MaterialTweaks { pub queen_red: String, // #[serde(skip)] // pub queen_red_handle: Handle, pub queen_blue: String, // #[serde(skip)] // pub queen_blue_handle: Handle, pub drone_red: String, // #[serde(skip)] // pub drone_red_handle: Handle, pub drone_blue: String, // #[serde(skip)] // pub drone_blue_handle: Handle, pub pawn_red: String, // #[serde(skip)] // pub pawn_red_handle: Handle, pub pawn_blue: String, // #[serde(skip)] // pub pawn_blue_handle: Handle, pub dots_red: String, // #[serde(skip)] // pub dots_red_handle: Handle, pub dots_blue: String, // #[serde(skip)] // pub dots_blue_handle: Handle, } #[derive(Debug, Deserialize, Default, Clone)] pub(crate) struct LightTweaks { pub spot_light_scale: f32, pub point_light_scale: f32, pub directional_light_scale: f32, } }