use std::{cell::RefCell, time::Duration}; use cgmath::{EuclideanSpace, InnerSpace, Point3, Rad, Vector2, Vector3, Vector4, Zero, perspective}; use winit::{event::ElementState, keyboard::KeyCode}; use crate::{GameState, solar_system, wgpuctx::WgpuCtx, window::ui::GameWindowUiState}; pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4 = cgmath::Matrix4::from_cols( Vector4::new(1.0, 0.0, 0.0, 0.0), Vector4::new(0.0, 1.0, 0.0, 0.0), Vector4::new(0.0, 0.0, 0.5, 0.0), Vector4::new(0.0, 0.0, 0.5, 1.0) ); pub struct Camera { abs_position: Point3, rel_position: Point3, pitch: Rad, yaw: Rad, scale: f32, target: Option, buffer: wgpu::Buffer, staging_buffer: wgpu::Buffer, bindgroup: wgpu::BindGroup } pub struct CameraController { position_pos_delta: Vector3, position_neg_delta: Vector3, rotation_pos_delta: Vector2, rotation_neg_delta: Vector2, scale_delta: Vector2, } #[repr(C)] #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct CameraUniform { view: [[f32;4];4], proj: [[f32;4];4], scale: f32 } pub struct Projection { aspect: f32, fovy: Rad, clip: (f32, f32) } impl Camera { pub fn new< V: Into>, Y: Into>, P: Into> >( wgpuctx: &WgpuCtx, position: V, yaw: Y, pitch: P) -> Self { use wgpu::BufferUsages; let buffer = wgpuctx.create_buffer( &wgpu::BufferDescriptor { label: Some("Camera buffer"), size: 144, usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, mapped_at_creation: false } ); let staging_buffer = wgpuctx.create_buffer( &wgpu::wgt::BufferDescriptor { label: Some("Camera staging buffer"), size: 144, usage: BufferUsages::COPY_SRC | BufferUsages::COPY_DST, mapped_at_creation: false } ); let bind_group_layout = Camera::bindgroup_layout(wgpuctx); let bind_group = wgpuctx.device().create_bind_group( &wgpu::BindGroupDescriptor { label: Some("Camera bind group"), layout: &bind_group_layout, entries: &[ wgpu::BindGroupEntry { binding: 0, resource: buffer.as_entire_binding() } ] } ); Self { abs_position: position.into(), rel_position: Point3::new(0.0, 0.0, 0.0), yaw: yaw.into(), pitch: pitch.into(), scale: 1.0, target: None, buffer: buffer, staging_buffer: staging_buffer, bindgroup: bind_group } } pub fn get_scale(&self) -> f32 { self.scale } pub fn get_position(&self) -> Point3 { Point3::new( self.abs_position.x + self.rel_position.x, self.abs_position.y + self.rel_position.y, self.abs_position.z + self.rel_position.z) } pub fn bindgroup_layout(wgpuctx: &WgpuCtx) -> wgpu::BindGroupLayout { wgpuctx.device().create_bind_group_layout( &wgpu::BindGroupLayoutDescriptor { label: Some("Camera bind group layout"), 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 bindgroup(&self) -> &wgpu::BindGroup { &self.bindgroup } pub fn stage_changes( &self, encoder: &mut wgpu::CommandEncoder) { let buffer_size = size_of::(); encoder.copy_buffer_to_buffer(&self.staging_buffer, 0, &self.buffer, 0, Some(buffer_size as u64)); } pub fn update_buffer( &self, wgpuctx: &WgpuCtx, projection: &Projection) { wgpuctx.queue().write_buffer(&self.staging_buffer, 0, bytemuck::cast_slice(&[self.uniform(projection)])); } pub fn set_target( &mut self, target: Option) { if target == self.target { return; } if !target.is_some() || !self.target.is_some() { self.abs_position = self.get_position(); self.rel_position = Point3::new(0.0, 0.0, 0.0); } self.target = target; } pub fn get_target(&self) -> Option { self.target } pub fn view_matrix( &self) -> cgmath::Matrix4 { let (yaw_sin, yaw_cos) = self.pitch.0.sin_cos(); let (pitch_sin, pitch_cos) = self.yaw.0.sin_cos(); if self.target.is_some() { cgmath::Matrix4::look_at_rh( self.get_position(), self.abs_position, Vector3::unit_y()) }else{ cgmath::Matrix4::look_to_rh( self.get_position(), Vector3::new( pitch_cos * yaw_cos, pitch_sin, pitch_cos * yaw_sin ).normalize(), Vector3::unit_y() ) } } pub fn uniform( &self, projection: &Projection) -> CameraUniform { CameraUniform { view: (self.view_matrix()).into(), proj: projection.projection_matrix().into(), scale: self.scale } } } //impl Camera impl CameraController { pub fn new() -> Self { Self { position_pos_delta: Vector3::new(0.0, 0.0, 0.0), position_neg_delta: Vector3::new(0.0, 0.0, 0.0), rotation_pos_delta: Vector2::new(0.0, 0.0), rotation_neg_delta: Vector2::new(0.0, 0.0), scale_delta: Vector2::new(0.0, 0.0), } } pub fn keyboard_input( &mut self, key_code: KeyCode, key_state: ElementState) { let press_q = if key_state == ElementState::Pressed { 1.0 } else { 0.0 }; match key_code { KeyCode::KeyW => { self.position_pos_delta.z = press_q; }, KeyCode::KeyS => { self.position_neg_delta.z = press_q; }, KeyCode::KeyA => { self.position_neg_delta.x = press_q; }, KeyCode::KeyD => { self.position_pos_delta.x = press_q; }, KeyCode::Space => { self.position_pos_delta.y = press_q; }, KeyCode::ShiftLeft => { self.position_neg_delta.y = press_q; }, KeyCode::KeyQ => { self.scale_delta.x = press_q; }, KeyCode::KeyE => { self.scale_delta.y = press_q; }, _ => {} } } fn update_orbit( &mut self, camera: &mut Camera, target: Point3, min_radius: f32, dt: Duration) { camera.abs_position = target * camera.scale; let dt = dt.as_secs_f32(); let speed = 1.0; let current_radius = camera.rel_position.to_vec().magnitude(); let polar_diff = (self.position_pos_delta.z - self.position_neg_delta.z) * speed * dt; let azimuth_diff = (self.position_pos_delta.x - self.position_neg_delta.x) * speed * dt; let dist_diff = (self.position_pos_delta.y - self.position_neg_delta.y) * speed * dt; camera.pitch.0 += polar_diff; let polar_cap = cgmath::Deg(179.999).0; if camera.pitch.0 > polar_cap { camera.pitch.0 = polar_cap; } if camera.pitch.0 < -polar_cap { camera.pitch.0 = -polar_cap; } camera.yaw.0 += azimuth_diff; let (az_sin, az_cos) = camera.yaw.0.sin_cos(); let (p_sin, p_cos) = camera.pitch.0.sin_cos(); let radius = f32::max(min_radius * camera.scale, current_radius + dist_diff); camera.rel_position = Point3::new( radius * p_cos * az_cos, radius * p_sin, radius * p_cos * az_sin ); } fn update_pan( &mut self, camera: &mut Camera, dt: Duration) { let dt = dt.as_secs_f32(); let speed = 1.0; let (yaw_sin, yaw_cos) = camera.yaw.0.sin_cos(); let forward = Vector3::new(yaw_cos, 0.0, yaw_sin).normalize(); let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize(); let up = Vector3::new(0.0, 1.0, 0.0); camera.rel_position += forward * (self.position_pos_delta.z - self.position_neg_delta.z) * speed * dt; camera.rel_position += right * (self.position_pos_delta.x - self.position_neg_delta.x) * speed * dt; camera.rel_position += up * (self.position_pos_delta.y - self.position_neg_delta.y) * speed * dt; camera.abs_position = camera.rel_position; camera.pitch.0 += (self.rotation_pos_delta.x - self.rotation_neg_delta.x) * speed * dt; camera.yaw.0 += (self.rotation_pos_delta.y - self.rotation_neg_delta.y) * speed * dt; } pub fn update( &mut self, camera: &mut Camera, game_state: &RefCell, ui_state: &mut GameWindowUiState, dt: Duration) { camera.scale *= 1.0 + ((self.scale_delta.y - self.scale_delta.x) * 0.1); camera.scale = f32::max(1e-16, f32::min(1.0, camera.scale)); match ui_state.camera_target { Some(body_id) => { let game_state = game_state.borrow(); let solar_systems = game_state.solar_systems(); let current_system = match ui_state.current_system { Some(id) => &solar_systems[id], None => return }; let body = ¤t_system.bodies()[body_id]; self.update_orbit( camera, body.position().cast().unwrap(), body.radius(), dt); } None => self.update_pan(camera, dt) } } } //impl CameraController impl Projection { pub fn new< F: Into> >( width: u32, height: u32, fovy: F, clip: (f32, f32)) -> Self { Self { aspect: width as f32 / height as f32, fovy: fovy.into(), clip: clip } } pub fn resize( &mut self, width: u32, height: u32) { self.aspect = width as f32 / height as f32; } pub fn projection_matrix( &self) -> cgmath::Matrix4 { perspective(self.fovy, self.aspect, self.clip.0, self.clip.1) } }