diff --git a/src/.display3d.rs.rustfmt b/src/.display3d.rs.rustfmt deleted file mode 100644 index 351b848..0000000 --- a/src/.display3d.rs.rustfmt +++ /dev/null @@ -1,609 +0,0 @@ -// TODO: Camera Animations -// Intro animation -// Turn changing animations -// TODO: Pickup/put-down sound in 3d -// TODO: Background during menu - -use crate::{ - game::{Board, BoardIndex, Piece, Side}, - prelude::*, -}; -use bevy::{ - core_pipeline::{tonemapping::DebandDither, Skybox}, - input::mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel}, - render::{ - render_resource::{TextureViewDescriptor, TextureViewDimension}, - view::ColorGrading, - }, - window::PrimaryWindow, -}; - -pub(crate) struct Display3dPlugin; - -impl Plugin for Display3dPlugin { - fn build(&self, app: &mut App) { - app.add_systems(OnEnter(GameState::Loading), load_assets) - .add_systems( - OnExit(GameState::Loading), - (initialize, fix_skybox.before(initialize)), - ) - .add_systems( - Update, - ( - hydrate_camera.run_if(any_component_added::), - set_piece_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::()), - ), - ) - .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), - ) - .add_systems(OnExit(DisplayState::Display3d), deactivate::); - } -} - -#[derive(Debug, Component)] -pub(crate) struct Display3d; - -#[derive(Debug, Resource)] -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>, -) { - 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 { - models: server.load("models/Martian Chess.glb"), - skybox: server.load("images/skybox.png"), - hitbox_shape, - hitbox_material, - }); -} - -/// 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!("Initializing 3D lights!"); - parent.spawn(( - Display3d, - PointLightBundle { - point_light: PointLight { - intensity: 3000.0, - shadows_enabled: true, - ..default() - }, - transform: Transform::from_xyz(0.0, 10.0, 5.0), - ..default() - }, - )); - - info!("Intializing 3D Board!"); - parent - .spawn((Display3d, game::BoardComponent, SceneBundle { ..default() })) - .with_children(|parent| { - // 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, - mut commands: Commands, -) { - events - .iter() - .filter(|(name, _)| name.as_str() == "GameCam") - .for_each(|(_, entity)| { - info!("Initialize 3d camera"); - commands.entity(entity).insert(( - Display3d, - Camera3dBundle { - camera: Camera { - is_active: false, - hdr: true, - ..default() - }, - transform: Transform::from_xyz(0.0, 12.5, 6.0).looking_at(Vec3::ZERO, Vec3::Y), - 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 { - color: Color::WHITE, - falloff: FogFalloff::from_visibility_colors(100.0, Color::NONE, Color::NONE), - ..default() - }, - )); - }); -} - -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>, -) { - 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("Pawn"), - game::Piece::Drone => gltf.named_scenes.get("Drone"), - game::Piece::Queen => gltf.named_scenes.get("Queen"), - } - .expect("Game board model") - .clone(); - }); -} - -fn set_board_model( - mut events: Query<&mut Handle, (With, With)>, - assets_map: Res, - gltfs: Res>, -) { - events.iter_mut().for_each(|mut handle| { - let gltf = gltfs.get(&assets_map.models).expect("Load GLTF content"); - *handle = gltf - .named_scenes - .get("Gameboard") - .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, 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.325 - } else { - x as f32 * 1.3 + 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.iter().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.iter().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, Changed)>, - all: Query<(Entity, &Piece, &Side), (With, With)>, - gltfs: Res>, - assets_map: Res, - children: Query<&Children>, - mut models: Query<(&Name, &mut Handle)>, -) { - let pieces = if events.is_empty() { - all.iter().collect::>() - } else { - events.iter().collect::>() - }; - pieces.iter().for_each(|(entity, piece, side)| { - 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) { - match (*piece, *side, n.as_str()) { - (Piece::Queen, Side::A, "Queen.0") => { - *m = gltf - .named_materials - .get("Queen") - .expect("Load Red Queen texture") - .clone() - } - (Piece::Queen, Side::A, "Queen.1") => { - *m = gltf - .named_materials - .get("Dots") - .expect("Load Red Dots texture") - .clone() - } - (Piece::Queen, Side::B, "Queen.0") => { - *m = gltf - .named_materials - .get("QueenBlue") - .expect("Load Blue Queen texture") - .clone() - } - (Piece::Queen, Side::B, "Queen.1") => { - *m = gltf - .named_materials - .get("DotsBlue") - .expect("Load Red Dots texture") - .clone() - } - (Piece::Drone, Side::A, "Drone.0") => { - *m = gltf - .named_materials - .get("Drone") - .expect("Load Red Drone texture") - .clone() - } - (Piece::Drone, Side::A, "Drone.1") => { - *m = gltf - .named_materials - .get("Dots") - .expect("Load Red Dots texture") - .clone() - } - (Piece::Drone, Side::B, "Drone.0") => { - *m = gltf - .named_materials - .get("DroneBlue") - .expect("Load Blue Drone texture") - .clone() - } - (Piece::Drone, Side::B, "Drone.1") => { - *m = gltf - .named_materials - .get("DotsBlue") - .expect("Load Blue Dots texture") - .clone() - } - (Piece::Pawn, Side::A, "Pawn.0") => { - *m = gltf - .named_materials - .get("Pawn") - .expect("Load Red Pawn texture") - .clone() - } - (Piece::Pawn, Side::A, "Pawn.1") => { - *m = gltf - .named_materials - .get("Dots") - .expect("Load Red Dots texture") - .clone() - } - (Piece::Pawn, Side::B, "Pawn.0") => { - *m = gltf - .named_materials - .get("DroneBlue") // TODO: FIX - .expect("Load Blue Pawn texture") - .clone() - } - (Piece::Pawn, Side::B, "Pawn.1") => { - *m = gltf - .named_materials - .get("DotsBlue") - .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, With)>, - children: Query<&Children>, - mut commands: Commands, - selected: Query< - Entity, - ( - With, - With, - With, - ), - >, -) { - events - .iter() - .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(|&e| { - // 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 - }) - .iter() - .for_each(|&e| { - commands.entity(e).insert(game::Selected); - }); - 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 - .possible_moves(*idx) - .iter() - .map(|i| Transform::from_translation(board_translation(i))) - .for_each(|t| gizmos.cuboid(t, Color::WHITE)) - }); -} - -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>, -) { - 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) { - let animation = match piece { - game::Piece::Queen => gltf.named_animations.get("QueenPickup"), - game::Piece::Drone => gltf.named_animations.get("DronePickup"), - game::Piece::Pawn => gltf.named_animations.get("PawnPickup"), - }; - let idle = match piece { - game::Piece::Queen => gltf.named_animations.get("QueenIdle"), - game::Piece::Drone => gltf.named_animations.get("DroneIdle"), - game::Piece::Pawn => gltf.named_animations.get("PawnIdle"), - }; - 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), - ) - .repeat(); - } - }) - }); -} - -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>, -) { - events.iter().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) { - let animation = match piece { - game::Piece::Queen => gltf.named_animations.get("QueenPutDown"), - game::Piece::Drone => gltf.named_animations.get("DronePutDown"), - game::Piece::Pawn => gltf.named_animations.get("PawnPutDown"), - }; - player - .start_with_transition( - animation.expect("PutDown Animation").clone(), - Duration::from_secs_f32(0.75), - ) - .stop_repeating(); - } - }) - } - }) -} - -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)); - }); -}