diff --git a/src/create.rs b/src/create.rs index eb987d2..d544904 100644 --- a/src/create.rs +++ b/src/create.rs @@ -1,31 +1,62 @@ +use bevy::math::Vec3Swizzles; use bevy::prelude::*; use crate::*; use bevy_prototype_lyon::prelude::*; pub fn create_sys( mut coms: Commands, + p_size: Res, state: Res, mouse: Res>, wnds: Res, - q_cam: Query<(&Camera, &GlobalTransform), With>, mut first_point: Local>, - paths: Query<&mut Path>, - spoints: Query<&SPoint>, + q_cam: Query<(&Camera, &GlobalTransform), With>, + mut paths: Query<&mut Path>, + mut transforms: Query<&mut Transform>, ) { let mouse_pos = get_mouse_pos(&q_cam, &wnds); if let Some(mouse_pos) = mouse_pos { if let Some(sd) = &mut *first_point { - update_main_shape_creation(paths, spoints, sd, mouse_pos); + update_main_shape_creation(&mut paths, &transforms, sd, mouse_pos); if mouse.just_released(MouseButton::Left) { - let done = insert_edge_to_shape_creation(sd, mouse_pos, &mut coms); + let done = insert_edge_to_shape_creation(&mut coms, sd, mouse_pos, p_size.0); if done { + let center = add_center_point(&mut coms, sd, &transforms, mouse_pos, p_size.0); + + // Update each point to be relative to the new center! + if let Ok(mut t) = transforms.get_mut(sd.main_shape) { + t.translation = center.extend(0.0); + } + if let Ok(mut t) = transforms.get_mut(sd.center.unwrap()) { + t.translation = Vec3::new(0.0, 0.0, 1.0); // Just to make sure the center is centered + } + for edge in sd.edges.iter() { + if let Ok(mut t) = transforms.get_mut(*edge) { + t.translation -= center.extend(0.0); + } + else { + // If we dont have a transform in the query for the relevant entity, + // then it should be the entity we just inserted, and we can just override its transform + coms.entity(*edge) + .insert(Transform::from_translation((mouse_pos - center).extend(1.0))); + } + } + + finalize_shape_redraw(&mut paths, &transforms, sd, mouse_pos - center); + + let mut ms = coms.entity(sd.main_shape); + ms.add_child(sd.center.unwrap()); + for e in sd.edges.iter() { + ms.add_child(*e); + } + + coms.spawn() .insert(first_point.take().unwrap()); - *first_point = None; } } @@ -43,29 +74,119 @@ pub fn create_sys( else if mouse.just_released(MouseButton::Left) { // We can now spawn a shape in the current mouse position... // Spawn the first point - *first_point = Some(create_new_shape(&mut coms, mouse_pos, state.create_shape)); + *first_point = Some(create_new_shape(&mut coms, mouse_pos, state.create_shape, p_size.0)); } } } -/// Inserts a new edge/point to the currently created shape, return if the shape is complete -fn insert_edge_to_shape_creation( - sd: &mut ShapeData, - mpos: Vec2, +/// lpos - should be in local coordinates! +fn finalize_shape_redraw( + paths: &mut Query<&mut Path>, + transforms: &Query<&mut Transform>, + sd: &ShapeData, + lpos: Vec2, // Last position(1 we just inserted) +) { + let path = match sd.shape { + CreateShape::Triangle => { + assert!(sd.edges.len() == 3); + let fp = transforms.get(sd.edges[0]).unwrap().translation.xy(); + let sp = transforms.get(sd.edges[1]).unwrap().translation.xy(); + + let shape = shapes::Polygon { + points: Vec::from([fp, sp, lpos]), + closed: true, + }; + + ShapePath::build_as(&shape) + }, + CreateShape::Square => { + assert!(sd.edges.len() == 2); + let opposite_point = transforms.get(sd.edges[0]).unwrap().translation.xy(); + let ext = (lpos - opposite_point).abs(); + + let shape = shapes::Rectangle { extents: ext, origin: RectangleOrigin::Center }; + ShapePath::build_as(&shape) + }, + CreateShape::Circle => { + assert!(sd.center.is_some()); + let center= transforms.get(sd.center.unwrap()).unwrap().translation.xy(); + + let shape = shapes::Circle { radius: (lpos - center).length(), center: Vec2::ZERO }; + ShapePath::build_as(&shape) + }, + CreateShape::Capsule => { + panic!("Capsule creation not implemented yet!"); + }, + }; + + if let Ok(mut p) = paths.get_mut(sd.main_shape) { + *p = path; + } +} +/// Adds a center point to the shape, returning the center location(due to commands not being run until end of frame) +fn add_center_point( coms: &mut Commands, -) -> bool { - // Spawn the new point - // Spawn the first point(where the user just clicked) + sd: &mut ShapeData, + transforms: &Query<&mut Transform>, + l_pos: Vec2, // We call this function before commands are executed, so we dont have the last entity YET! + p_size: f32, +) -> Vec2 { + if sd.center.is_some() { + return transforms.get(sd.center.unwrap()).unwrap().translation.xy(); + } + + let center = match sd.shape { + CreateShape::Triangle => { + assert!(sd.edges.len() == 3); + + let fp = transforms.get(sd.edges[0]).unwrap().translation.xy(); + let sp = transforms.get(sd.edges[1]).unwrap().translation.xy(); + + (fp + sp + l_pos) / 3.0 + }, + CreateShape::Square => { + assert!(sd.edges.len() == 2); + + let e1 = transforms.get(sd.edges[0]).unwrap().translation.xy(); + + (e1 + l_pos) * 0.5 + }, + _ => panic!("Should not be reachable, as circle has a center and capsule is disabled for now!"), + }; + let shape = shapes::Circle { - radius: 3.0, - center: mpos, + radius: p_size, + center: Vec2::ZERO, }; let np = coms.spawn_bundle(GeometryBuilder::build_as( &shape, DrawMode::Fill(FillMode::color(Color::RED)), Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)) )) - .insert(SPoint(mpos)) + .id(); + sd.center = Some(np); + + center +} + +/// Inserts a new edge/point to the currently created shape, return if the shape is complete +fn insert_edge_to_shape_creation( + coms: &mut Commands, + sd: &mut ShapeData, + mpos: Vec2, + p_size: f32, +) -> bool { + // Spawn the new point + // Spawn the first point(where the user just clicked) + let shape = shapes::Circle { + radius: p_size, + center: Vec2::ZERO, + }; + let np = coms.spawn_bundle(GeometryBuilder::build_as( + &shape, + DrawMode::Fill(FillMode::color(Color::RED)), + Transform::from_translation(mpos.extend(1.0)) + )) .id(); match sd.shape { @@ -77,7 +198,7 @@ fn insert_edge_to_shape_creation( sd.edges.push(np); sd.edges.len() == 2 }, - CreateShape::SquareCenter | CreateShape::Circle => { + CreateShape::Circle => { sd.edges.push(np); sd.edges.len() == 1 }, @@ -98,22 +219,22 @@ fn insert_edge_to_shape_creation( } fn update_main_shape_creation( - mut paths: Query<&mut Path>, - spoints: Query<&SPoint>, + paths: &mut Query<&mut Path>, + transforms: &Query<&mut Transform>, sd: &ShapeData, mpos: Vec2, ) { let path = match sd.shape { CreateShape::Triangle => { if sd.edges.len() == 1 { - let first_point = spoints.get(sd.edges[0]).unwrap().0; + let first_point = transforms.get(sd.edges[0]).unwrap().translation.xy(); let shape = shapes::Line(first_point, mpos); ShapePath::build_as(&shape) } else if sd.edges.len() == 2 { - let fp = spoints.get(sd.edges[0]).unwrap().0; - let sp = spoints.get(sd.edges[1]).unwrap().0; + let fp = transforms.get(sd.edges[0]).unwrap().translation.xy(); + let sp = transforms.get(sd.edges[1]).unwrap().translation.xy(); let shape = shapes::Polygon { points: Vec::from([fp, sp, mpos]), @@ -127,24 +248,16 @@ fn update_main_shape_creation( }, CreateShape::Square => { assert!(sd.edges.len() == 1); - let opposite_point = spoints.get(sd.edges[0]).unwrap().0; + let opposite_point = transforms.get(sd.edges[0]).unwrap().translation.xy(); let ext = (mpos - opposite_point).abs(); let center = mpos.min(opposite_point) + ext * 0.5; let shape = shapes::Rectangle { extents: ext, origin: RectangleOrigin::CustomCenter(center) }; ShapePath::build_as(&shape) }, - CreateShape::SquareCenter => { - assert!(sd.center.is_some()); - let center = spoints.get(sd.center.unwrap()).unwrap().0; - let ext = (mpos - center).abs() * 2.0; - - let shape = shapes::Rectangle { extents: ext, origin: RectangleOrigin::CustomCenter(center) }; - ShapePath::build_as(&shape) - }, CreateShape::Circle => { assert!(sd.center.is_some()); - let center= spoints.get(sd.center.unwrap()).unwrap().0; + let center= transforms.get(sd.center.unwrap()).unwrap().translation.xy(); let shape = shapes::Circle { radius: (mpos - center).length(), center }; ShapePath::build_as(&shape) @@ -159,7 +272,7 @@ fn update_main_shape_creation( } } -fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape) -> ShapeData { +fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape, p_size: f32) -> ShapeData { // Shape draw mode... let draw_mode = DrawMode::Outlined { fill_mode: FillMode::color(Color::rgba(0.0, 0.5, 0.5, 0.4)), @@ -168,15 +281,14 @@ fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape) - // Spawn the first point(where the user just clicked) let shape = shapes::Circle { - radius: 3.0, - center: pos, + radius: p_size, + center: Vec2::ZERO, }; let fp = coms.spawn_bundle(GeometryBuilder::build_as( &shape, DrawMode::Fill(FillMode::color(Color::RED)), - Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)) + Transform::from_translation(pos.extend(1.0)) )) - .insert(SPoint(pos)) .id(); // Spawn main shape! let main_shape = match create_shape { @@ -188,7 +300,7 @@ fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape) - Transform::from_translation(Vec3::ZERO) )).id() }, - CreateShape::Square | CreateShape::SquareCenter => { + CreateShape::Square => { let shape = shapes::Rectangle { extents: Vec2::ZERO, origin: RectangleOrigin::CustomCenter(pos)}; coms.spawn_bundle(GeometryBuilder::build_as( &shape, @@ -207,7 +319,7 @@ fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape) - }; // Get center point(if exists) let center = match create_shape { - CreateShape::SquareCenter | CreateShape::Circle => Some(fp), + CreateShape::Circle => Some(fp), _ => None, }; let edges = if center.is_none() { Vec::from([fp]) } else { Vec::new() }; diff --git a/src/lib.rs b/src/lib.rs index 11eb2b0..75daa35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,19 @@ +#![feature(let_chains)] + + use bevy::prelude::*; use bevy_egui::egui; mod create; mod helpers; +mod modify; +pub use modify::modify_sys; pub use create::create_sys; pub use helpers::*; -#[derive(Component, Debug, Clone, Copy)] -pub struct SPoint(pub Vec2); + +#[derive(Clone, Debug)] +pub struct PointSize(pub f32); #[derive(Component, Debug)] pub struct ShapeData { @@ -21,12 +27,12 @@ pub struct ShapeData { pub struct MainCamera; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum CreateShape { - Triangle, Square, SquareCenter, Circle, Capsule + Triangle, Square, Circle, Capsule } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Action { - Create, Modify, Delete + Create, Modify, Delete, Rotate } pub struct UiState { pub current_action: Action, diff --git a/src/main.rs b/src/main.rs index 83ba34e..1910a31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use bevy::ecs::schedule::ShouldRun; +use bevy::math::Vec3Swizzles; use bevy::{prelude::*, window::PresentMode, winit::WinitSettings}; use bevy_egui::{egui, EguiContext, EguiPlugin}; use bevy_prototype_lyon::prelude::*; @@ -20,6 +21,7 @@ fn main() { }) .insert_resource(ButtonsColors::default()) .insert_resource(UiState::default()) + .insert_resource(PointSize(5.0)) ; app @@ -72,6 +74,11 @@ fn action_bar_sys( if m.clicked() { state.current_action = Action::Modify; } + let r = hui.button(egui::RichText::new("R").color(state.current_action_color(&colors, Action::Rotate))) + .on_hover_text("Rotate"); + if r.clicked() { + state.current_action = Action::Rotate; + } let c = hui.button(egui::RichText::new("C").color(state.current_action_color(&colors, Action::Create))) .on_hover_text("Create"); if c.clicked() { @@ -103,11 +110,6 @@ fn action_bar_sys( if squ.clicked() { state.create_shape = CreateShape::Square; } - let sce = hui.button(egui::RichText::new("SqC").color(state.create_shape_color(&colors, CreateShape::SquareCenter))) - .on_hover_text("Square - from center point and edge"); - if sce.clicked() { - state.create_shape = CreateShape::SquareCenter; - } let cir = hui.button(egui::RichText::new("Cir").color(state.create_shape_color(&colors, CreateShape::Circle))) .on_hover_text("Circle - center point and radius"); if cir.clicked() { @@ -123,19 +125,3 @@ fn action_bar_sys( }); } - -fn modify_sys( - mouse: Res>, - wnds: Res, - q_cam: Query<(&Camera, &GlobalTransform), With>, - mut paths: Query<&mut Path>, - mut transforms: Query<&mut Transform>, - shapes: Query<&ShapeData>, -) { - let mouse_pos = get_mouse_pos(&q_cam, &wnds); - - if let Some(mouse_pos) = mouse_pos { - - } -} - diff --git a/src/modify.rs b/src/modify.rs new file mode 100644 index 0000000..2c5e403 --- /dev/null +++ b/src/modify.rs @@ -0,0 +1,154 @@ +use bevy::math::Vec3Swizzles; +use bevy::prelude::*; +use crate::*; +use bevy_prototype_lyon::prelude::*; + +pub fn modify_sys( + // Which entity the user currently drags, and which specific part of it(ShapeData entity, path entity) + mut holding: Local>, + mouse: Res>, + wnds: Res, + p_size: Res, + paths: Query<&mut Path>, + mut transforms: Query<&mut Transform>, + gtransforms: Query<&GlobalTransform>, + q_cam: Query<(&Camera, &GlobalTransform), With>, + shapes: Query<(Entity, &ShapeData)>, +) { + let mouse_pos = get_mouse_pos(&q_cam, &wnds); + + if let Some(mouse_pos) = mouse_pos { + if mouse.just_pressed(MouseButton::Left) && holding.is_none() { + // Grab attempt - search if we are holding a shape or something + for (e, sd) in shapes.iter() { + if let Ok(t) = gtransforms.get(sd.center.unwrap()) { + let t = t.translation.xy(); + + if (mouse_pos - t).length_squared() < p_size.0.powi(2) { + *holding = Some((e, sd.center.unwrap())); + break; + } + } + for edge in sd.edges.iter() { + if let Ok(t) = gtransforms.get(*edge) { + let t = t.translation.xy(); + + if (mouse_pos - t).length_squared() < p_size.0.powi(2) { + *holding = Some((e, *edge)); + break; + } + } + } + } + } + else if mouse.just_released(MouseButton::Left) && holding.is_some() { + *holding = None; // We just released our sad little dot/shape + } + else if let Some((se, pe)) = *holding { + if let Ok(sd) = shapes.get_component::(se) { + // Middle of a drag :D + if let Some(ce) = sd.center && ce == pe { + if let Ok(mut t) = transforms.get_mut(sd.main_shape) { + t.translation = mouse_pos.extend(t.translation.z); + } + } + else if let Ok(mut t) = transforms.get_mut(pe) { + // Update the dot position + let delta = mouse_pos - gtransforms.get(pe).unwrap().translation.xy(); + t.translation += delta.extend(0.0); + // We need to recalculate the center, and update the points to be the new relevant point from the center + let center_offset = calc_shape_center_offset(&transforms, sd).extend(0.0); + // Update each edge's offset, and then move the main shape's translation + for edge in sd.edges.iter() { + if let Ok(mut t) = transforms.get_mut(*edge) { + t.translation -= center_offset; + } + } + if let Ok(mut t) = transforms.get_mut(sd.main_shape) { + t.translation += center_offset; + } + + // Now we need to update the shape itself + update_main_shape(paths, transforms, sd); + } + } + } + } +} + +fn calc_shape_center_offset(transforms: &Query<&mut Transform>, sd: &ShapeData) -> Vec2 { + match sd.shape { + CreateShape::Triangle => { + assert!(sd.edges.len() == 3); + + let e1 = transforms.get(sd.edges[0]).unwrap().translation.xy(); + let e2 = transforms.get(sd.edges[1]).unwrap().translation.xy(); + let e3 = transforms.get(sd.edges[2]).unwrap().translation.xy(); + + (e1 + e2 + e3) / 3.0 + }, + CreateShape::Square => { + assert!(sd.edges.len() == 2); + + let e1 = transforms.get(sd.edges[0]).unwrap().translation.xy(); + let e2 = transforms.get(sd.edges[1]).unwrap().translation.xy(); + + (e1 + e2) * 0.5 + }, + CreateShape::Circle => { + if let Ok(gt) = transforms.get(sd.center.unwrap()) { + gt.translation.xy() + } + else { + Vec2::ZERO + } + }, + CreateShape::Capsule => unimplemented!("Capsule is disabled for now"), + } +} + +fn update_main_shape(mut paths: Query<&mut Path>, transforms: Query<&mut Transform>, sd: &ShapeData) { + let path = match sd.shape { + CreateShape::Triangle => { + assert!(sd.edges.len() == 3); + assert!(sd.center.is_some()); + + let fp = transforms.get(sd.edges[0]).unwrap().translation.xy(); + let sp = transforms.get(sd.edges[1]).unwrap().translation.xy(); + let tp = transforms.get(sd.edges[2]).unwrap().translation.xy(); + + let shape = shapes::Polygon { + points: Vec::from([fp, sp, tp]), + closed: true, + }; + ShapePath::build_as(&shape) + }, + CreateShape::Square => { + assert!(sd.edges.len() == 2); + assert!(sd.center.is_some()); + + let e1 = transforms.get(sd.edges[0]).unwrap().translation.xy(); + let e2 = transforms.get(sd.edges[1]).unwrap().translation.xy(); + let ext = (e2 - e1).abs(); + + let shape = shapes::Rectangle { extents: ext, origin: RectangleOrigin::Center }; + ShapePath::build_as(&shape) + }, + CreateShape::Circle => { + assert!(sd.center.is_some()); + assert!(sd.edges.len() == 1); + + let center= transforms.get(sd.center.unwrap()).unwrap().translation.xy(); + let edge = transforms.get(sd.edges[0]).unwrap().translation.xy(); + + let shape = shapes::Circle { radius: (edge - center).length(), center: Vec2::ZERO }; + ShapePath::build_as(&shape) + }, + CreateShape::Capsule => { + panic!("Capsule creation not implemented yet!"); + }, + }; + if let Ok(mut p) = paths.get_mut(sd.main_shape) { + *p = path; + } +} \ No newline at end of file