Implement view swap
This commit is contained in:
parent
d1a3b9d734
commit
5b11cd45eb
4 changed files with 283 additions and 20 deletions
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"workbench.colorCustomizations": {
|
||||||
|
"activityBar.background": "#3D2A09",
|
||||||
|
"titleBar.activeBackground": "#553B0C",
|
||||||
|
"titleBar.activeForeground": "#FDFAF4"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,67 @@
|
||||||
use bevy::prelude::*;
|
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 struct GlobalState {
|
||||||
pub cursor_position: Vec2,
|
pub cursor_position: Vec2,
|
||||||
pub window_extent: Vec2,
|
pub window_extent: Vec2,
|
||||||
pub polygon_started: bool,
|
pub polygon_started: bool,
|
||||||
pub view_2d: bool,
|
pub view_2d: bool,
|
||||||
|
pub extrusion_height: f32,
|
||||||
|
|
||||||
|
pub camera: Option<Camera>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
impl Default for GlobalState {
|
||||||
|
@ -15,6 +71,9 @@ impl Default for GlobalState {
|
||||||
window_extent: Vec2::default(),
|
window_extent: Vec2::default(),
|
||||||
polygon_started: false,
|
polygon_started: false,
|
||||||
view_2d: true,
|
view_2d: true,
|
||||||
|
extrusion_height: 20.0,
|
||||||
|
|
||||||
|
camera: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
36
src/main.rs
36
src/main.rs
|
@ -26,8 +26,8 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawns the camera
|
// Spawns the camera
|
||||||
fn setup_camera(mut cmd: Commands) {
|
fn setup_camera(mut cmd: Commands, mut global_state: ResMut<GlobalState>) {
|
||||||
cmd.spawn(Camera2dBundle::default());
|
global_state.setup_2d_cam(&mut cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create UI
|
// Create UI
|
||||||
|
@ -35,6 +35,10 @@ fn update_ui_side_panel(
|
||||||
mut contexts: EguiContexts,
|
mut contexts: EguiContexts,
|
||||||
mut global_state: ResMut<GlobalState>,
|
mut global_state: ResMut<GlobalState>,
|
||||||
mut object_infos: ResMut<ObjectInfos>,
|
mut object_infos: ResMut<ObjectInfos>,
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut standard_materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
mut color_materials: ResMut<Assets<ColorMaterial>>,
|
||||||
) {
|
) {
|
||||||
egui::SidePanel::right("2D View")
|
egui::SidePanel::right("2D View")
|
||||||
.default_width(400.0)
|
.default_width(400.0)
|
||||||
|
@ -43,17 +47,31 @@ fn update_ui_side_panel(
|
||||||
if ui.button("Change to 3D View").clicked() {
|
if ui.button("Change to 3D View").clicked() {
|
||||||
global_state.view_2d = false;
|
global_state.view_2d = false;
|
||||||
|
|
||||||
object_infos
|
object_infos.iter_mut().for_each(|polygon| {
|
||||||
.iter_mut()
|
polygon.swap_to_3d(
|
||||||
.for_each(|polygon| polygon.swap_to_3d());
|
&global_state,
|
||||||
|
&mut commands,
|
||||||
|
&mut meshes,
|
||||||
|
&mut standard_materials,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
global_state.setup_3d_cam(&mut commands);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ui.button("Change to 2D View").clicked() {
|
if ui.button("Change to 2D View").clicked() {
|
||||||
global_state.view_2d = true;
|
global_state.view_2d = true;
|
||||||
|
|
||||||
object_infos
|
object_infos.iter_mut().for_each(|polygon| {
|
||||||
.iter_mut()
|
polygon.swap_to_2d(
|
||||||
.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 {
|
MouseButton::Left => match event.state {
|
||||||
ButtonState::Pressed => {
|
ButtonState::Pressed => {
|
||||||
// only add new points if "New Polygon" button got 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() {
|
let polygon = match object_infos.active_polygon() {
|
||||||
Some(polygon) => polygon,
|
Some(polygon) => polygon,
|
||||||
None => object_infos.add_polygon(),
|
None => object_infos.add_polygon(),
|
||||||
|
|
199
src/polygon.rs
199
src/polygon.rs
|
@ -38,7 +38,7 @@ impl Default for PolygonState {
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum PolygonView {
|
enum PolygonView {
|
||||||
TwoDimensional(PolygonState),
|
TwoDimensional(PolygonState),
|
||||||
ThreeDimensional(Entity),
|
ThreeDimensional(Option<Entity>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PolygonView {
|
impl PolygonView {
|
||||||
|
@ -52,6 +52,20 @@ impl PolygonView {
|
||||||
fn is_3d(&self) -> bool {
|
fn is_3d(&self) -> bool {
|
||||||
!self.is_2d()
|
!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<Entity> {
|
||||||
|
match self {
|
||||||
|
PolygonView::TwoDimensional(_) => panic!("called 3d on 2d!"),
|
||||||
|
PolygonView::ThreeDimensional(t) => t,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PolygonView {
|
impl Default for PolygonView {
|
||||||
|
@ -69,16 +83,177 @@ pub struct Polygon {
|
||||||
impl Polygon {
|
impl Polygon {
|
||||||
const PROXIMITY_THRESHOLD: f32 = 10.0;
|
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<Assets<Mesh>>,
|
||||||
|
materials: &mut ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
debug_assert!(self.state.is_2d());
|
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<Vec3> = 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<Assets<Mesh>>,
|
||||||
|
materials: &mut ResMut<Assets<ColorMaterial>>,
|
||||||
|
) {
|
||||||
debug_assert!(self.state.is_3d());
|
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] {
|
pub fn points(&self) -> &[Vec2] {
|
||||||
|
@ -101,7 +276,7 @@ impl Polygon {
|
||||||
if self.points.len() >= 3 && self.check_first_for_proximity(point) {
|
if self.points.len() >= 3 && self.check_first_for_proximity(point) {
|
||||||
// remove points and lines from collection state
|
// 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() {
|
for point_id in collection_ids.points.iter() {
|
||||||
commands.entity(*point_id).despawn();
|
commands.entity(*point_id).despawn();
|
||||||
|
@ -126,14 +301,14 @@ impl Polygon {
|
||||||
.id();
|
.id();
|
||||||
|
|
||||||
// store triangle entity id in state
|
// store triangle entity id in state
|
||||||
self.state = PolygonState::Finished(triangle_id);
|
*self.state.two_dimensional_mut() = PolygonState::Finished(triangle_id);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// deny too close points
|
// deny too close points
|
||||||
if !self.check_all_for_proximity(point) {
|
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
|
// create point mesh at click position
|
||||||
let point_id = commands
|
let point_id = commands
|
||||||
|
@ -180,8 +355,11 @@ impl Polygon {
|
||||||
|
|
||||||
pub fn finished(&self) -> bool {
|
pub fn finished(&self) -> bool {
|
||||||
match &self.state {
|
match &self.state {
|
||||||
PolygonState::Collection(_) => false,
|
PolygonView::TwoDimensional(t) => match t {
|
||||||
PolygonState::Finished(_) => true,
|
PolygonState::Collection(_) => false,
|
||||||
|
PolygonState::Finished(_) => true,
|
||||||
|
},
|
||||||
|
PolygonView::ThreeDimensional(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +378,7 @@ impl Polygon {
|
||||||
fn create_triangulated_mesh(&self) -> Mesh {
|
fn create_triangulated_mesh(&self) -> Mesh {
|
||||||
let vertices: Vec<_> = self.points.iter().map(|p| p.extend(0.0)).collect();
|
let vertices: Vec<_> = self.points.iter().map(|p| p.extend(0.0)).collect();
|
||||||
|
|
||||||
|
// TODO: proper ear cutting algo
|
||||||
let indices = Indices::U32({
|
let indices = Indices::U32({
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue