shape creation works!
This commit is contained in:
parent
c50609cd64
commit
898cb5e83c
4 changed files with 369 additions and 15 deletions
216
src/create.rs
Normal file
216
src/create.rs
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use crate::*;
|
||||||
|
use bevy_prototype_lyon::prelude::*;
|
||||||
|
|
||||||
|
pub fn create_sys(
|
||||||
|
mut coms: Commands,
|
||||||
|
state: Res<UiState>,
|
||||||
|
mouse: Res<Input<MouseButton>>,
|
||||||
|
wnds: Res<Windows>,
|
||||||
|
q_cam: Query<(&Camera, &GlobalTransform), With<MainCamera>>,
|
||||||
|
mut first_point: Local<Option<ShapeData>>,
|
||||||
|
paths: Query<&mut Path>,
|
||||||
|
spoints: Query<&SPoint>,
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
if mouse.just_released(MouseButton::Left) {
|
||||||
|
let done = insert_edge_to_shape_creation(sd, mouse_pos, &mut coms);
|
||||||
|
|
||||||
|
if done {
|
||||||
|
coms.spawn()
|
||||||
|
.insert(first_point.take().unwrap());
|
||||||
|
|
||||||
|
*first_point = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if mouse.just_released(MouseButton::Right) {
|
||||||
|
if let Some(e) = &sd.center {
|
||||||
|
coms.entity(*e).despawn();
|
||||||
|
}
|
||||||
|
coms.entity(sd.main_shape).despawn();
|
||||||
|
sd.edges.iter().for_each(|e| coms.entity(*e).despawn());
|
||||||
|
|
||||||
|
*first_point = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
coms: &mut Commands,
|
||||||
|
) -> bool {
|
||||||
|
// Spawn the new point
|
||||||
|
// Spawn the first point(where the user just clicked)
|
||||||
|
let shape = shapes::Circle {
|
||||||
|
radius: 3.0,
|
||||||
|
center: mpos,
|
||||||
|
};
|
||||||
|
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();
|
||||||
|
|
||||||
|
match sd.shape {
|
||||||
|
CreateShape::Triangle => {
|
||||||
|
sd.edges.push(np);
|
||||||
|
sd.edges.len() == 3
|
||||||
|
},
|
||||||
|
CreateShape::Square => {
|
||||||
|
sd.edges.push(np);
|
||||||
|
sd.edges.len() == 2
|
||||||
|
},
|
||||||
|
CreateShape::SquareCenter | CreateShape::Circle => {
|
||||||
|
sd.edges.push(np);
|
||||||
|
sd.edges.len() == 1
|
||||||
|
},
|
||||||
|
CreateShape::Capsule => {
|
||||||
|
if sd.edges.len() == 1 {
|
||||||
|
sd.edges.push(np);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
else if sd.edges.len() == 2 {
|
||||||
|
sd.center = Some(np);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
panic!("Capsule was started without any edges... WHAT!");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_main_shape_creation(
|
||||||
|
mut paths: Query<&mut Path>,
|
||||||
|
spoints: Query<&SPoint>,
|
||||||
|
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 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 shape = shapes::Polygon {
|
||||||
|
points: Vec::from([fp, sp, mpos]),
|
||||||
|
closed: true,
|
||||||
|
};
|
||||||
|
ShapePath::build_as(&shape)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
panic!("Triangle cannot have less than 1 edges or more than 2 during creation!: {:?}", sd);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CreateShape::Square => {
|
||||||
|
assert!(sd.edges.len() == 1);
|
||||||
|
let opposite_point = spoints.get(sd.edges[0]).unwrap().0;
|
||||||
|
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 shape = shapes::Circle { radius: (mpos - center).length(), center };
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_new_shape(coms: &mut Commands, pos: Vec2, create_shape: CreateShape) -> ShapeData {
|
||||||
|
// Shape draw mode...
|
||||||
|
let draw_mode = DrawMode::Outlined {
|
||||||
|
fill_mode: FillMode::color(Color::rgba(0.0, 0.5, 0.5, 0.4)),
|
||||||
|
outline_mode: StrokeMode::new(Color::rgba(0.0, 0.5, 0.5, 0.6), 3.0),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Spawn the first point(where the user just clicked)
|
||||||
|
let shape = shapes::Circle {
|
||||||
|
radius: 3.0,
|
||||||
|
center: pos,
|
||||||
|
};
|
||||||
|
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))
|
||||||
|
))
|
||||||
|
.insert(SPoint(pos))
|
||||||
|
.id();
|
||||||
|
// Spawn main shape!
|
||||||
|
let main_shape = match create_shape {
|
||||||
|
CreateShape::Triangle | CreateShape::Capsule => {
|
||||||
|
let shape = shapes::Line(pos, pos);
|
||||||
|
coms.spawn_bundle(GeometryBuilder::build_as(
|
||||||
|
&shape,
|
||||||
|
draw_mode,
|
||||||
|
Transform::from_translation(Vec3::ZERO)
|
||||||
|
)).id()
|
||||||
|
},
|
||||||
|
CreateShape::Square | CreateShape::SquareCenter => {
|
||||||
|
let shape = shapes::Rectangle { extents: Vec2::ZERO, origin: RectangleOrigin::CustomCenter(pos)};
|
||||||
|
coms.spawn_bundle(GeometryBuilder::build_as(
|
||||||
|
&shape,
|
||||||
|
draw_mode,
|
||||||
|
Transform::from_translation(Vec3::ZERO)
|
||||||
|
)).id()
|
||||||
|
},
|
||||||
|
CreateShape::Circle => {
|
||||||
|
let shape = shapes::Circle { radius: 0.0, center: pos };
|
||||||
|
coms.spawn_bundle(GeometryBuilder::build_as(
|
||||||
|
&shape,
|
||||||
|
draw_mode,
|
||||||
|
Transform::from_translation(Vec3::ZERO)
|
||||||
|
)).id()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Get center point(if exists)
|
||||||
|
let center = match create_shape {
|
||||||
|
CreateShape::SquareCenter | CreateShape::Circle => Some(fp),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let edges = if center.is_none() { Vec::from([fp]) } else { Vec::new() };
|
||||||
|
|
||||||
|
ShapeData { shape: create_shape, main_shape, edges, center }
|
||||||
|
}
|
19
src/helpers.rs
Normal file
19
src/helpers.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub fn get_mouse_pos(q_cam: &Query<(&Camera, &GlobalTransform), With<MainCamera>>, wnds: &Windows) -> Option<Vec2> {
|
||||||
|
let (cam, cam_pos) = q_cam.get_single().expect("More than 1 MainCamera...");
|
||||||
|
let wnd = wnds.get_primary().expect("No window available???");
|
||||||
|
if let Some(screen_pos) = wnd.cursor_position() {
|
||||||
|
let w_size = Vec2::new(wnd.width() as f32, wnd.height() as f32);
|
||||||
|
let ndc = (screen_pos / w_size) * 2.0 - Vec2::ONE;
|
||||||
|
let ndc_to_world = cam_pos.compute_matrix() * cam.projection_matrix.inverse();
|
||||||
|
let world_pos = ndc_to_world.project_point3(ndc.extend(-1.0));
|
||||||
|
let world_pos: Vec2 = world_pos.truncate();
|
||||||
|
|
||||||
|
Some(world_pos)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
52
src/lib.rs
52
src/lib.rs
|
@ -1,18 +1,64 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
use bevy_egui::egui;
|
use bevy_egui::egui;
|
||||||
|
|
||||||
#[derive(Debug)]
|
mod create;
|
||||||
|
mod helpers;
|
||||||
|
pub use create::create_sys;
|
||||||
|
pub use helpers::*;
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Clone, Copy)]
|
||||||
|
pub struct SPoint(pub Vec2);
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct ShapeData {
|
||||||
|
pub shape: CreateShape,
|
||||||
|
pub main_shape: Entity,
|
||||||
|
pub edges: Vec<Entity>,
|
||||||
|
pub center: Option<Entity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct MainCamera;
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum CreateShape {
|
||||||
|
Triangle, Square, SquareCenter, Circle, Capsule
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Create, Modify, Delete
|
Create, Modify, Delete
|
||||||
}
|
}
|
||||||
pub struct UiState {
|
pub struct UiState {
|
||||||
pub current_action: Action,
|
pub current_action: Action,
|
||||||
|
pub create_shape: CreateShape,
|
||||||
}
|
}
|
||||||
impl Default for UiState {
|
impl Default for UiState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { current_action: Action::Modify }
|
Self {
|
||||||
|
current_action: Action::Modify,
|
||||||
|
create_shape: CreateShape::Square,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
impl UiState {
|
||||||
|
pub fn create_shape_color(&self, colors: &ButtonsColors, shape: CreateShape) -> egui::Rgba {
|
||||||
|
if self.create_shape == shape {
|
||||||
|
colors.clicked
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
colors.regular
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn current_action_color(&self, colors: &ButtonsColors, action: Action) -> egui::Rgba {
|
||||||
|
if self.current_action == action {
|
||||||
|
colors.clicked
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
colors.regular
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ButtonsColors {
|
pub struct ButtonsColors {
|
||||||
pub regular: egui::Rgba,
|
pub regular: egui::Rgba,
|
||||||
|
|
95
src/main.rs
95
src/main.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use bevy::ecs::schedule::ShouldRun;
|
||||||
use bevy::{prelude::*, window::PresentMode, winit::WinitSettings};
|
use bevy::{prelude::*, window::PresentMode, winit::WinitSettings};
|
||||||
use bevy_egui::{egui, EguiContext, EguiPlugin};
|
use bevy_egui::{egui, EguiContext, EguiPlugin};
|
||||||
use bevy_prototype_lyon::prelude::*;
|
use bevy_prototype_lyon::prelude::*;
|
||||||
|
@ -10,7 +11,9 @@ use shape_maker::*;
|
||||||
/// - toggling hidpi scaling (by pressing '/' button);
|
/// - toggling hidpi scaling (by pressing '/' button);
|
||||||
/// - configuring egui contexts during the startup.
|
/// - configuring egui contexts during the startup.
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
let mut app = App::new();
|
||||||
|
|
||||||
|
app
|
||||||
.insert_resource(ClearColor(Color::rgb(0.1, 0.1, 0.12)))
|
.insert_resource(ClearColor(Color::rgb(0.1, 0.1, 0.12)))
|
||||||
.insert_resource(Msaa { samples: 4 })
|
.insert_resource(Msaa { samples: 4 })
|
||||||
// Optimal power saving and present mode settings for desktop apps.
|
// Optimal power saving and present mode settings for desktop apps.
|
||||||
|
@ -22,12 +25,33 @@ fn main() {
|
||||||
})
|
})
|
||||||
.insert_resource(ButtonsColors::default())
|
.insert_resource(ButtonsColors::default())
|
||||||
.insert_resource(UiState::default())
|
.insert_resource(UiState::default())
|
||||||
|
;
|
||||||
|
|
||||||
|
app
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_plugin(EguiPlugin)
|
.add_plugin(EguiPlugin)
|
||||||
.add_plugin(ShapePlugin)
|
.add_plugin(ShapePlugin)
|
||||||
|
;
|
||||||
|
app
|
||||||
.add_startup_system(configure_visuals)
|
.add_startup_system(configure_visuals)
|
||||||
|
.add_startup_system(basic_setup_sys)
|
||||||
|
.add_system(create_sys.with_run_criteria(|state: Res<UiState>, mut ec: ResMut<EguiContext>|
|
||||||
|
if !ec.ctx_mut().is_pointer_over_area() && state.current_action == Action::Create { ShouldRun::Yes } else { ShouldRun::No }
|
||||||
|
))
|
||||||
|
.add_system(modify_sys.with_run_criteria(|state: Res<UiState>, mut ec: ResMut<EguiContext>|
|
||||||
|
if !ec.ctx_mut().is_pointer_over_area() && state.current_action == Action::Modify { ShouldRun::Yes } else { ShouldRun::No }
|
||||||
|
))
|
||||||
.add_system(action_bar_sys)
|
.add_system(action_bar_sys)
|
||||||
.run();
|
;
|
||||||
|
|
||||||
|
app.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn basic_setup_sys(
|
||||||
|
mut coms: Commands,
|
||||||
|
) {
|
||||||
|
coms.spawn_bundle(OrthographicCameraBundle::new_2d())
|
||||||
|
.insert(MainCamera);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_visuals(mut egui_ctx: ResMut<EguiContext>) {
|
fn configure_visuals(mut egui_ctx: ResMut<EguiContext>) {
|
||||||
|
@ -48,26 +72,75 @@ fn action_bar_sys(
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.show(egui_ctx.ctx_mut(), |ui| {
|
.show(egui_ctx.ctx_mut(), |ui| {
|
||||||
ui.horizontal(|hui| {
|
ui.horizontal(|hui| {
|
||||||
let modify_color = if let Action::Modify = state.current_action { colors.clicked } else { colors.regular };
|
let m = hui.button(egui::RichText::new("M").color(state.current_action_color(&colors, Action::Modify)))
|
||||||
let create_color = if let Action::Create = state.current_action { colors.clicked } else { colors.regular };
|
.on_hover_text("Modify");
|
||||||
let delete_color = if let Action::Delete = state.current_action { colors.clicked } else { colors.regular };
|
if m.clicked() {
|
||||||
|
|
||||||
if hui.button(egui::RichText::new("M").color(modify_color)).clicked() {
|
|
||||||
state.current_action = Action::Modify;
|
state.current_action = Action::Modify;
|
||||||
}
|
}
|
||||||
if hui.button(egui::RichText::new("C").color(create_color)).clicked() {
|
let c = hui.button(egui::RichText::new("C").color(state.current_action_color(&colors, Action::Create)))
|
||||||
|
.on_hover_text("Create");
|
||||||
|
if c.clicked() {
|
||||||
state.current_action = Action::Create;
|
state.current_action = Action::Create;
|
||||||
}
|
}
|
||||||
if hui.button(egui::RichText::new("D").color(delete_color)).clicked() {
|
let d = hui.button(egui::RichText::new("D").color(state.current_action_color(&colors, Action::Delete)))
|
||||||
|
.on_hover_text("Delete");
|
||||||
|
if d.clicked() {
|
||||||
state.current_action = Action::Delete;
|
state.current_action = Action::Delete;
|
||||||
}
|
}
|
||||||
hui.label(" | ");
|
hui.label(" | ");
|
||||||
|
|
||||||
if hui.button(" I ").clicked() {
|
if hui.button(" I ").on_hover_text("Import Image").clicked() {
|
||||||
println!("Image loading is still not supported!");
|
println!("Importing Images is still not supported!");
|
||||||
}
|
}
|
||||||
|
|
||||||
hui.label(": :");
|
hui.label(": :");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if state.current_action == Action::Create {
|
||||||
|
ui.horizontal(|hui| {
|
||||||
|
let tri = hui.button(egui::RichText::new("Tri").color(state.create_shape_color(&colors, CreateShape::Triangle)))
|
||||||
|
.on_hover_text("Triangle - from 3 edges");
|
||||||
|
if tri.clicked() {
|
||||||
|
state.create_shape = CreateShape::Triangle;
|
||||||
|
}
|
||||||
|
let squ = hui.button(egui::RichText::new("Squ").color(state.create_shape_color(&colors, CreateShape::Square)))
|
||||||
|
.on_hover_text("Square - from 2 opposing edges");
|
||||||
|
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() {
|
||||||
|
state.create_shape = CreateShape::Circle;
|
||||||
|
}
|
||||||
|
// let cap = hui.button(egui::RichText::new("Cap").color(state.create_shape_color(&colors, CreateShape::Capsule)))
|
||||||
|
// .on_hover_text("Capsule - from 2 center points and a radius");
|
||||||
|
// if cap.clicked() {
|
||||||
|
// state.create_shape = CreateShape::Capsule;
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn modify_sys(
|
||||||
|
mouse: Res<Input<MouseButton>>,
|
||||||
|
wnds: Res<Windows>,
|
||||||
|
q_cam: Query<(&Camera, &GlobalTransform), With<MainCamera>>,
|
||||||
|
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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue