cyborg/editor/src/main.rs

334 lines
11 KiB
Rust

use egui_wgpu_backend::wgpu;
use egui_wgpu_backend::RenderPass as EguiRenderPass;
use egui_winit::winit::{
self,
dpi::PhysicalSize,
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
use legion::EntityStore;
use std::sync::Arc;
mod render;
mod ui;
struct Application {
window: winit::window::Window,
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
size: winit::dpi::PhysicalSize<u32>,
surface: wgpu::Surface,
config: wgpu::SurfaceConfiguration,
egui_state: egui_winit::State,
egui_ctx: egui::Context,
egui_rp: EguiRenderPass,
ui: ui::UserInterface,
viewport: ui::ViewportWidget,
render_state: render::RenderState,
file_receiver: crossbeam_channel::Receiver<ui::FileEvent>,
objects: Vec<ui::ObjectWidget>,
}
impl Application {
pub async fn new(window: winit::window::Window) -> Self {
let size = window.inner_size();
let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY);
let surface = unsafe { instance.create_surface(&window) };
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await
.unwrap();
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
limits: wgpu::Limits::default(),
label: None,
},
None,
)
.await
.unwrap();
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface.get_preferred_format(&adapter).unwrap(),
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Mailbox,
};
surface.configure(&device, &config);
let device = Arc::new(device);
let queue = Arc::new(queue);
let egui_state = egui_winit::State::new(4096, &window);
let egui_ctx = egui::Context::default();
let mut egui_rp = egui_wgpu_backend::RenderPass::new(&device, config.format, 1);
let render_state =
render::RenderState::new(device.clone(), queue.clone(), config.format, &mut egui_rp);
let viewport_texture = render_state
.resources
.get::<render::ViewportStore>()
.unwrap()
.viewport
.egui_texture;
let (file_sender, file_receiver) = crossbeam_channel::unbounded();
Self {
window,
device,
queue,
size,
surface,
config,
egui_state,
egui_ctx,
egui_rp,
ui: ui::UserInterface::new(file_sender),
viewport: ui::ViewportWidget::new(viewport_texture),
render_state,
file_receiver,
objects: vec![],
}
}
pub fn update(&mut self) {
self.window.request_redraw();
loop {
match self.file_receiver.try_recv() {
Ok(ui::FileEvent::Save) => println!("Saving!"),
Ok(ui::FileEvent::SaveAs(path)) => println!("Saving as: {:?}", path),
Ok(ui::FileEvent::Import(kind, path)) => match kind {
ui::ImportKind::Stl => {
self.load_stl(path);
}
},
Err(crossbeam_channel::TryRecvError::Empty) => break,
Err(crossbeam_channel::TryRecvError::Disconnected) => {
panic!("File event sender hung up!");
}
}
}
for object in self.objects.iter_mut() {
object.flush_dirty(|object| {
let translation = glam::Vec3::from_slice(&object.position);
let rotation = glam::Quat::from_euler(
glam::EulerRot::XYZ,
object.rotation[0],
object.rotation[1],
object.rotation[2],
);
let scale = glam::Vec3::splat(object.scale);
let transform =
glam::Mat4::from_scale_rotation_translation(scale, rotation, translation);
self.render_state
.world
.entry_mut(object.entity)
.unwrap()
.get_component_mut::<cyborg::scene::Transform>()
.unwrap()
.transform = transform;
});
}
}
pub fn load_stl(&mut self, path: std::path::PathBuf) {
let root_name = path
.file_name()
.map(|v| v.to_string_lossy().to_string())
.unwrap_or("<unnamed>".to_string());
use cyborg::pass::{
mesh::{Index, MeshPass, Vertex},
RenderPassBox,
};
use cyborg::storage::mesh::{AttrBuffer, MeshBuffer};
eprintln!("loading {:?}", path);
let mut file = std::fs::File::open(path).unwrap();
let stl = stl::read_stl(&mut file).unwrap();
let mut vertices = Vec::new();
let mut indices = Vec::new();
for tri in stl.triangles.iter() {
let tan_frame = 0; // TODO make from normal
indices.push(vertices.len() as Index);
vertices.push(Vertex {
position: tri.v1,
tan_frame,
});
indices.push(vertices.len() as Index);
vertices.push(Vertex {
position: tri.v2,
tan_frame,
});
indices.push(vertices.len() as Index);
vertices.push(Vertex {
position: tri.v3,
tan_frame,
});
}
let mesh_pass = self
.render_state
.resources
.get::<RenderPassBox<MeshPass>>()
.unwrap();
let attributes = mesh_pass.get_attributes();
let vertices = AttrBuffer {
id: attributes.vertex,
count: vertices.len(),
data: bytemuck::cast_slice(&vertices).to_vec(),
};
let indices = AttrBuffer {
id: attributes.index,
count: indices.len(),
data: bytemuck::cast_slice(&indices).to_vec(),
};
let mut mesh = MeshBuffer::default();
mesh.attributes.push(vertices);
mesh.attributes.push(indices);
let entity = self.render_state.world.push((
cyborg::scene::Mesh {
mesh: mesh_pass.get_mesh_pool().load(mesh).unwrap(),
},
cyborg::scene::Transform {
transform: Default::default(),
},
));
let object = ui::ObjectWidget {
name: root_name,
position: [0.0; 3],
rotation: [0.0; 3],
scale: 1.0,
entity,
dirty: false,
};
self.objects.push(object);
}
pub fn on_resize(&mut self, new_size: PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
self.config.width = new_size.width;
self.config.height = new_size.height;
self.surface.configure(&self.device, &self.config);
}
}
pub fn render(&mut self) {
match self.surface.get_current_texture() {
Err(wgpu::SurfaceError::Lost) => self.on_resize(self.size),
Err(e) => panic!("Surface error: {:?}", e),
Ok(surface_texture) => {
puffin::GlobalProfiler::lock().new_frame();
let output = {
puffin::profile_scope!("Draw egui");
let raw_input = self.egui_state.take_egui_input(&self.window);
self.egui_ctx.run(raw_input, |ctx| {
self.ui.run(ctx, &mut self.viewport, &mut self.objects)
})
};
{
puffin::profile_scope!("Main render");
self.render_state
.update_viewport(&mut self.egui_rp, &mut self.viewport);
self.render_state.render();
}
puffin::profile_scope!("Render egui");
self.egui_state.handle_platform_output(
&self.window,
&self.egui_ctx,
output.platform_output,
);
let meshes = self.egui_ctx.tessellate(output.shapes);
let screen_desc = egui_wgpu_backend::ScreenDescriptor {
physical_width: self.config.width,
physical_height: self.config.height,
scale_factor: 1.0, // TODO ???
};
self.egui_rp
.update_buffers(&self.device, &self.queue, &meshes, &screen_desc);
self.egui_rp
.add_textures(&self.device, &self.queue, &output.textures_delta)
.unwrap();
let output_view = surface_texture.texture.create_view(&Default::default());
let mut cmds = self.device.create_command_encoder(&Default::default());
self.egui_rp
.execute(&mut cmds, &output_view, &meshes, &screen_desc, None)
.unwrap();
self.queue.submit(std::iter::once(cmds.finish()));
surface_texture.present();
self.egui_rp.remove_textures(output.textures_delta).unwrap();
}
}
}
}
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Cyborg Editor")
.build(&event_loop)
.unwrap();
let mut app = pollster::block_on(Application::new(window));
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::RedrawRequested(window_id) if window_id == app.window.id() => {
app.render();
}
Event::MainEventsCleared => {
app.update();
if app.ui.should_quit() {
*control_flow = ControlFlow::Exit;
}
}
Event::WindowEvent { event, window_id } if window_id == app.window.id() => {
match &event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(physical_size) => {
app.on_resize(*physical_size);
}
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
app.on_resize(**new_inner_size);
}
_ => {}
}
app.egui_state.on_event(&app.egui_ctx, &event);
}
_ => (),
}
});
}