diff --git a/src/global_state.rs b/src/global_state.rs index 9bd90f4..f504f3c 100644 --- a/src/global_state.rs +++ b/src/global_state.rs @@ -1,6 +1,6 @@ -use bevy::prelude::*; +use bevy::{prelude::*, render::view::RenderLayers}; -#[derive(Resource, Debug, PartialEq, Clone)] +#[derive(Resource, Debug, Clone)] pub struct GlobalState { pub cursor_position: Vec2, pub window_extent: Vec2, @@ -8,39 +8,22 @@ pub struct GlobalState { pub view_2d: bool, pub extrusion_height: f32, - pub offscreen_image + pub offscreen_image: Handle, + pub offscreen_image_size: Vec2, + pub offscreen_render_layer: RenderLayers, - camera_3d: Option, - camera_2d: Option, + pub camera_3d: Entity, + pub camera_2d: Entity, } impl GlobalState { - pub fn setup_2d_cam(&mut self, cmd: &mut Commands) { - self.camera_2d = Some(cmd.spawn(Camera2dBundle::default()).id()); - } - - pub fn setup_3d_cam(&mut self, cmd: &mut Commands) { - self.camera_3d = Some( - cmd.spawn(Camera3dBundle { - transform: Transform::from_xyz(200.0, -600.0, 600.0) - .looking_at((self.window_extent * 0.5).extend(0.0), Vec3::Z), - ..default() - }) - .id(), - ); - } - - pub fn camera_2d(&self) -> Entity { - self.camera_2d.unwrap() - } - - pub fn camera_3d(&self) -> Entity { - self.camera_3d.unwrap() - } -} - -impl Default for GlobalState { - fn default() -> Self { + pub fn new( + camera_2d: Entity, + camera_3d: Entity, + offscreen_image: Handle, + offscreen_image_size: Vec2, + offscreen_render_layer: RenderLayers, + ) -> Self { Self { cursor_position: Vec2::default(), window_extent: Vec2::default(), @@ -48,10 +31,12 @@ impl Default for GlobalState { view_2d: true, extrusion_height: 20.0, - camera_3d: None, - camera_2d: None, + offscreen_image, + offscreen_image_size, + offscreen_render_layer, + + camera_3d, + camera_2d, } } } - -#[derive()] diff --git a/src/main.rs b/src/main.rs index 5bf460e..d537017 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,17 @@ use bevy::{ ButtonState, }, prelude::*, + render::{ + camera::RenderTarget, + render_resource::{ + Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + }, + view::RenderLayers, + }, window::WindowResized, }; -use bevy_egui::{egui, EguiContexts, EguiPlugin}; +use bevy_egui::{egui, EguiContexts, EguiPlugin, EguiUserTextures}; use bevy_inspector_egui::quick::WorldInspectorPlugin; use global_state::GlobalState; use polygon::ObjectInfos; @@ -20,17 +27,77 @@ fn main() { .add_plugins(DefaultPlugins) .add_plugin(EguiPlugin) .add_plugin(WorldInspectorPlugin::new()) - .insert_resource(GlobalState::default()) .insert_resource(ObjectInfos::default()) - .add_startup_system(setup_cameras) + .add_startup_system(setup) .add_systems((mouse_event_system, update_ui_side_panel, on_resize_system)) .run(); } -// Spawns the camera -fn setup_cameras(mut cmd: Commands, mut global_state: ResMut) { - global_state.setup_2d_cam(&mut cmd); - global_state.setup_3d_cam(&mut cmd); +// Creates GlobalState +fn setup( + mut cmd: Commands, + mut images: ResMut>, + mut egui_user_textures: ResMut, +) { + let offscreen_image_size = Vec2::new(256.0, 256.0); + + let size = Extent3d { + width: offscreen_image_size.x as u32, + height: offscreen_image_size.y as u32, + ..default() + }; + + // This is the texture that will be rendered to. + let mut image = Image { + texture_descriptor: TextureDescriptor { + label: None, + size, + dimension: TextureDimension::D2, + format: TextureFormat::Bgra8UnormSrgb, + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }, + ..default() + }; + + // fill image.data with zeroes + image.resize(size); + + let image_handle = images.add(image); + egui_user_textures.add_image(image_handle.clone()); + + // This specifies the layer used for the offscreen image, which will be attached to the first pass camera. + let offscreen_render_layer = RenderLayers::layer(1); + + let camera_2d = cmd.spawn(Camera2dBundle::default()).id(); + + let camera_3d = cmd + .spawn(( + Camera3dBundle { + camera: Camera { + target: RenderTarget::Image(image_handle.clone()), + ..default() + }, + transform: Transform::from_xyz(200.0, -600.0, 600.0) + .looking_at(Vec3::ZERO, Vec3::Z), + + ..default() + }, + offscreen_render_layer, + )) + .id(); + + cmd.insert_resource(GlobalState::new( + camera_2d, + camera_3d, + image_handle, + offscreen_image_size, + offscreen_render_layer, + )); } // Create UI @@ -43,44 +110,44 @@ fn update_ui_side_panel( mut standard_materials: ResMut>, mut color_materials: ResMut>, ) { + let offscreen_texture_id = contexts.image_id(&global_state.offscreen_image).unwrap(); + egui::SidePanel::right("2D View") .default_width(400.0) .show(contexts.ctx_mut(), |ui| { + ui.add(egui::widgets::Image::new( + offscreen_texture_id, + bevy_egui::egui::Vec2::new( + global_state.offscreen_image_size.x, + global_state.offscreen_image_size.y, + ), + )); + if global_state.view_2d { if ui.button("Change to 3D View").clicked() { global_state.view_2d = false; - 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); - - debug_assert!(global_state.camera.is_some()); - debug_assert!(global_state.camera.as_ref().unwrap().is_3d()); + // object_infos.iter_mut().for_each(|polygon| { + // polygon.swap_to_3d( + // &global_state, + // &mut commands, + // &mut meshes, + // &mut standard_materials, + // ) + // }); } } 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( - &global_state, - &mut commands, - &mut meshes, - &mut color_materials, - ) - }); - - global_state.setup_2d_cam(&mut commands); - - debug_assert!(global_state.camera.is_some()); - debug_assert!(global_state.camera.as_ref().unwrap().is_2d()); + // object_infos.iter_mut().for_each(|polygon| { + // polygon.swap_to_2d( + // &global_state, + // &mut commands, + // &mut meshes, + // &mut color_materials, + // ) + // }); } } @@ -109,7 +176,8 @@ fn mouse_event_system( mut object_infos: ResMut, mut commands: Commands, mut meshes: ResMut>, - mut materials: ResMut>, + mut color_materials: ResMut>, + mut standard_materials: ResMut>, ) { for event in mouse_button_input_events.iter() { match event.button { @@ -126,7 +194,8 @@ fn mouse_event_system( &global_state, &mut commands, &mut meshes, - &mut materials, + &mut color_materials, + &mut standard_materials, ) { global_state.polygon_started = false; } @@ -153,9 +222,15 @@ fn mouse_event_system( fn on_resize_system( mut resize_reader: EventReader, mut global_state: ResMut, + mut camera: Query<&mut Transform, &Camera3d>, ) { for e in resize_reader.iter() { // When resolution is being changed global_state.window_extent = Vec2::new(e.width, e.height); + + for mut transform in &mut camera { + *transform = Transform::from_xyz(200.0, -600.0, 600.0) + .looking_at((global_state.window_extent * 0.5).extend(0.0), Vec3::Z); + } } } diff --git a/src/polygon.rs b/src/polygon.rs index 2e83d96..ccc4778 100644 --- a/src/polygon.rs +++ b/src/polygon.rs @@ -14,10 +14,16 @@ struct CollectionIDs { lines: Vec, } +#[derive(Debug, PartialEq)] +struct FinishedIDs { + plane: Entity, + object: Entity, +} + #[derive(Debug, PartialEq)] enum PolygonState { Collection(CollectionIDs), - Finished(Entity), + Finished(FinishedIDs), } impl PolygonState { @@ -35,84 +41,234 @@ impl Default for PolygonState { } } -#[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, + state: PolygonState, } impl Polygon { const PROXIMITY_THRESHOLD: f32 = 10.0; - pub fn swap_to_3d( + // 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>, - ) { - debug_assert!(self.state.is_2d()); + color_materials: &mut ResMut>, + standard_materials: &mut ResMut>, + ) -> bool { + debug_assert_eq!(self.finished(), false); - // 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(); + 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.collection_mut(); + + for point_id in collection_ids.points.iter() { + commands.entity(*point_id).despawn(); } - for line in collection_ids.lines.iter() { - commands.entity(*line).despawn(); + for line_id in collection_ids.lines.iter() { + commands.entity(*line_id).despawn(); } - - false } - PolygonState::Finished(mesh) => { - commands.entity(*mesh).despawn(); - true - } - }; + // create triangle mesh + let triangle_mesh = self.create_triangulated_mesh(); - self.state = PolygonView::ThreeDimensional(if finished { + 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: color_materials.add(ColorMaterial::from(Color::BLACK)), + ..default() + }) + .id(); + + // create 3d object let vertices: Vec = self .points .iter() @@ -152,163 +308,29 @@ impl Polygon { 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() - }) + .spawn(( + MaterialMeshBundle { + mesh: meshes.add(mesh).into(), + transform: Transform::default(), + material: standard_materials.add(StandardMaterial::from(Color::BLACK)), + ..default() + }, + global_state.offscreen_render_layer, + )) .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); + // store finished entities in state + self.state = PolygonState::Finished(FinishedIDs { + plane: triangle_id, + object: mesh_id, + }); return true; } // deny too close points if !self.check_all_for_proximity(point) { - let collection_ids = self.state.two_dimensional_mut().collection_mut(); + let collection_ids = self.state.collection_mut(); // create point mesh at click position let point_id = commands @@ -320,7 +342,7 @@ impl Polygon { (global_state.cursor_position - (global_state.window_extent * 0.5)) .extend(0.0), ), - material: materials.add(ColorMaterial::from(Color::BLACK)), + material: color_materials.add(ColorMaterial::from(Color::BLACK)), ..default() }) .id(); @@ -339,7 +361,7 @@ impl Polygon { (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)), + material: color_materials.add(ColorMaterial::from(Color::BLACK)), ..default() }) .id(); @@ -355,11 +377,8 @@ impl Polygon { pub fn finished(&self) -> bool { match &self.state { - PolygonView::TwoDimensional(t) => match t { - PolygonState::Collection(_) => false, - PolygonState::Finished(_) => true, - }, - PolygonView::ThreeDimensional(_) => false, + PolygonState::Collection(_) => false, + PolygonState::Finished(_) => true, } }