From 49c3a1517dc0a49a93f71a490cc2bca4767dfefc Mon Sep 17 00:00:00 2001 From: hodasemi Date: Sat, 13 May 2023 09:42:08 +0200 Subject: [PATCH] Draw dots on click --- Cargo.toml | 2 + src/global_state.rs | 8 +++ src/main.rs | 134 +++++++++++++++++++++++++++++++++++++++++++- src/polygon.rs | 63 +++++++++++++++++++++ 4 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/global_state.rs create mode 100644 src/polygon.rs diff --git a/Cargo.toml b/Cargo.toml index 8b46ab1..3fcf837 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bevy = "0.10.1" +bevy_egui = "0.20.3" diff --git a/src/global_state.rs b/src/global_state.rs new file mode 100644 index 0000000..da99ca2 --- /dev/null +++ b/src/global_state.rs @@ -0,0 +1,8 @@ +use bevy::prelude::*; + +#[derive(Resource, Default, Debug, PartialEq, Clone, Copy)] +pub struct GlobalState { + pub cursor_position: Vec2, + pub window_extent: Vec2, + pub polygon_started: bool, +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..c3fda01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,135 @@ +mod global_state; +mod polygon; + +///! This example illustrates how to resize windows, and how to respond to a window being resized. +use bevy::{ + input::{ + mouse::{MouseButtonInput, MouseWheel}, + ButtonState, + }, + prelude::*, + sprite::MaterialMesh2dBundle, + window::WindowResized, +}; + +use bevy_egui::{egui, EguiContexts, EguiPlugin}; +use global_state::GlobalState; +use polygon::ObjectInfos; + fn main() { - println!("Hello, world!"); + App::new() + .add_plugins(DefaultPlugins) + .add_plugin(EguiPlugin) + .insert_resource(GlobalState::default()) + .insert_resource(ObjectInfos::default()) + .add_startup_systems((setup_camera, setup)) + .add_systems(( + print_mouse_events_system, + update_ui_side_panel, + on_resize_system, + )) + .run(); +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.spawn(MaterialMesh2dBundle { + mesh: meshes.add(Mesh::from(shape::Quad::default())).into(), + transform: Transform::default() + .with_scale(Vec3::splat(4.)) + .with_translation(Vec3::new(100.0, 10.0, 0.0)), + material: materials.add(ColorMaterial::from(Color::YELLOW)), + ..default() + }); +} + +// Spawns the camera that draws UI +fn setup_camera(mut cmd: Commands) { + cmd.spawn(Camera2dBundle::default()); +} + +fn update_ui_side_panel(mut contexts: EguiContexts, mut global_state: ResMut) { + egui::Window::new("Hello").show(contexts.ctx_mut(), |ui| { + if global_state.polygon_started { + // TODO + } else { + if ui.button("New Polygon").clicked() { + global_state.polygon_started = true; + } + } + }); +} + +/// This system prints out all mouse events as they come in +fn print_mouse_events_system( + mut mouse_button_input_events: EventReader, + mut cursor_moved_events: EventReader, + mut mouse_wheel_events: EventReader, + mut global_state: ResMut, + mut object_infos: ResMut, + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + for event in mouse_button_input_events.iter() { + match event.button { + MouseButton::Left => match event.state { + ButtonState::Pressed => { + println!( + "Left mouse button pressed at {:?}", + global_state.cursor_position + ); + + let polygon = match object_infos.active_polygon() { + Some(polygon) => polygon, + None => object_infos.add_polygon(), + }; + + if polygon.add_point(global_state.cursor_position) { + global_state.polygon_started = false; + + // TODO remove single points -> draw full polygon + } else { + commands.spawn(MaterialMesh2dBundle { + mesh: meshes.add(Mesh::from(shape::Quad::default())).into(), + transform: Transform::default() + .with_scale(Vec3::splat(4.0)) + .with_translation( + (global_state.cursor_position + - (global_state.window_extent * 0.5)) + .extend(0.0), + ), + material: materials.add(ColorMaterial::from(Color::BLACK)), + ..default() + }); + } + } + ButtonState::Released => (), + }, + + _ => (), + } + } + + // store latest mouse cursor location + for event in cursor_moved_events.iter() { + global_state.cursor_position = event.position; + } + + for _event in mouse_wheel_events.iter() { + // + } +} + +fn on_resize_system( + mut resize_reader: EventReader, + mut global_state: ResMut, +) { + for e in resize_reader.iter() { + // When resolution is being changed + global_state.window_extent = Vec2::new(e.width, e.height); + } } diff --git a/src/polygon.rs b/src/polygon.rs new file mode 100644 index 0000000..83459d6 --- /dev/null +++ b/src/polygon.rs @@ -0,0 +1,63 @@ +use bevy::prelude::*; + +#[derive(Default, Debug, PartialEq)] +pub struct Polygon { + points: Vec, + finished: bool, +} + +impl Polygon { + const PROXIMITY_THRESHOLD: f32 = 5.0; + + pub fn add_point(&mut self, point: Vec2) -> bool { + debug_assert_eq!(self.finished, false); + + // polygon must be at least an triangle + // and check if we can close up the polygon + if self.points.len() >= 3 && self.check_first_for_proximity(point) { + self.points.push(point); + self.finished = true; + + return true; + } + + // deny too close points + if !self.check_all_for_proximity(point) { + self.points.push(point); + } + + false + } + + pub fn finished(&self) -> bool { + self.finished + } + + fn check_all_for_proximity(&self, point: Vec2) -> bool { + self.points + .iter() + .any(|p| p.distance(point) < Self::PROXIMITY_THRESHOLD) + } + + fn check_first_for_proximity(&self, point: Vec2) -> bool { + debug_assert!(!self.points.is_empty()); + + self.points.first().unwrap().distance(point) < Self::PROXIMITY_THRESHOLD + } +} + +#[derive(Resource, Default, Debug, PartialEq)] +pub struct ObjectInfos { + polygons: Vec, +} + +impl ObjectInfos { + pub fn active_polygon(&mut self) -> Option<&mut Polygon> { + self.polygons.iter_mut().find(|polygon| !polygon.finished()) + } + + pub fn add_polygon(&mut self) -> &mut Polygon { + self.polygons.push(Polygon::default()); + self.polygons.last_mut().unwrap() + } +}