use std::slice::IterMut; 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(Debug, PartialEq)] enum PolygonView { TwoDimensional(PolygonState), ThreeDimensional(Option), } impl PolygonView { fn is_2d(&self) -> bool { match self { PolygonView::TwoDimensional(_) => true, PolygonView::ThreeDimensional(_) => false, } } fn is_3d(&self) -> bool { !self.is_2d() } fn two_dimensional_mut(&mut self) -> &mut PolygonState { match self { PolygonView::TwoDimensional(t) => t, PolygonView::ThreeDimensional(_) => panic!("called 2d on 3d!"), } } fn three_dimensional_mut(&mut self) -> &mut Option { match self { PolygonView::TwoDimensional(_) => panic!("called 3d on 2d!"), PolygonView::ThreeDimensional(t) => t, } } } impl Default for PolygonView { fn default() -> Self { Self::TwoDimensional(PolygonState::default()) } } #[derive(Default, Debug, PartialEq)] pub struct Polygon { points: Vec, state: PolygonView, } impl Polygon { const PROXIMITY_THRESHOLD: f32 = 10.0; pub fn swap_to_3d( &mut self, global_state: &GlobalState, commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, ) { debug_assert!(self.state.is_2d()); // clear all 2d entities let finished = match self.state.two_dimensional_mut() { PolygonState::Collection(collection_ids) => { for point in collection_ids.points.iter() { commands.entity(*point).despawn(); } for line in collection_ids.lines.iter() { commands.entity(*line).despawn(); } false } PolygonState::Finished(mesh) => { commands.entity(*mesh).despawn(); true } }; self.state = PolygonView::ThreeDimensional(if finished { let vertices: Vec = self .points .iter() .map(|p| vec![p.extend(0.0), p.extend(global_state.extrusion_height)]) .flatten() .collect(); let indices = Indices::U32({ let mut v = Vec::new(); for i in 0..self.points.len() { let index = (i as u32) * 2; // create quads from 4 adjacent points v.push(index); v.push((index + 2) % vertices.len() as u32); v.push(index + 1); v.push(index + 1); v.push((index + 2) % vertices.len() as u32); v.push((index + 3) % vertices.len() as u32); } // TODO: proper ear cutting algo for i in 2..self.points.len() as u32 { let index = (i as u32) * 2 + 1; v.push(1); v.push(index - 2); v.push(index); } v }); let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); mesh.set_indices(Some(indices)); mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices); let mesh_id = commands .spawn(MaterialMeshBundle { mesh: meshes.add(mesh).into(), transform: Transform::default(), material: materials.add(StandardMaterial::from(Color::BLACK)), ..default() }) .id(); Some(mesh_id) } else { None }); } pub fn swap_to_2d( &mut self, global_state: &GlobalState, commands: &mut Commands, meshes: &mut ResMut>, materials: &mut ResMut>, ) { debug_assert!(self.state.is_3d()); // clear 3d entity let finished = match self.state.three_dimensional_mut() { Some(mesh_id) => { commands.entity(*mesh_id).despawn(); true } None => false, }; self.state = PolygonView::TwoDimensional(if finished { // 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 PolygonState::Finished(triangle_id) } else { // create point meshes for every point let point_ids = self .points .iter() .map(|&point| { commands .spawn(MaterialMesh2dBundle { mesh: meshes.add(Mesh::from(shape::Quad::default())).into(), transform: Transform::default() .with_scale(Vec3::splat(4.0)) .with_translation( (point - (global_state.window_extent * 0.5)).extend(0.0), ), material: materials.add(ColorMaterial::from(Color::BLACK)), ..default() }) .id() }) .collect(); // draw lines between the points let line_ids = (1..self.points.len()) .map(|i| { let previous_point = self.points[i - 1]; let point = self.points[i]; let a = Vec2::X.angle_between((point - previous_point).normalize()); commands .spawn(MaterialMesh2dBundle { mesh: meshes .add(Self::create_line_mesh(previous_point, point)) .into(), transform: Transform::default() .with_translation( (previous_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() }) .collect(); PolygonState::Collection(CollectionIDs { points: point_ids, lines: line_ids, }) }); } pub fn points(&self) -> &[Vec2] { &self.points } 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.two_dimensional_mut().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.two_dimensional_mut() = PolygonState::Finished(triangle_id); return true; } // deny too close points if !self.check_all_for_proximity(point) { let collection_ids = self.state.two_dimensional_mut().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 { PolygonView::TwoDimensional(t) => match t { PolygonState::Collection(_) => false, PolygonState::Finished(_) => true, }, PolygonView::ThreeDimensional(_) => false, } } 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(); // TODO: proper ear cutting algo 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() } pub fn iter_mut(&mut self) -> IterMut<'_, Polygon> { self.polygons.iter_mut() } }