Implement offscreen rendering

This commit is contained in:
hodasemi 2023-05-15 07:14:38 +02:00
parent 64d474694f
commit 13a912d228
3 changed files with 365 additions and 286 deletions

View file

@ -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<Image>,
pub offscreen_image_size: Vec2,
pub offscreen_render_layer: RenderLayers,
camera_3d: Option<Entity>,
camera_2d: Option<Entity>,
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<Image>,
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()]

View file

@ -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<GlobalState>) {
global_state.setup_2d_cam(&mut cmd);
global_state.setup_3d_cam(&mut cmd);
// Creates GlobalState
fn setup(
mut cmd: Commands,
mut images: ResMut<Assets<Image>>,
mut egui_user_textures: ResMut<EguiUserTextures>,
) {
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<Assets<StandardMaterial>>,
mut color_materials: ResMut<Assets<ColorMaterial>>,
) {
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<ObjectInfos>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut color_materials: ResMut<Assets<ColorMaterial>>,
mut standard_materials: ResMut<Assets<StandardMaterial>>,
) {
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<WindowResized>,
mut global_state: ResMut<GlobalState>,
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);
}
}
}

View file

@ -14,10 +14,16 @@ struct CollectionIDs {
lines: Vec<Entity>,
}
#[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<Entity>),
}
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<Entity> {
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<Vec2>,
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<Assets<Mesh>>,
// materials: &mut ResMut<Assets<StandardMaterial>>,
// ) {
// 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<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,
// global_state: &GlobalState,
// commands: &mut Commands,
// meshes: &mut ResMut<Assets<Mesh>>,
// materials: &mut ResMut<Assets<ColorMaterial>>,
// ) {
// 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<Assets<Mesh>>,
materials: &mut ResMut<Assets<StandardMaterial>>,
) {
debug_assert!(self.state.is_2d());
color_materials: &mut ResMut<Assets<ColorMaterial>>,
standard_materials: &mut ResMut<Assets<StandardMaterial>>,
) -> 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<Vec3> = 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<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
) {
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<Assets<Mesh>>,
materials: &mut ResMut<Assets<ColorMaterial>>,
) -> 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,
}
}