Skip to content

Commit

Permalink
Implement single object selection
Browse files Browse the repository at this point in the history
Relates to #10.
  • Loading branch information
Indy2222 committed Apr 4, 2022
1 parent 33fa095 commit bb3ce38
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ pub mod game;
pub mod map;
pub mod math;
pub mod object;
pub mod selection;
pub mod states;
pub mod terrain;
3 changes: 3 additions & 0 deletions src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ use bevy::prelude::Component;

#[derive(Component)]
pub struct SolidObject;

#[derive(Component)]
pub struct Playable;
110 changes: 110 additions & 0 deletions src/selection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use crate::{collisions::SolidObjects, math::ray::Ray, object::Playable, states::GameStates};
use bevy::{
ecs::system::SystemParam,
input::{mouse::MouseButtonInput, ElementState},
prelude::{
App, Camera, Commands, Component, Entity, EventReader, GlobalTransform, MouseButton,
Plugin, Query, Res, SystemSet, With,
},
window::Windows,
};
use glam::{Vec2, Vec3};
use std::collections::HashSet;

pub struct SelectionPlugin;

impl Plugin for SelectionPlugin {
fn build(&self, app: &mut App) {
app.add_system_set(SystemSet::on_update(GameStates::InGame).with_system(mouse_click_event));
}
}

#[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<Ray> {
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(SystemParam)]
struct Selector<'w, 's> {
commands: Commands<'w, 's>,
selected: Query<'w, 's, Entity, With<Selected>>,
}

impl<'w, 's> Selector<'w, 's> {
fn select_single(&mut self, entity: Option<Entity>) {
let entities = match entity {
Some(entity) => vec![entity],
None => Vec::new(),
};
self.select(&entities);
}

fn select(&mut self, entities: &[Entity]) {
let selected: HashSet<Entity> = self.selected.iter().collect();
let desired: HashSet<Entity> = entities.iter().cloned().collect();

for deselect in &selected - &desired {
self.commands.entity(deselect).remove::<Selected>();
}
for select in &desired - &selected {
self.commands.entity(select).insert(Selected);
}
}
}

fn mouse_click_event(
mut event: EventReader<MouseButtonInput>,
playable: SolidObjects<With<Playable>>,
mouse: MouseInWorld,
mut selector: Selector,
) {
if !event
.iter()
.any(|e| e.button == MouseButton::Left && e.state == ElementState::Pressed)
{
return;
}

let mouse_ray = match mouse.mouse_ray() {
Some(ray) => ray,
None => return,
};
selector.select_single(
playable
.ray_intersection(&mouse_ray)
.map(|(entity, _)| entity),
);
}
4 changes: 2 additions & 2 deletions src/states.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::camera::MainCameraPlugin;
use crate::map::plugin::MapPlugin;
use crate::{camera::MainCameraPlugin, selection::SelectionPlugin};
use bevy::{app::PluginGroupBuilder, prelude::PluginGroup};

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
Expand All @@ -20,6 +20,6 @@ pub struct InGamePluginGroup;

impl PluginGroup for InGamePluginGroup {
fn build(&mut self, group: &mut PluginGroupBuilder) {
group.add(MainCameraPlugin);
group.add(MainCameraPlugin).add(SelectionPlugin);
}
}

0 comments on commit bb3ce38

Please sign in to comment.