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, state: PolygonState, } impl Polygon { const PROXIMITY_THRESHOLD: f32 = 10.0; 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) { // 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); } false } pub fn finished(&self) -> bool { match &self.state { PolygonState::Collection(_) => false, PolygonState::Finished(_) => true, } } 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 } 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)] 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() } }