From 5b11cd45eb8187b1498a4b657fdc1d5a748aa9ce Mon Sep 17 00:00:00 2001 From: hodasemi Date: Sun, 14 May 2023 15:15:51 +0200 Subject: [PATCH] Implement view swap --- .vscode/settings.json | 7 ++ src/global_state.rs | 61 ++++++++++++- src/main.rs | 36 ++++++-- src/polygon.rs | 199 +++++++++++++++++++++++++++++++++++++++--- 4 files changed, 283 insertions(+), 20 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..184d391 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "workbench.colorCustomizations": { + "activityBar.background": "#3D2A09", + "titleBar.activeBackground": "#553B0C", + "titleBar.activeForeground": "#FDFAF4" + } +} \ No newline at end of file diff --git a/src/global_state.rs b/src/global_state.rs index f2b88b0..1e522e2 100644 --- a/src/global_state.rs +++ b/src/global_state.rs @@ -1,11 +1,67 @@ use bevy::prelude::*; -#[derive(Resource, Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Clone)] +pub enum Camera { + TwoD(Entity), + ThreeD(Entity), +} + +impl Camera { + fn create_2d(cmd: &mut Commands) -> Self { + Self::TwoD(cmd.spawn(Camera2dBundle::default()).id()) + } + + fn create_3d(cmd: &mut Commands, window_extent: Vec2) -> Self { + Self::TwoD( + cmd.spawn(Camera3dBundle { + transform: Transform::from_xyz(200.0, -600.0, 600.0) + .looking_at(window_extent.extend(0.0), Vec3::Z), + ..default() + }) + .id(), + ) + } +} + +#[derive(Resource, Debug, PartialEq, Clone)] pub struct GlobalState { pub cursor_position: Vec2, pub window_extent: Vec2, pub polygon_started: bool, pub view_2d: bool, + pub extrusion_height: f32, + + pub camera: Option, +} + +impl GlobalState { + pub fn setup_2d_cam(&mut self, cmd: &mut Commands) { + match &mut self.camera { + Some(camera) => match camera { + Camera::TwoD(_) => (), + Camera::ThreeD(cam) => { + cmd.entity(*cam).despawn(); + + *camera = Camera::create_2d(cmd); + } + }, + None => self.camera = Some(Camera::create_2d(cmd)), + } + } + + pub fn setup_3d_cam(&mut self, cmd: &mut Commands) { + match &mut self.camera { + Some(camera) => match camera { + Camera::TwoD(cam) => { + cmd.entity(*cam).despawn(); + + *camera = Camera::create_3d(cmd, self.window_extent); + } + Camera::ThreeD(_) => (), + }, + None => self.camera = Some(Camera::create_3d(cmd, self.window_extent)), + } + } } impl Default for GlobalState { @@ -15,6 +71,9 @@ impl Default for GlobalState { window_extent: Vec2::default(), polygon_started: false, view_2d: true, + extrusion_height: 20.0, + + camera: None, } } } diff --git a/src/main.rs b/src/main.rs index 609cd26..ce062c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,8 +26,8 @@ fn main() { } // Spawns the camera -fn setup_camera(mut cmd: Commands) { - cmd.spawn(Camera2dBundle::default()); +fn setup_camera(mut cmd: Commands, mut global_state: ResMut) { + global_state.setup_2d_cam(&mut cmd); } // Create UI @@ -35,6 +35,10 @@ fn update_ui_side_panel( mut contexts: EguiContexts, mut global_state: ResMut, mut object_infos: ResMut, + mut commands: Commands, + mut meshes: ResMut>, + mut standard_materials: ResMut>, + mut color_materials: ResMut>, ) { egui::SidePanel::right("2D View") .default_width(400.0) @@ -43,17 +47,31 @@ fn update_ui_side_panel( if ui.button("Change to 3D View").clicked() { global_state.view_2d = false; - object_infos - .iter_mut() - .for_each(|polygon| polygon.swap_to_3d()); + object_infos.iter_mut().for_each(|polygon| { + polygon.swap_to_3d( + &global_state, + &mut commands, + &mut meshes, + &mut standard_materials, + ) + }); + + global_state.setup_3d_cam(&mut commands); } } else { if ui.button("Change to 2D View").clicked() { global_state.view_2d = true; - object_infos - .iter_mut() - .for_each(|polygon| polygon.swap_to_2d()); + object_infos.iter_mut().for_each(|polygon| { + polygon.swap_to_2d( + &global_state, + &mut commands, + &mut meshes, + &mut color_materials, + ) + }); + + global_state.setup_2d_cam(&mut commands); } } @@ -87,7 +105,7 @@ fn mouse_event_system( MouseButton::Left => match event.state { ButtonState::Pressed => { // only add new points if "New Polygon" button got pressed - if global_state.polygon_started { + if global_state.polygon_started && global_state.view_2d { let polygon = match object_infos.active_polygon() { Some(polygon) => polygon, None => object_infos.add_polygon(), diff --git a/src/polygon.rs b/src/polygon.rs index cc4b0a8..2e83d96 100644 --- a/src/polygon.rs +++ b/src/polygon.rs @@ -38,7 +38,7 @@ impl Default for PolygonState { #[derive(Debug, PartialEq)] enum PolygonView { TwoDimensional(PolygonState), - ThreeDimensional(Entity), + ThreeDimensional(Option), } impl PolygonView { @@ -52,6 +52,20 @@ impl PolygonView { 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 { @@ -69,16 +83,177 @@ pub struct Polygon { impl Polygon { const PROXIMITY_THRESHOLD: f32 = 10.0; - pub fn swap_to_3d(&mut self) { + 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()); - todo!() + // 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) { + 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()); - todo!() + // 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] { @@ -101,7 +276,7 @@ impl 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(); + let collection_ids = self.state.two_dimensional_mut().collection_mut(); for point_id in collection_ids.points.iter() { commands.entity(*point_id).despawn(); @@ -126,14 +301,14 @@ impl Polygon { .id(); // store triangle entity id in state - self.state = PolygonState::Finished(triangle_id); + *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.collection_mut(); + let collection_ids = self.state.two_dimensional_mut().collection_mut(); // create point mesh at click position let point_id = commands @@ -180,8 +355,11 @@ impl Polygon { pub fn finished(&self) -> bool { match &self.state { - PolygonState::Collection(_) => false, - PolygonState::Finished(_) => true, + PolygonView::TwoDimensional(t) => match t { + PolygonState::Collection(_) => false, + PolygonState::Finished(_) => true, + }, + PolygonView::ThreeDimensional(_) => false, } } @@ -200,6 +378,7 @@ impl Polygon { 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();