diff --git a/src/game/mod.rs b/src/game/mod.rs index e9a274c31..fa934ae00 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -1,11 +1,11 @@ use self::{ - camera::CameraPlugin, config::GameConfig, maploader::MapLoaderPlugin, + camera::CameraPlugin, config::GameConfig, maploader::MapLoaderPlugin, pointer::PointerPlugin, selection::SelectionPlugin, }; use crate::AppStates; use bevy::{ app::PluginGroupBuilder, - prelude::{App, Plugin, PluginGroup, ResMut, State, SystemSet}, + prelude::{App, Plugin, PluginGroup, ResMut, State, SystemLabel, SystemSet}, }; pub mod config; @@ -15,6 +15,7 @@ mod collisions; mod mapdescr; mod maploader; mod objects; +mod pointer; mod selection; mod terrain; @@ -26,10 +27,16 @@ impl PluginGroup for GamePluginGroup { .add(GamePlugin) .add(MapLoaderPlugin) .add(CameraPlugin) - .add(SelectionPlugin); + .add(SelectionPlugin) + .add(PointerPlugin); } } +#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, SystemLabel)] +enum Labels { + PreInputUpdate, +} + struct GamePlugin; impl Plugin for GamePlugin { diff --git a/src/game/pointer.rs b/src/game/pointer.rs new file mode 100644 index 000000000..985c388f5 --- /dev/null +++ b/src/game/pointer.rs @@ -0,0 +1,90 @@ +use super::{collisions::Intersector, objects::Playable, GameStates, Labels}; +use crate::math::ray::Ray; +use bevy::{ + ecs::system::SystemParam, + input::mouse::MouseMotion, + prelude::{ + App, Camera, Entity, EventReader, GlobalTransform, ParallelSystemDescriptorCoercion, + Plugin, Query, Res, ResMut, SystemSet, With, + }, + window::Windows, +}; +use glam::{Vec2, Vec3}; + +pub struct PointerPlugin; + +impl Plugin for PointerPlugin { + fn build(&self, app: &mut App) { + app.init_resource::().add_system_set( + SystemSet::on_update(GameStates::Playing) + .with_system(mouse_move_handler.label(Labels::PreInputUpdate)), + ); + } +} + +#[derive(Default)] +pub struct Pointer { + entity: Option, +} + +impl Pointer { + pub fn entity(&self) -> Option { + self.entity + } + + fn set_entity(&mut self, entity: Option) { + self.entity = entity; + } +} + +#[derive(SystemParam)] +struct MouseInWorld<'w, 's> { + windows: Res<'w, Windows>, + cameras: Query<'w, 's, (&'static GlobalTransform, &'static Camera)>, +} + +impl<'w, 's> MouseInWorld<'w, 's> { + fn mouse_ray(&self) -> Option { + let window = self.windows.get_primary().unwrap(); + + // Normalized to values between -1.0 to 1.0 with (0.0, 0.0) in the + // middle of the screen. + let cursor_position = match window.cursor_position() { + Some(position) => { + let screen_size = Vec2::new(window.width() as f32, window.height() as f32); + (position / screen_size) * 2.0 - Vec2::ONE + } + None => return None, + }; + + let (camera_transform, camera) = self.cameras.single(); + let camera_transform_mat = camera_transform.compute_matrix(); + let camera_projection = camera.projection_matrix; + + let screen_to_world = camera_transform_mat * camera_projection.inverse(); + let world_to_screen = camera_projection * camera_transform_mat; + + // Depth of camera near plane in screen coordinates. + let near_plane = world_to_screen.transform_point3(-Vec3::Z * camera.near).z; + let ray_origin = screen_to_world.transform_point3(cursor_position.extend(near_plane)); + let ray_direction = ray_origin - camera_transform.translation; + Some(Ray::new(ray_origin, ray_direction)) + } +} + +fn mouse_move_handler( + mut resource: ResMut, + event: EventReader, + mouse: MouseInWorld, + playable: Intersector>, +) { + if event.len() == 0 { + return; + } + + let entity = mouse + .mouse_ray() + .and_then(|ray| playable.ray_intersection(&ray)) + .map(|(entity, _)| entity); + resource.set_entity(entity); +} diff --git a/src/game/selection.rs b/src/game/selection.rs index 0dbfe1d54..8c98a5c51 100644 --- a/src/game/selection.rs +++ b/src/game/selection.rs @@ -1,15 +1,12 @@ -use super::{collisions::Intersector, objects::Playable, GameStates}; -use crate::math::ray::Ray; +use super::{pointer::Pointer, GameStates, Labels}; use bevy::{ ecs::system::SystemParam, input::{mouse::MouseButtonInput, ElementState, Input}, prelude::{ - App, Camera, Commands, Component, Entity, EventReader, GlobalTransform, KeyCode, - MouseButton, Plugin, Query, Res, SystemSet, With, + App, Commands, Component, Entity, EventReader, KeyCode, MouseButton, + ParallelSystemDescriptorCoercion, Plugin, Query, Res, SystemSet, With, }, - window::Windows, }; -use glam::{Vec2, Vec3}; use std::collections::HashSet; pub struct SelectionPlugin; @@ -17,7 +14,8 @@ pub struct SelectionPlugin; impl Plugin for SelectionPlugin { fn build(&self, app: &mut App) { app.add_system_set( - SystemSet::on_update(GameStates::Playing).with_system(mouse_click_handler), + SystemSet::on_update(GameStates::Playing) + .with_system(mouse_click_handler.after(Labels::PreInputUpdate)), ); } } @@ -25,41 +23,6 @@ impl Plugin for SelectionPlugin { #[derive(Component)] pub struct Selected; -#[derive(SystemParam)] -struct MouseInWorld<'w, 's> { - windows: Res<'w, Windows>, - cameras: Query<'w, 's, (&'static GlobalTransform, &'static Camera)>, -} - -impl<'w, 's> MouseInWorld<'w, 's> { - fn mouse_ray(&self) -> Option { - let window = self.windows.get_primary().unwrap(); - - // Normalized to values between -1.0 to 1.0 with (0.0, 0.0) in the - // middle of the screen. - let cursor_position = match window.cursor_position() { - Some(position) => { - let screen_size = Vec2::new(window.width() as f32, window.height() as f32); - (position / screen_size) * 2.0 - Vec2::ONE - } - None => return None, - }; - - let (camera_transform, camera) = self.cameras.single(); - let camera_transform_mat = camera_transform.compute_matrix(); - let camera_projection = camera.projection_matrix; - - let screen_to_world = camera_transform_mat * camera_projection.inverse(); - let world_to_screen = camera_projection * camera_transform_mat; - - // Depth of camera near plane in screen coordinates. - let near_plane = world_to_screen.transform_point3(-Vec3::Z * camera.near).z; - let ray_origin = screen_to_world.transform_point3(cursor_position.extend(near_plane)); - let ray_direction = ray_origin - camera_transform.translation; - Some(Ray::new(ray_origin, ray_direction)) - } -} - #[derive(Clone, Copy, PartialEq)] enum SelectionMode { Replace, @@ -99,8 +62,7 @@ impl<'w, 's> Selector<'w, 's> { fn mouse_click_handler( mut event: EventReader, keys: Res>, - playable: Intersector>, - mouse: MouseInWorld, + pointer: Res, mut selector: Selector, ) { if !event @@ -110,21 +72,11 @@ fn mouse_click_handler( return; } - let mouse_ray = match mouse.mouse_ray() { - Some(ray) => ray, - None => return, - }; - let mode = if keys.pressed(KeyCode::LControl) { SelectionMode::Add } else { SelectionMode::Replace }; - selector.select_single( - playable - .ray_intersection(&mouse_ray) - .map(|(entity, _)| entity), - mode, - ); + selector.select_single(pointer.entity(), mode); }