From 63464c9be4168863dd9af5c92adf9bac7767b15a Mon Sep 17 00:00:00 2001 From: hodasemi Date: Sat, 13 May 2023 16:08:26 +0200 Subject: [PATCH] Triangulate face --- src/main.rs | 73 +++++++--------------- src/polygon.rs | 161 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 174 insertions(+), 60 deletions(-) diff --git a/src/main.rs b/src/main.rs index c3fda01..7468d2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,12 @@ 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, }; @@ -22,35 +20,17 @@ fn main() { .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, - )) + .add_startup_system(setup_camera) + .add_systems((mouse_event_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 +// Spawns the camera fn setup_camera(mut cmd: Commands) { cmd.spawn(Camera2dBundle::default()); } +// Create UI 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 { @@ -63,8 +43,8 @@ fn update_ui_side_panel(mut contexts: EguiContexts, mut global_state: ResMut, mut cursor_moved_events: EventReader, mut mouse_wheel_events: EventReader, @@ -78,33 +58,21 @@ fn print_mouse_events_system( match event.button { MouseButton::Left => match event.state { ButtonState::Pressed => { - println!( - "Left mouse button pressed at {:?}", - global_state.cursor_position - ); + // only add new points if "New Polygon" button got pressed + if global_state.polygon_started { + let polygon = match object_infos.active_polygon() { + Some(polygon) => polygon, + None => object_infos.add_polygon(), + }; - 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() - }); + if polygon.add_point( + &global_state, + &mut commands, + &mut meshes, + &mut materials, + ) { + global_state.polygon_started = false; + } } } ButtonState::Released => (), @@ -124,6 +92,7 @@ fn print_mouse_events_system( } } +// Handles resize events fn on_resize_system( mut resize_reader: EventReader, mut global_state: ResMut, diff --git a/src/polygon.rs b/src/polygon.rs index 83459d6..6c16c87 100644 --- a/src/polygon.rs +++ b/src/polygon.rs @@ -1,28 +1,134 @@ -use bevy::prelude::*; +use bevy::{ + prelude::*, + render::{mesh::Indices, render_resource::PrimitiveTopology}, + sprite::MaterialMesh2dBundle, +}; + +use crate::global_state::GlobalState; + +#[derive(Default, Debug, PartialEq)] +struct CollectionIDs { + points: Vec, + lines: Vec, +} + +#[derive(Debug, PartialEq)] +enum PolygonState { + Collection(CollectionIDs), + Finished(Entity), +} + +impl PolygonState { + fn collection_mut(&mut self) -> &mut CollectionIDs { + match self { + PolygonState::Collection(c) => c, + PolygonState::Finished(_) => panic!("called collection on finished state!"), + } + } +} + +impl Default for PolygonState { + fn default() -> Self { + Self::Collection(CollectionIDs::default()) + } +} #[derive(Default, Debug, PartialEq)] pub struct Polygon { points: Vec, - finished: bool, + state: PolygonState, } impl Polygon { - const PROXIMITY_THRESHOLD: f32 = 5.0; + const PROXIMITY_THRESHOLD: f32 = 10.0; - pub fn add_point(&mut self, point: Vec2) -> bool { - debug_assert_eq!(self.finished, false); + pub fn add_point( + &mut self, + global_state: &GlobalState, + commands: &mut Commands, + meshes: &mut ResMut>, + materials: &mut ResMut>, + ) -> bool { + debug_assert_eq!(self.finished(), false); + + let point = global_state.cursor_position; // 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; + // remove points and lines from collection state + { + let collection_ids = self.state.collection_mut(); + + for point_id in collection_ids.points.iter() { + commands.entity(*point_id).despawn(); + } + + for line_id in collection_ids.lines.iter() { + commands.entity(*line_id).despawn(); + } + } + + // create triangle mesh + let triangle_mesh = self.create_triangulated_mesh(); + + let triangle_id = commands + .spawn(MaterialMesh2dBundle { + mesh: meshes.add(triangle_mesh).into(), + transform: Transform::default() + .with_translation((-global_state.window_extent * 0.5).extend(0.0)), + material: materials.add(ColorMaterial::from(Color::BLACK)), + ..default() + }) + .id(); + + // store triangle entity id in state + self.state = PolygonState::Finished(triangle_id); return true; } // deny too close points if !self.check_all_for_proximity(point) { + let collection_ids = self.state.collection_mut(); + + // create point mesh at click position + let point_id = 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() + }) + .id(); + + collection_ids.points.push(point_id); + + // draw a line between new point and last point + if let Some(&last_point) = self.points.last() { + let a = Vec2::X.angle_between((point - last_point).normalize()); + + let line_id = commands + .spawn(MaterialMesh2dBundle { + mesh: meshes.add(Self::create_line_mesh(last_point, point)).into(), + transform: Transform::default() + .with_translation( + (last_point - (global_state.window_extent * 0.5)).extend(0.0), + ) + .with_rotation(Quat::from_rotation_z(a)), + material: materials.add(ColorMaterial::from(Color::BLACK)), + ..default() + }) + .id(); + + collection_ids.lines.push(line_id); + } + self.points.push(point); } @@ -30,7 +136,10 @@ impl Polygon { } pub fn finished(&self) -> bool { - self.finished + match &self.state { + PolygonState::Collection(_) => false, + PolygonState::Finished(_) => true, + } } fn check_all_for_proximity(&self, point: Vec2) -> bool { @@ -44,6 +153,42 @@ impl Polygon { self.points.first().unwrap().distance(point) < Self::PROXIMITY_THRESHOLD } + + fn create_triangulated_mesh(&self) -> Mesh { + let vertices: Vec<_> = self.points.iter().map(|p| p.extend(0.0)).collect(); + + let indices = Indices::U32({ + let mut v = Vec::new(); + + for i in 2..self.points.len() as u32 { + v.push(0); + v.push(i - 1); + v.push(i); + } + + v + }); + + let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); + mesh.set_indices(Some(indices)); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices); + + mesh + } + + fn create_line_mesh(start: Vec2, end: Vec2) -> Mesh { + let d = start.distance(end); + + let vertices = [[0.0, 0.0, 0.0], [d, 0.0, 0.0]]; + + let mut mesh = Mesh::new(PrimitiveTopology::LineList); + mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + vertices.into_iter().map(|v| v).collect::>(), + ); + + mesh + } } #[derive(Resource, Default, Debug, PartialEq)]