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.
1440 lines
54 KiB
Rust
1440 lines
54 KiB
Rust
use crate::{
|
|
game::{Board, BoardIndex, Piece, Side},
|
|
prelude::*,
|
|
tweak::Tweaks,
|
|
};
|
|
use bevy::{
|
|
animation::RepeatAnimation,
|
|
core_pipeline::{
|
|
experimental::taa::{TemporalAntiAliasPlugin, TemporalAntiAliasSettings},
|
|
prepass::MotionVectorPrepass,
|
|
tonemapping::{DebandDither, Tonemapping},
|
|
Skybox,
|
|
},
|
|
input::mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
|
|
pbr::{
|
|
ExtendedMaterial, MaterialExtension, OpaqueRendererMethod,
|
|
ScreenSpaceAmbientOcclusionBundle, ScreenSpaceAmbientOcclusionSettings,
|
|
},
|
|
render::{
|
|
render_resource::{AsBindGroup, ShaderRef, TextureViewDescriptor, TextureViewDimension},
|
|
view::ColorGrading,
|
|
},
|
|
window::PrimaryWindow,
|
|
};
|
|
use tweaks::*;
|
|
|
|
pub(crate) struct Display3dPlugin;
|
|
|
|
impl Plugin for Display3dPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_plugins((
|
|
TemporalAntiAliasPlugin,
|
|
MaterialPlugin::<DissolveMaterial>::default(),
|
|
))
|
|
.insert_resource(Msaa::Off)
|
|
.add_systems(
|
|
OnExit(GameState::Loading),
|
|
(
|
|
initialize,
|
|
fix_skybox.before(initialize),
|
|
update_tweaks.run_if(resource_exists::<tweak::GameTweaks>()),
|
|
),
|
|
)
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
load_assets
|
|
.run_if(in_state(GameState::Loading))
|
|
.run_if(on_event::<AssetEvent<Tweaks>>()),
|
|
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_valid_move_model.run_if(any_component_added::<game::ValidMove>),
|
|
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>)
|
|
.run_if(resource_exists::<tweak::GameTweaks>()),
|
|
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>()),
|
|
update_tweaks
|
|
.run_if(on_event::<AssetEvent<Tweaks>>())
|
|
.run_if(resource_exists::<tweak::GameTweaks>()),
|
|
scale_lighting.run_if(
|
|
any_component_added::<DirectionalLight>
|
|
.or_else(any_component_added::<SpotLight>)
|
|
.or_else(any_component_added::<PointLight>)
|
|
.or_else(on_event::<AssetEvent<Tweaks>>()),
|
|
),
|
|
capture_piece.run_if(any_with_component::<game::Captured>()),
|
|
),
|
|
)
|
|
.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.run_if(resource_exists::<tweak::GameTweaks>()),
|
|
opening_animation
|
|
.run_if(run_once())
|
|
.run_if(in_state(GameState::Play)),
|
|
),
|
|
)
|
|
.add_systems(OnExit(DisplayState::Display3d), deactivate::<Display3d>)
|
|
.add_systems(
|
|
OnEnter(GameState::Play),
|
|
(
|
|
activate::<Display3d>.run_if(in_state(DisplayState::Display3d)),
|
|
set_piece_texture,
|
|
update_tweaks.run_if(resource_exists::<tweak::GameTweaks>()),
|
|
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 {
|
|
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(
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
) {
|
|
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()
|
|
});
|
|
commands.insert_resource(AssetsMap {
|
|
hitbox_shape,
|
|
hitbox_material,
|
|
});
|
|
}
|
|
|
|
/// 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)| {
|
|
let side = Board::side(index).expect("Spawn valid side");
|
|
|
|
parent.spawn((
|
|
Display3d,
|
|
index,
|
|
tile,
|
|
PbrBundle {
|
|
mesh: assets.hitbox_shape.clone(),
|
|
material: assets.hitbox_material.clone(),
|
|
visibility: Visibility::Hidden,
|
|
..default()
|
|
},
|
|
side,
|
|
game::Selectable,
|
|
));
|
|
});
|
|
|
|
// Valid move indicators
|
|
game::tiles().for_each(|(index, _)| {
|
|
let side = Board::side(index).expect("Spawn valid side");
|
|
|
|
parent.spawn((
|
|
Display3d,
|
|
index,
|
|
SceneBundle {
|
|
visibility: Visibility::Hidden,
|
|
transform: Transform::from_translation(board_translation(&index)),
|
|
..default()
|
|
},
|
|
side,
|
|
game::ValidMove,
|
|
));
|
|
});
|
|
|
|
// 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>>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
state: Res<State<game::TurnState>>,
|
|
mut players: Query<&mut AnimationPlayer>,
|
|
mut commands: Commands,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
events
|
|
.iter()
|
|
.filter(|(name, _)| name.as_str() == "GameCam")
|
|
.for_each(|(_, entity)| {
|
|
info!("Initialize 3d camera");
|
|
let skybox_handle = tweak
|
|
.get_handle::<Image>("display3d_models_skybox_file")
|
|
.unwrap();
|
|
info!("Hydrating camera {:?}", entity);
|
|
// Populate the components for the camera
|
|
commands.entity(entity).insert((
|
|
Display3d,
|
|
DisplayState::Display3d,
|
|
Camera3dBundle {
|
|
camera: Camera {
|
|
is_active: true,
|
|
hdr: tweak.get::<bool>("display3d_hdr").unwrap(),
|
|
..default()
|
|
},
|
|
dither: DebandDither::Enabled,
|
|
color_grading: ColorGrading { ..default() },
|
|
..default()
|
|
},
|
|
Skybox(skybox_handle.clone()),
|
|
EnvironmentMapLight {
|
|
diffuse_map: skybox_handle.clone(),
|
|
specular_map: skybox_handle.clone(),
|
|
},
|
|
UiCameraConfig { show_ui: true },
|
|
FogSettings { ..default() },
|
|
ScreenSpaceAmbientOcclusionBundle { ..default() },
|
|
TemporalAntiAliasSettings { ..default() },
|
|
MotionVectorPrepass { ..default() },
|
|
// Name::new("3D Camera"),
|
|
));
|
|
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).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(game::Side::A) => gltf.named_animations.get(
|
|
tweak
|
|
.get::<String>("display3d_models_animations_intro_a")
|
|
.unwrap()
|
|
.as_str(),
|
|
),
|
|
game::TurnState(game::Side::B) => gltf.named_animations.get(
|
|
tweak
|
|
.get::<String>("display3d_models_animations_intro_b")
|
|
.unwrap()
|
|
.as_str(),
|
|
),
|
|
}
|
|
.expect("Camera Startup");
|
|
player.play(animation.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_file: Res<tweak::GameTweaks>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
// mut lights: Query<
|
|
// (
|
|
// Option<&mut SpotLight>,
|
|
// Option<&mut PointLight>,
|
|
// Option<&mut DirectionalLight>,
|
|
// ),
|
|
// Or<(With<SpotLight>, With<PointLight>, With<DirectionalLight>)>,
|
|
// >,
|
|
mut commands: Commands,
|
|
) {
|
|
if let Some(tweak) = tweaks.get(tweaks_file.handle.clone()) {
|
|
camera_settings.iter_mut().for_each(
|
|
|(entity, mut fog, mut color_grading, mut tonemapping)| {
|
|
*fog = tweak
|
|
.get::<TweakFogSettings>("display3d_fog")
|
|
.unwrap()
|
|
.into();
|
|
*color_grading = tweak
|
|
.get::<TweakColorGrading>("display3d_color_grading")
|
|
.unwrap()
|
|
.into();
|
|
*tonemapping = tweak
|
|
.get::<TweakTonemapping>("display3d_color_tonemapping")
|
|
.unwrap()
|
|
.into();
|
|
|
|
let quality_level = tweak.get::<TweakScreenSpaceAmbientOcclusionQualityLevel>(
|
|
"display3d_ssao_quality_level",
|
|
);
|
|
match quality_level {
|
|
Some(quality_level) => {
|
|
commands
|
|
.entity(entity)
|
|
.insert(ScreenSpaceAmbientOcclusionSettings {
|
|
quality_level: quality_level.into(),
|
|
});
|
|
commands.insert_resource(Msaa::Off);
|
|
}
|
|
None => {
|
|
commands
|
|
.entity(entity)
|
|
.remove::<ScreenSpaceAmbientOcclusionSettings>();
|
|
let msaa: Msaa = tweak.get::<TweakMsaa>("display3d_msaa").unwrap().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>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
) {
|
|
let tweak = tweaks.get(tweaks_file.handle.clone()).unwrap();
|
|
let handle = tweak
|
|
.get_handle_unchecked::<Image>("display3d_models_skybox_file")
|
|
.unwrap();
|
|
let image = images.get_mut(handle).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>)>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
events.iter_mut().for_each(|(mut handle, piece)| {
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
*handle = match piece {
|
|
game::Piece::Pawn => gltf.named_scenes.get(
|
|
tweak
|
|
.get::<String>("display3d_models_scenes_pawn")
|
|
.unwrap()
|
|
.as_str(),
|
|
),
|
|
game::Piece::Drone => gltf.named_scenes.get(
|
|
tweak
|
|
.get::<String>("display3d_models_scenes_drone")
|
|
.unwrap()
|
|
.as_str(),
|
|
),
|
|
game::Piece::Queen => gltf.named_scenes.get(
|
|
tweak
|
|
.get::<String>("display3d_models_scenes_queen")
|
|
.unwrap()
|
|
.as_str(),
|
|
),
|
|
}
|
|
.expect("Game board model")
|
|
.clone();
|
|
});
|
|
}
|
|
|
|
fn set_board_model(
|
|
mut boards: Query<
|
|
&mut Handle<Scene>,
|
|
(
|
|
With<game::BoardComponent>,
|
|
Without<TilesComponent>,
|
|
With<Display3d>,
|
|
),
|
|
>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
boards.iter_mut().for_each(|mut handle| {
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
*handle = gltf
|
|
.named_scenes
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_scenes_board")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Game board 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 capture_translation(side: &Side, num: usize) -> Vec3 {
|
|
info!("Side: {:?} Num: {:?}", side, num);
|
|
let x = 5.0 - ((num % 4) as f32 * 1.3);
|
|
let y = -1.3;
|
|
let z = 4.0 + ((num / 4) as f32 * 1.3);
|
|
match side {
|
|
Side::B => Vec3::new(-x, y, z),
|
|
Side::A => Vec3::new(x, y, -z),
|
|
}
|
|
}
|
|
|
|
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>>,
|
|
children: Query<&Children>,
|
|
mut models: Query<(&Name, &mut Handle<StandardMaterial>)>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
events.iter().for_each(|(entity, piece, side)| {
|
|
debug!("Checking piece texture for {:?}", entity);
|
|
let tweak = tweaks.get(tweaks_file.handle.clone()).unwrap();
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
if let Some(gltf) = gltfs.get(assets_handle) {
|
|
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
|
|
.get::<String>("display3d_models_materials_queen_red")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Red Queen texture")
|
|
.clone()
|
|
}
|
|
(Piece::Queen, Side::A, "Queen.1") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_dots_red")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Red Dots texture")
|
|
.clone()
|
|
}
|
|
(Piece::Queen, Side::B, "Queen.0") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_queen_blue")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Blue Queen texture")
|
|
.clone()
|
|
}
|
|
(Piece::Queen, Side::B, "Queen.1") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_dots_blue")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Red Dots texture")
|
|
.clone()
|
|
}
|
|
(Piece::Drone, Side::A, "Drone.0") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_drone_red")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Red Drone texture")
|
|
.clone()
|
|
}
|
|
(Piece::Drone, Side::A, "Drone.1") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_dots_red")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Red Dots texture")
|
|
.clone()
|
|
}
|
|
(Piece::Drone, Side::B, "Drone.0") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_drone_blue")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Blue Drone texture")
|
|
.clone()
|
|
}
|
|
(Piece::Drone, Side::B, "Drone.1") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_dots_blue")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Blue Dots texture")
|
|
.clone()
|
|
}
|
|
(Piece::Pawn, Side::A, "Pawn.0") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_pawn_red")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Red Pawn texture")
|
|
.clone()
|
|
}
|
|
(Piece::Pawn, Side::A, "Pawn.1") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_dots_red")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Red Dots texture")
|
|
.clone()
|
|
}
|
|
(Piece::Pawn, Side::B, "Pawn.0") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_pawn_blue")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Load Blue Pawn texture")
|
|
.clone()
|
|
}
|
|
(Piece::Pawn, Side::B, "Pawn.1") => {
|
|
*m = gltf
|
|
.named_materials
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_materials_dots_blue")
|
|
.unwrap()
|
|
.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, &Side), (With<game::Selectable>, With<Display3d>)>,
|
|
selected: Query<Entity, With<game::Selected>>,
|
|
children: Query<&Children>,
|
|
mut selections: EventWriter<game::Selection>,
|
|
state: Res<State<game::TurnState>>,
|
|
) {
|
|
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, &side)| {
|
|
// Check the side of the selection if no piece is selected
|
|
// Otherwise this is fine, select away
|
|
let side_check =
|
|
!selected.is_empty() || state.get().0 == side;
|
|
|
|
let hit_check = {
|
|
// This entity was hit (tile hitboxes)
|
|
let primary = entity == e;
|
|
|
|
// A child was hit (pieces)
|
|
let secondary = children
|
|
.iter_descendants(e)
|
|
.any(|child| child == entity);
|
|
|
|
primary || secondary
|
|
};
|
|
|
|
(side_check && hit_check).then_some(board_index)
|
|
})
|
|
.iter()
|
|
.for_each(|&board_index| {
|
|
info!("Board index selected: {:?}", 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))
|
|
});
|
|
}
|
|
|
|
fn set_valid_move_model(
|
|
mut events: Query<
|
|
(&mut Handle<Scene>, &mut Visibility),
|
|
(With<Display3d>, Added<game::ValidMove>),
|
|
>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
if let Some(gltf) = gltfs.get(assets_handle) {
|
|
events.iter_mut().for_each(|(mut handle, mut visibility)| {
|
|
*handle = gltf
|
|
.named_scenes
|
|
.get(
|
|
tweak
|
|
.get::<String>("display3d_models_scenes_valid_move")
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.unwrap()
|
|
.clone();
|
|
|
|
*visibility = Visibility::Hidden;
|
|
})
|
|
}
|
|
}
|
|
|
|
fn _play_valid_move_animation(_players: Query<&AnimationPlayer>) {
|
|
todo!();
|
|
}
|
|
|
|
fn pick_up(
|
|
mut events: Query<
|
|
(Entity, &game::Piece),
|
|
(With<game::Piece>, With<Display3d>, Added<game::Selected>),
|
|
>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
children: Query<&Children>,
|
|
mut players: Query<(&Name, &mut AnimationPlayer)>,
|
|
clips: Res<Assets<AnimationClip>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
events.iter_mut().for_each(|(entity, piece)| {
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
info!("Pickup animation for {:?}", entity);
|
|
children.iter_descendants(entity).for_each(|child| {
|
|
info!(" Child: {:?}", child);
|
|
if let Ok((name, mut player)) = players.get_mut(child) {
|
|
info!("Picking up {:?} ({:?}) {:?}", name, entity, piece);
|
|
let pickup_animation =
|
|
format!("display3d_models_animations_pick_up_{:?}", piece).to_ascii_lowercase();
|
|
let pickup_handle = gltf
|
|
.named_animations
|
|
.get(
|
|
tweak
|
|
.get::<String>(pickup_animation.as_str())
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Pickup Animation");
|
|
let idle_animation =
|
|
format!("display3d_models_animations_idle_{:?}", piece).to_ascii_lowercase();
|
|
let idle_handle = gltf
|
|
.named_animations
|
|
.get(
|
|
tweak
|
|
.get::<String>(idle_animation.as_str())
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("Idle animation");
|
|
if let Some(pickup_clip) = clips.get(pickup_handle) {
|
|
if let Some(idle_clip) = clips.get(idle_handle) {
|
|
if pickup_clip.compatible_with(name) && idle_clip.compatible_with(name) {
|
|
player
|
|
.start_with_transition(
|
|
pickup_handle.clone(),
|
|
Duration::from_secs_f32(0.75),
|
|
)
|
|
.start_with_transition(
|
|
idle_handle.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>)>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
children: Query<&Children>,
|
|
mut players: Query<(&Name, &mut AnimationPlayer)>,
|
|
clips: Res<Assets<AnimationClip>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(&tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
events.read().for_each(|entity| {
|
|
if let Ok(piece) = query.get_mut(entity) {
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
children.iter_descendants(entity).for_each(|child| {
|
|
if let Ok((name, mut player)) = players.get_mut(child) {
|
|
info!("Putting down {:?}", entity);
|
|
let putdown_animation =
|
|
format!("display3d_models_animations_put_down_{:?}", piece)
|
|
.to_ascii_lowercase();
|
|
let putdown_handle = gltf
|
|
.named_animations
|
|
.get(
|
|
tweak
|
|
.get::<String>(putdown_animation.as_str())
|
|
.unwrap()
|
|
.as_str(),
|
|
)
|
|
.expect("PutDown Animation");
|
|
if let Some(putdown_clip) = clips.get(putdown_handle) {
|
|
if putdown_clip.compatible_with(name) {
|
|
player
|
|
.start_with_transition(
|
|
putdown_handle.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>)>,
|
|
gltfs: Res<Assets<Gltf>>,
|
|
state: Res<State<game::TurnState>>,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
let assets_handle = tweak
|
|
.get_handle::<Gltf>("display3d_models_assets_file")
|
|
.unwrap();
|
|
let gltf = gltfs.get(assets_handle).expect("Load GLTF content");
|
|
players.iter_mut().for_each(|mut player| {
|
|
let animation = match state.get() {
|
|
game::TurnState(game::Side::A) => gltf.named_animations.get(
|
|
tweak
|
|
.get::<String>("display3d_models_animations_turn_a")
|
|
.unwrap()
|
|
.as_str(),
|
|
),
|
|
game::TurnState(game::Side::B) => gltf.named_animations.get(
|
|
tweak
|
|
.get::<String>("display3d_models_animations_turn_b")
|
|
.unwrap()
|
|
.as_str(),
|
|
),
|
|
};
|
|
player.start_with_transition(
|
|
animation.expect("Camera Transition Animation").clone(),
|
|
Duration::from_secs_f32(1.00),
|
|
);
|
|
});
|
|
}
|
|
|
|
fn scale_lighting(
|
|
mut directional: Query<(
|
|
Entity,
|
|
&mut DirectionalLight,
|
|
Option<&Original<DirectionalLight>>,
|
|
)>,
|
|
mut spot: Query<(Entity, &mut SpotLight, Option<&Original<SpotLight>>)>,
|
|
mut point: Query<(Entity, &mut PointLight, Option<&Original<PointLight>>)>,
|
|
mut commands: Commands,
|
|
tweaks: Res<Assets<Tweaks>>,
|
|
tweaks_file: Res<tweak::GameTweaks>,
|
|
) {
|
|
let tweak = tweaks
|
|
.get(tweaks_file.handle.clone())
|
|
.expect("Load tweakfile");
|
|
|
|
let directional_tweak = tweak
|
|
.get::<f32>("display3d_lights_scaling_directional")
|
|
.expect("Directional lighting scalar");
|
|
directional
|
|
.iter_mut()
|
|
.for_each(|(entity, mut val, original)| {
|
|
debug!("Scaling directional light {:?}", entity);
|
|
if let Some(Original(v)) = original {
|
|
val.illuminance = v.illuminance * directional_tweak;
|
|
} else {
|
|
commands.entity(entity).insert(Original(val.clone()));
|
|
val.illuminance *= directional_tweak;
|
|
}
|
|
});
|
|
|
|
let spot_tweak = tweak
|
|
.get::<f32>("display3d_lights_scaling_spot")
|
|
.expect("Spot lighting scalar");
|
|
spot.iter_mut().for_each(|(entity, mut val, original)| {
|
|
debug!("Scaling spot light {:?}", entity);
|
|
if let Some(Original(v)) = original {
|
|
val.intensity = v.intensity * spot_tweak;
|
|
} else {
|
|
commands.entity(entity).insert(Original(val.clone()));
|
|
val.intensity *= spot_tweak;
|
|
}
|
|
});
|
|
|
|
let point_tweak = tweak
|
|
.get::<f32>("display3d_lights_scaling_point")
|
|
.expect("Point lighting scalar");
|
|
point.iter_mut().for_each(|(entity, mut val, original)| {
|
|
debug!("Scaling point light {:?}", entity);
|
|
if let Some(Original(v)) = original {
|
|
val.intensity = v.intensity * point_tweak;
|
|
} else {
|
|
commands.entity(entity).insert(Original(val.clone()));
|
|
val.intensity *= point_tweak;
|
|
}
|
|
});
|
|
}
|
|
|
|
pub(super) mod tweaks {
|
|
use bevy::{
|
|
core_pipeline::tonemapping::Tonemapping,
|
|
math::Vec3,
|
|
pbr::{FogFalloff, FogSettings, ScreenSpaceAmbientOcclusionQualityLevel},
|
|
render::{
|
|
color::Color,
|
|
view::{ColorGrading, Msaa},
|
|
},
|
|
};
|
|
use serde::Deserialize;
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub enum TweakTonemapping {
|
|
None,
|
|
Reinhard,
|
|
ReinhardLuminance,
|
|
AcesFitted,
|
|
AgX,
|
|
SomewhatBoringDisplayTransform,
|
|
TonyMcMapface,
|
|
BlenderFilmic,
|
|
}
|
|
|
|
impl From<TweakTonemapping> for Tonemapping {
|
|
fn from(src: TweakTonemapping) -> Tonemapping {
|
|
match src {
|
|
TweakTonemapping::None => Tonemapping::None,
|
|
TweakTonemapping::Reinhard => Tonemapping::Reinhard,
|
|
TweakTonemapping::ReinhardLuminance => Tonemapping::ReinhardLuminance,
|
|
TweakTonemapping::AcesFitted => Tonemapping::AcesFitted,
|
|
TweakTonemapping::AgX => Tonemapping::AgX,
|
|
TweakTonemapping::SomewhatBoringDisplayTransform => {
|
|
Tonemapping::SomewhatBoringDisplayTransform
|
|
}
|
|
TweakTonemapping::TonyMcMapface => Tonemapping::TonyMcMapface,
|
|
TweakTonemapping::BlenderFilmic => Tonemapping::BlenderFilmic,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub enum TweakMsaa {
|
|
Off,
|
|
Sample2,
|
|
Sample4,
|
|
Sample8,
|
|
}
|
|
|
|
impl From<TweakMsaa> for Msaa {
|
|
fn from(src: TweakMsaa) -> Msaa {
|
|
match src {
|
|
TweakMsaa::Off => Msaa::Off,
|
|
TweakMsaa::Sample2 => Msaa::Sample2,
|
|
TweakMsaa::Sample4 => Msaa::Sample4,
|
|
TweakMsaa::Sample8 => Msaa::Sample8,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
pub enum TweakScreenSpaceAmbientOcclusionQualityLevel {
|
|
Low,
|
|
Medium,
|
|
High,
|
|
Ultra,
|
|
}
|
|
|
|
impl From<TweakScreenSpaceAmbientOcclusionQualityLevel>
|
|
for ScreenSpaceAmbientOcclusionQualityLevel
|
|
{
|
|
fn from(
|
|
src: TweakScreenSpaceAmbientOcclusionQualityLevel,
|
|
) -> ScreenSpaceAmbientOcclusionQualityLevel {
|
|
match src {
|
|
TweakScreenSpaceAmbientOcclusionQualityLevel::Low => {
|
|
ScreenSpaceAmbientOcclusionQualityLevel::Low
|
|
}
|
|
TweakScreenSpaceAmbientOcclusionQualityLevel::Medium => {
|
|
ScreenSpaceAmbientOcclusionQualityLevel::Medium
|
|
}
|
|
TweakScreenSpaceAmbientOcclusionQualityLevel::High => {
|
|
ScreenSpaceAmbientOcclusionQualityLevel::High
|
|
}
|
|
TweakScreenSpaceAmbientOcclusionQualityLevel::Ultra => {
|
|
ScreenSpaceAmbientOcclusionQualityLevel::Ultra
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct TweakFogSettings {
|
|
pub color: Color,
|
|
pub directional_light_color: Color,
|
|
pub directional_light_exponent: f32,
|
|
pub falloff: TweakFogFalloff,
|
|
}
|
|
|
|
impl From<TweakFogSettings> for FogSettings {
|
|
fn from(src: TweakFogSettings) -> FogSettings {
|
|
FogSettings {
|
|
color: src.color,
|
|
directional_light_color: src.directional_light_color,
|
|
directional_light_exponent: src.directional_light_exponent,
|
|
falloff: src.falloff.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub enum TweakFogFalloff {
|
|
Linear {
|
|
start: f32,
|
|
end: f32,
|
|
},
|
|
Exponential {
|
|
density: f32,
|
|
},
|
|
ExponentialSquared {
|
|
density: f32,
|
|
},
|
|
Atmospheric {
|
|
extinction: Vec3,
|
|
inscattering: Vec3,
|
|
},
|
|
}
|
|
|
|
impl From<TweakFogFalloff> for FogFalloff {
|
|
fn from(src: TweakFogFalloff) -> FogFalloff {
|
|
match src {
|
|
TweakFogFalloff::Linear { start, end } => FogFalloff::Linear { start, end },
|
|
TweakFogFalloff::Exponential { density } => FogFalloff::Exponential { density },
|
|
TweakFogFalloff::ExponentialSquared { density } => {
|
|
FogFalloff::ExponentialSquared { density }
|
|
}
|
|
TweakFogFalloff::Atmospheric {
|
|
extinction,
|
|
inscattering,
|
|
} => FogFalloff::Atmospheric {
|
|
extinction,
|
|
inscattering,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct TweakColorGrading {
|
|
pub exposure: f32,
|
|
pub gamma: f32,
|
|
pub pre_saturation: f32,
|
|
pub post_saturation: f32,
|
|
}
|
|
|
|
impl From<TweakColorGrading> for ColorGrading {
|
|
fn from(src: TweakColorGrading) -> ColorGrading {
|
|
ColorGrading {
|
|
exposure: src.exposure,
|
|
gamma: src.gamma,
|
|
pre_saturation: src.pre_saturation,
|
|
post_saturation: src.post_saturation,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Type expressing the extended material of standardMaterial + dissolveMaterial
|
|
type DissolveMaterial = ExtendedMaterial<StandardMaterial, DissolveExtension>;
|
|
|
|
/// Material extension for dissolving effect
|
|
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
|
|
struct DissolveExtension {
|
|
#[uniform(100)]
|
|
percentage: f32,
|
|
}
|
|
|
|
impl MaterialExtension for DissolveExtension {
|
|
fn fragment_shader() -> ShaderRef {
|
|
"shaders/dissolve.wgsl".into()
|
|
}
|
|
}
|
|
|
|
// Component for 'backing up' components which are temporarily not used
|
|
#[derive(Debug, Component, Clone)]
|
|
struct Backup<T: Component>(T);
|
|
|
|
/// When a piece is captured...
|
|
/// 1. Play a cool "captured" animation and a neat sound
|
|
/// 2. Move the piece to the side of the board
|
|
/// 3. Play the same "captured" animation in reverse
|
|
/// The animation is like a 'beam me up scotty' sorta thing.
|
|
fn capture_piece(
|
|
events: Query<Entity, (With<Display3d>, Added<game::Captured>)>,
|
|
mut query: Query<
|
|
(&mut Visibility, &mut Transform, &Side),
|
|
(With<Display3d>, With<game::Captured>),
|
|
>,
|
|
mut state: Local<Option<game::CaptureFlow>>,
|
|
standard_materials: ResMut<Assets<StandardMaterial>>,
|
|
mut dissolve_materials: ResMut<Assets<DissolveMaterial>>,
|
|
object_standard_materials: Query<&Handle<StandardMaterial>>,
|
|
object_dissolve_materials: Query<&Handle<DissolveMaterial>>,
|
|
backup_material: Query<&Backup<Handle<StandardMaterial>>>,
|
|
children: Query<&Children>,
|
|
mut commands: Commands,
|
|
time: Res<Time>,
|
|
score: Res<game::Score>,
|
|
) {
|
|
let duration: f32 = 3.0;
|
|
|
|
match *state {
|
|
Some(s) => {
|
|
match s {
|
|
game::CaptureFlow::FadeOut(entity) => {
|
|
// TODO: Do we need this?
|
|
let (_, _, _) = query
|
|
.get_mut(entity)
|
|
.expect("Visibility and Transform of captured piece");
|
|
|
|
// Play fade-out animation
|
|
{
|
|
object_dissolve_materials.iter().for_each(|handle| {
|
|
let extended_material = dissolve_materials
|
|
.get_mut(handle)
|
|
.expect("Get the dissolve material");
|
|
|
|
// Calculate how much of the animation has passed
|
|
let delta = time.delta_seconds() / duration;
|
|
|
|
// Change the material's value to create animation
|
|
extended_material.extension.percentage -= delta; // TODO: Tweak this timing
|
|
|
|
debug!(
|
|
"Play fade out animation {:?} {:?}",
|
|
delta, extended_material.extension.percentage
|
|
);
|
|
|
|
if extended_material.extension.percentage <= 0.0 {
|
|
// Set to exactly 0 for simplicity
|
|
extended_material.extension.percentage = 0.0;
|
|
|
|
// Move to next state now that animation is done
|
|
*state = s.next();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
game::CaptureFlow::Store(entity) => {
|
|
let (mut v, mut t, side) = query
|
|
.get_mut(entity)
|
|
.expect("Visibility and Transform of captured piece");
|
|
|
|
// Hide piece now that animation is done
|
|
*v = Visibility::Hidden;
|
|
|
|
t.translation = capture_translation(side, score.get(*side));
|
|
|
|
*state = s.next();
|
|
}
|
|
game::CaptureFlow::FadeIn(entity) => {
|
|
let (mut v, _, _) = query
|
|
.get_mut(entity)
|
|
.expect("Visibility and Transform of captured piece");
|
|
|
|
// Show piece now that it is moved
|
|
*v = Visibility::Inherited;
|
|
|
|
// Play fade-in animation
|
|
{
|
|
object_dissolve_materials.iter().for_each(|handle| {
|
|
let extended_material = dissolve_materials
|
|
.get_mut(handle)
|
|
.expect("Get the dissolve material");
|
|
|
|
// Calculate how much of the animation has passed
|
|
let delta = time.delta_seconds() / duration;
|
|
|
|
// Change the material's value to create animation
|
|
extended_material.extension.percentage += delta; // TODO: Tweak this timing
|
|
|
|
debug!(
|
|
"Play fade in animation {:?} {:?}",
|
|
delta, extended_material.extension.percentage
|
|
);
|
|
|
|
if extended_material.extension.percentage >= 1.0 {
|
|
// Move to next state now that animation is done
|
|
*state = s.next();
|
|
|
|
// Remove the captured component for bookkeeping
|
|
commands.entity(entity).remove::<game::Captured>();
|
|
|
|
// Remove the dissolve material
|
|
commands.entity(entity).remove::<Handle<DissolveMaterial>>();
|
|
|
|
// Re-add the original material
|
|
if let Ok(Backup(orig)) = backup_material.get(entity) {
|
|
commands.entity(entity).insert(orig.clone());
|
|
commands
|
|
.entity(entity)
|
|
.remove::<Backup<Handle<StandardMaterial>>>();
|
|
} else {
|
|
warn!("Entity {:?} does not have original material", entity)
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
*state = events.iter().next().map(|entity| {
|
|
children
|
|
.iter_descendants(entity)
|
|
.filter_map(|e| object_standard_materials.get(e).ok().map(|h| (e, h)))
|
|
.for_each(|(child, handle)| {
|
|
// Extension we will add to existing gltf-sourced materials
|
|
let extension = DissolveExtension { percentage: 1.0 };
|
|
// Base material we will extend for the duration of the dissolve effect
|
|
let mut base = standard_materials
|
|
.get(handle)
|
|
.expect("Resolve material data")
|
|
.clone();
|
|
|
|
base.opaque_render_method = OpaqueRendererMethod::Auto;
|
|
base.alpha_mode = AlphaMode::Mask(0.5);
|
|
|
|
commands
|
|
.entity(child)
|
|
.insert(dissolve_materials.add(ExtendedMaterial { base, extension }))
|
|
.insert(Backup(handle.clone()))
|
|
.remove::<Handle<StandardMaterial>>();
|
|
});
|
|
|
|
// Set the next state to start fading out
|
|
game::CaptureFlow::FadeOut(entity)
|
|
});
|
|
}
|
|
}
|
|
}
|