You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
martian-chess/src/display3d.rs

1215 lines
45 KiB
Rust

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::<AssetEvent<Tweakfile>>()),
hydrate_camera.run_if(any_component_added::<Camera3d>),
set_piece_model.run_if(any_component_added::<Piece>),
set_board_model.run_if(any_component_added::<game::BoardComponent>),
set_board_model.run_if(any_component_added::<TilesComponent>),
set_tile_hitbox.run_if(any_component_added::<game::Tile>),
set_piece_position.run_if(any_component_changed::<BoardIndex>),
set_piece_texture.run_if(any_component_changed::<Side>),
select
.run_if(in_state(GameState::Play))
.run_if(in_state(DisplayState::Display3d))
.run_if(on_event::<MouseButtonInput>()),
pick_up.run_if(any_component_added::<game::Selected>),
put_down.run_if(any_component_removed::<game::Selected>()),
switch_sides
.run_if(in_state(GameState::Play))
.run_if(state_changed::<game::TurnState>()),
create_valid_move_entity.run_if(any_component_added::<game::Selected>),
remove_valid_move_entity.run_if(any_component_removed::<game::Selected>()),
set_valid_move_model.run_if(any_component_added::<game::ValidMove>),
update_tweaks.run_if(on_event::<AssetEvent<Tweakfile>>()),
),
)
.add_systems(
Update,
(
move_camera.run_if(on_event::<MouseMotion>()),
mouse_zoom.run_if(on_event::<MouseWheel>()),
gizmo_system,
selected_gizmo,
moves_gizmo,
)
.run_if(resource_exists::<debug::DebugEnabled>())
.run_if(in_state(GameState::Play))
.run_if(in_state(DisplayState::Display3d)),
)
.add_systems(
OnEnter(DisplayState::Display3d),
(
activate::<Display3d>,
set_piece_texture,
opening_animation
.run_if(run_once())
.run_if(in_state(GameState::Play)),
update_tweaks,
),
)
.add_systems(OnExit(DisplayState::Display3d), deactivate::<Display3d>)
.add_systems(
OnEnter(GameState::Play),
(
activate::<Display3d>.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<Gltf>,
skybox: Handle<Image>,
hitbox_shape: Handle<Mesh>,
hitbox_material: Handle<StandardMaterial>,
}
/// 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<AssetServer>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
tweak_file: Res<crate::tweak::GameTweakfile>,
tweak_files: Res<Assets<Tweakfile>>,
) {
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<game::Board>, assets: Res<AssetsMap>) {
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<Camera>>,
assets: Res<AssetsMap>,
assets_map: Res<AssetsMap>,
gltfs: Res<Assets<Gltf>>,
state: Res<State<game::TurnState>>,
mut players: Query<&mut AnimationPlayer>,
mut commands: Commands,
tweaks: Res<Assets<Tweakfile>>,
server: Res<AssetServer>,
) {
let handle: Handle<Tweakfile> = 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<Display3d>,
>,
tweaks: Res<Assets<Tweakfile>>,
// mut lights: Query<
// (
// Option<&mut SpotLight>,
// Option<&mut PointLight>,
// Option<&mut DirectionalLight>,
// ),
// Or<(With<SpotLight>, With<PointLight>, With<DirectionalLight>)>,
// >,
mut commands: Commands,
server: Res<AssetServer>,
) {
let handle: Handle<Tweakfile> = 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<ScreenSpaceAmbientOcclusionQualityLevel> =
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::<ScreenSpaceAmbientOcclusionSettings>();
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<Image>>, assets: Res<AssetsMap>) {
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<Scene>, &Piece), (Added<game::Piece>, With<Display3d>)>,
assets_map: Res<AssetsMap>,
gltfs: Res<Assets<Gltf>>,
tweaks: Res<Assets<Tweakfile>>,
server: Res<AssetServer>,
) {
let handle: Handle<Tweakfile> = 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<Scene>,
(
With<game::BoardComponent>,
Without<TilesComponent>,
With<Display3d>,
),
>,
mut tiles: Query<
&mut Handle<Scene>,
(
With<TilesComponent>,
Without<game::BoardComponent>,
With<Display3d>,
),
>,
assets_map: Res<AssetsMap>,
gltfs: Res<Assets<Gltf>>,
tweaks: Res<Assets<Tweakfile>>,
server: Res<AssetServer>,
) {
let handle: Handle<Tweakfile> = 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<game::Piece>, With<Display3d>, Changed<BoardIndex>),
>,
) {
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<Input<MouseButton>>,
mut events: EventReader<MouseMotion>,
mut camera: Query<&mut Transform, (With<Display3d>, With<Camera>)>,
) {
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<MouseWheel>,
mut camera: Query<&mut Transform, (With<Display3d>, With<Camera>)>,
) {
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<game::Piece>,
With<Display3d>,
Or<(Changed<Side>, Added<Side>)>,
),
>,
gltfs: Res<Assets<Gltf>>,
assets_map: Res<AssetsMap>,
children: Query<&Children>,
mut models: Query<(&Name, &mut Handle<StandardMaterial>)>,
tweaks: Res<Assets<Tweakfile>>,
server: Res<AssetServer>,
) {
let handle: Handle<Tweakfile> = 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<MouseButtonInput>,
query: Query<(Entity, &Handle<Mesh>, &GlobalTransform)>,
meshes: Res<Assets<Mesh>>,
cameras: Query<(&Camera, &GlobalTransform)>,
windows: Query<&Window, With<PrimaryWindow>>,
selectable: Query<(Entity, &BoardIndex), (With<game::Selectable>, With<Display3d>)>,
children: Query<&Children>,
mut selections: EventWriter<game::Selection>,
) {
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, &gt).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<game::Selected>, With<Display3d>)>,
mut gizmos: Gizmos,
) {
selected.iter().for_each(|transform| {
gizmos.cuboid(transform.clone(), Color::GREEN);
})
}
fn moves_gizmo(
selected: Query<&BoardIndex, (With<game::Piece>, With<game::Selected>, With<Display3d>)>,
board: Res<Board>,
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<game::Piece>, Added<game::Selected>, With<Display3d>)>,
board: Res<Board>,
root: Query<Entity, With<game::BoardComponent>>,
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<Scene>, (With<Display3d>, Added<game::ValidMove>)>,
gltfs: Res<Assets<Gltf>>,
assets_map: Res<AssetsMap>,
tweaks: Res<Assets<Tweakfile>>,
server: Res<AssetServer>,
) {
let handle: Handle<Tweakfile> = 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<game::Selected>,
valid_moves: Query<Entity, With<game::ValidMove>>,
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<game::Piece>, With<Display3d>, Added<game::Selected>),
>,
assets_map: Res<AssetsMap>,
gltfs: Res<Assets<Gltf>>,
children: Query<&Children>,
mut players: Query<&mut AnimationPlayer>,
tweaks: Res<Assets<Tweakfile>>,
server: Res<AssetServer>,
) {
let handle: Handle<Tweakfile> = 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<game::Selected>,
mut query: Query<&game::Piece, (With<game::Piece>, With<game::Selectable>, With<Display3d>)>,
assets_map: Res<AssetsMap>,
gltfs: Res<Assets<Gltf>>,
children: Query<&Children>,
mut players: Query<&mut AnimationPlayer>,
tweaks: Res<Assets<Tweakfile>>,
server: Res<AssetServer>,
) {
let handle: Handle<Tweakfile> = 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<Display3d>, Added<game::Tile>)>,
) {
events.iter_mut().for_each(|(mut transform, index)| {
*transform = Transform::from_translation(board_translation(index));
});
}
fn opening_animation(mut players: Query<&mut AnimationPlayer, (With<Camera>, With<Display3d>)>) {
players.iter_mut().for_each(|mut player| {
info!("Playing intro camera animation");
player.resume()
});
}
fn switch_sides(
mut players: Query<&mut AnimationPlayer, (With<Camera>, With<Display3d>)>,
assets_map: Res<AssetsMap>,
gltfs: Res<Assets<Gltf>>,
state: Res<State<game::TurnState>>,
tweaks: Res<Assets<Tweakfile>>,
server: Res<AssetServer>,
) {
let handle: Handle<Tweakfile> = 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<ColorGrading> 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<Tonemapping> 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<FogSettings> 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<FogFalloff> 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<std::option::Option<ScreenSpaceAmbientOcclusionQualityLevel>> for SsaoQualityLevelTweak {
fn into(self) -> Option<ScreenSpaceAmbientOcclusionQualityLevel> {
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<Msaa> 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<Gltf>,
pub skybox_file: String,
#[serde(skip)]
pub skybox_handle: Handle<Image>,
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<Scene>,
pub drone: String,
// #[serde(skip)]
// pub drone_handle: Handle<Scene>,
pub pawn: String,
// #[serde(skip)]
// pub pawn_handle: Handle<Scene>,
pub board: String,
// #[serde(skip)]
// pub board_handle: Handle<Scene>,
pub valid_move: String,
// #[serde(skip)]
// pub valid_move_handle: Handle<Scene>,
}
#[derive(Debug, Deserialize, Default, Clone)]
pub(crate) struct AnimationTweaks {
pub intro_a: String,
// #[serde(skip)]
// pub intro_a_handle: Handle<AnimationClip>,
pub intro_b: String,
// #[serde(skip)]
// pub intro_b_handle: Handle<AnimationClip>,
pub turn_a: String,
// #[serde(skip)]
// pub turn_a_handle: Handle<AnimationClip>,
pub turn_b: String,
// #[serde(skip)]
// pub turn_b_handle: Handle<AnimationClip>,
pub pick_up: String,
// #[serde(skip)]
// pub pick_up_handle: Handle<AnimationClip>,
pub put_down: String,
// #[serde(skip)]
// pub put_down_handle: Handle<AnimationClip>,
pub idle: String,
// #[serde(skip)]
// pub idle_handle: Handle<AnimationClip>,
}
#[derive(Debug, Deserialize, Default, Clone)]
pub(crate) struct MaterialTweaks {
pub queen_red: String,
// #[serde(skip)]
// pub queen_red_handle: Handle<StandardMaterial>,
pub queen_blue: String,
// #[serde(skip)]
// pub queen_blue_handle: Handle<StandardMaterial>,
pub drone_red: String,
// #[serde(skip)]
// pub drone_red_handle: Handle<StandardMaterial>,
pub drone_blue: String,
// #[serde(skip)]
// pub drone_blue_handle: Handle<StandardMaterial>,
pub pawn_red: String,
// #[serde(skip)]
// pub pawn_red_handle: Handle<StandardMaterial>,
pub pawn_blue: String,
// #[serde(skip)]
// pub pawn_blue_handle: Handle<StandardMaterial>,
pub dots_red: String,
// #[serde(skip)]
// pub dots_red_handle: Handle<StandardMaterial>,
pub dots_blue: String,
// #[serde(skip)]
// pub dots_blue_handle: Handle<StandardMaterial>,
}
#[derive(Debug, Deserialize, Default, Clone)]
pub(crate) struct LightTweaks {
pub spot_light_scale: f32,
pub point_light_scale: f32,
pub directional_light_scale: f32,
}
}