pub mod camera; mod render; mod body_render; mod fleet_render; mod orbit_render; use std::time::Duration; use cgmath::InnerSpace; use winit::event::ElementState; use winit::keyboard::KeyCode; use crate::fleet::FleetsManager; use crate::solar_system::SolarSystem; use crate::solar_system::SystemId; use crate::texture::Texture; use crate::timeman::Second; use crate::timeman::TimeMan; use crate::ui; use crate::wgpuctx::RenderPassBuilder; use crate::wgpuctx::SceneCtx; use crate::wgpuctx::WgpuCtx; use crate::wgpuctx::pipeline::RenderPipelineBuilder; use render::*; use body_render::*; use fleet_render::*; use orbit_render::*; use camera::*; pub struct TacticalMap { canvas_size: winit::dpi::PhysicalSize, canvas_texture: Option, canvas_pipeline: wgpu::RenderPipeline, canvas_buffer: wgpu::Buffer, canvas_bindgroup: wgpu::BindGroup, camera: Camera, pmatrix: Projection, camera_controller: CameraController, system_for_render: Option, body_renderer: BodyRenderer, fleet_renderer: FleetRenderer, grid_renderer: GridRenderer, orbit_renderer: OrbitRenderer } impl TacticalMap { fn bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout { device.create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: None, entries: &[ wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, count: None, } ] } ) } pub fn new(wgpuctx: &WgpuCtx) -> Self { let surface_size = winit::dpi::PhysicalSize::new( wgpuctx.surface_config().width, wgpuctx.surface_config().height ); let canvas_shader = wgpuctx.create_shader( wgpu::include_wgsl!(concat!( env!("CARGO_MANIFEST_DIR"), "/assets/shaders/canvas.wgsl") )); let canvas_texture_bindgroup_layout = Texture::bindgroup_layout(wgpuctx.device()); let canvas_position_bindgroup_layout = Self::bindgroup_layout(wgpuctx.device()); let canvas_pipeline = RenderPipelineBuilder::new(&canvas_shader) .primitive_topology(wgpu::PrimitiveTopology::TriangleStrip) .cull_mode(None) .add_bindgroup(&canvas_texture_bindgroup_layout) .add_bindgroup(&canvas_position_bindgroup_layout) .build(Some("Tactical map canvas pipeline"), wgpuctx); let canvas_buffer = wgpuctx.create_buffer( &wgpu::BufferDescriptor { label: None, size: 4 * std::mem::size_of::() as u64, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, } ); let canvas_bindgroup = wgpuctx.device().create_bind_group( &wgpu::BindGroupDescriptor { label: None, layout: &canvas_position_bindgroup_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: canvas_buffer.as_entire_binding(), }], } ); let camera = Camera::new( wgpuctx, cgmath::Vector3::new(0.0, 0.0, 1.0), cgmath::Deg(0.0), cgmath::Rad(0.0)); let projection = Projection::new( surface_size.width, surface_size.height, cgmath::Deg(60.0), (0.01, 1000.0)); Self { canvas_size: winit::dpi::PhysicalSize::new(0, 0), canvas_texture: None, canvas_pipeline: canvas_pipeline, canvas_buffer: canvas_buffer, canvas_bindgroup: canvas_bindgroup, camera: camera, pmatrix: projection, camera_controller: CameraController::new(), system_for_render: None, body_renderer: BodyRenderer::new(wgpuctx), fleet_renderer: FleetRenderer::new(wgpuctx), grid_renderer: GridRenderer::new(wgpuctx), orbit_renderer: OrbitRenderer::new(wgpuctx), } } pub fn resize( &mut self, width: u32, height: u32) { let new_size = winit::dpi::PhysicalSize::new(width, height); if self.canvas_size != new_size { self.pmatrix.resize(width, height); self.canvas_size = new_size; self.canvas_texture = None; } } pub fn update( &mut self, solar_system: &SolarSystem, ui_state: &mut ui::State, time: Second, dt: Duration) { self.camera.set_target(ui_state.camera_target); self.camera_controller.update(&mut self.camera, solar_system, ui_state, time, dt); } pub fn keyboard_input( &mut self, key_code: KeyCode, key_state: ElementState) { self.camera_controller.keyboard_input(key_code, key_state); } pub fn prepare( &mut self, rect: egui::Rect, wgpuctx: &WgpuCtx, fleets_man: &FleetsManager, solar_system: &SolarSystem, timeman: &TimeMan, ) -> Result<(), ()> { //Prepare canvas if self.canvas_texture.is_none() { println!("Rebuilding canvas texture to {:?}", rect); self.canvas_texture = Some(wgpuctx.new_texture( wgpu::TextureDescriptor { label: None, size: wgpu::Extent3d { width: self.canvas_size.width, height: self.canvas_size.height, depth_or_array_layers: 1 }, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: wgpuctx.surface_config().format, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &wgpuctx.surface_config().view_formats } )); } self.camera.update_buffer(wgpuctx, &self.pmatrix); if self.system_for_render.is_none() || self.system_for_render.unwrap() != solar_system.id() { self.body_renderer.mark_to_rebuild(); self.orbit_renderer.mark_to_rebuild(); } self.system_for_render = Some(solar_system.id()); //Update buffers for the current system and time. self.body_renderer.rebuild(wgpuctx, solar_system); self.orbit_renderer.rebuild(wgpuctx, solar_system); match self.orbit_renderer.update( wgpuctx, fleets_man, solar_system, timeman.seconds() ){ Ok(()) => {}, Err(e) => println!("Tactical map orbit render update error: {}", e) } match self.body_renderer.update( wgpuctx, solar_system, timeman.seconds()) { Ok(()) => {}, Err(e) => println!("Tactical map body render update error: {}", e) } match self.fleet_renderer.update( wgpuctx, fleets_man, solar_system, timeman.seconds()) { Ok(()) => {}, Err(e) => println!("Tactical map fleet render update error: {}", e) } let window_width = wgpuctx.surface_config().width as f32; let window_height = wgpuctx.surface_config().height as f32; wgpuctx.queue().write_buffer(&self.canvas_buffer, 0, bytemuck::cast_slice(&[ rect.min.x / window_width, rect.min.y / window_height, rect.width() / window_width, rect.height() / window_height ])); let canvas_view = self.canvas_texture.as_ref().unwrap().view(); let mut canvas_scene = SceneCtx::from_view_default(wgpuctx, canvas_view, Some("Tactical map canvas scene")); self.camera.stage_changes(canvas_scene.encoder_mut()); { let mut pass = RenderPassBuilder::new(Some("Tactical map render pass"), canvas_view) .clear_color(wgpu::Color { r: 0.0, g: 0.0, b: 0.1, a: 1.0 }) .build_from_scene(&mut canvas_scene); self.grid_renderer.render( &mut pass, &self.camera); self.orbit_renderer.render(&mut pass, &self.camera); self.fleet_renderer.render(&mut pass, &self.camera); self.body_renderer.render( &mut pass, &self.camera); } canvas_scene.submit(wgpuctx); Ok(()) } pub fn paint( &self, pass: &mut wgpu::RenderPass<'_>) { pass.set_pipeline(&self.canvas_pipeline); self.canvas_texture.as_ref().unwrap().use_on_pass(pass, 0); pass.set_bind_group(1, &self.canvas_bindgroup, &[]); pass.draw(0..4, 0..1); } pub fn paint_labels( &self, solar_system: &SolarSystem, time: Second, screen_size: egui::Vec2, ui: &mut egui::Ui) { let view_matrix = self.camera.view_matrix(); let proj_matrix = self.pmatrix.projection_matrix(); let pv_matrix = proj_matrix * view_matrix; //Paint body labels let bodies = solar_system.bodies(); bodies.iter().for_each(|body| { let scaled_radius = (screen_size.y * body.radius() * self.camera.get_scale()).max(16.0); let offset_pos = body.relative_position(time); let origin_pos = match body.get_orbit() { Some(orbit) => { solar_system.body_position(&bodies[orbit.parent()], time) }, None => cgmath::vec3(0.0, 0.0, 0.0) }; let origin_local_pos = origin_pos - self.camera.get_combined_position(); let offset_pos_scaled = offset_pos * self.camera.get_scale() as f64; let origin_pos_scaled = origin_local_pos * self.camera.get_scale() as f64; let origin_clip_pos = pv_matrix * origin_pos_scaled.map(|v| { v as f32 }).extend(1.0); let offset_clip_pos = pv_matrix * offset_pos_scaled.map(|v| { v as f32}).extend(0.0); let clip_pos = origin_clip_pos + offset_clip_pos; let ndc_pos = clip_pos.truncate() / clip_pos.w; let offset_ndc = offset_clip_pos.truncate() / origin_clip_pos.w; if offset_ndc.truncate().magnitude() < 0.1 { if offset_pos != cgmath::vec3(0.0, 0.0, 0.0) { return; } } let screen_pos = egui::pos2( ((ndc_pos.x + 1.0) * 0.5) * screen_size.x, (((-ndc_pos.y + 1.0) * 0.5) * screen_size.y) + (scaled_radius / clip_pos.w).max(16.0)); if clip_pos.z < 0.0 { return; } ui.put( egui::Rect::from_pos(screen_pos).expand(64.0), egui::Label::new( egui::RichText::new(body.name()) .color(egui::Color32::WHITE) ).selectable(false).sense(egui::Sense::empty())); }); } } // impl SystemViewport