From 08e49e1b223412974505e06a1f9d88c2c30d9ddf Mon Sep 17 00:00:00 2001 From: Verox001 Date: Mon, 5 May 2025 20:07:20 +0200 Subject: [PATCH] Implemented 3D support and camera --- simulator/src/main.rs | 2 +- solar_engine/src/camera.rs | 57 ++++++++++++++++++++++++++++++++++++ solar_engine/src/lib.rs | 1 + solar_engine/src/render.rs | 2 +- solar_engine/src/shader.wgsl | 11 ++----- solar_engine/src/state.rs | 16 ++++++++-- 6 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 solar_engine/src/camera.rs diff --git a/simulator/src/main.rs b/simulator/src/main.rs index 007faf0..ef4f4b3 100644 --- a/simulator/src/main.rs +++ b/simulator/src/main.rs @@ -36,7 +36,7 @@ pub async fn run() { sim.step(dt * timewarp as f64); } - thread::sleep(Duration::from_millis(1)); + thread::sleep(Duration::from_nanos(1)); } }); diff --git a/solar_engine/src/camera.rs b/solar_engine/src/camera.rs new file mode 100644 index 0000000..c9ca35b --- /dev/null +++ b/solar_engine/src/camera.rs @@ -0,0 +1,57 @@ +use cgmath::{Matrix4, Point3, Vector3, Deg, perspective, InnerSpace, Rotation, Quaternion, Rotation3, Rad}; + +#[derive(Debug)] +pub struct Camera { + pub eye: Point3, + pub target: Point3, + pub up: Vector3, + pub fov_y: Deg, + pub aspect: f32, + pub znear: f32, + pub zfar: f32, +} + +impl Camera { + pub fn new(aspect: f32) -> Self { + Self { + eye: Point3::new(0.0, 0.0, 5.0), + target: Point3::new(0.0, 0.0, 0.0), + up: Vector3::unit_y(), + fov_y: Deg(45.0), + aspect, + znear: 0.1, + zfar: 100.0, + } + } + + pub fn build_view_projection_matrix(&self) -> Matrix4 { + let view = Matrix4::look_at_rh(self.eye, self.target, self.up); + let proj = perspective(self.fov_y, self.aspect, self.znear, self.zfar); + proj * view + } + + pub fn rotate_yaw_pitch(&mut self, yaw: f32, pitch: f32) { + let dir = (self.target - self.eye).normalize(); + + let horizontal = Vector3::unit_y().cross(dir).normalize(); + let vertical = horizontal.cross(dir).normalize(); + + let pitch_q = Quaternion::from_axis_angle(vertical, Rad(pitch.to_radians())); + let yaw_q = Quaternion::from_axis_angle(Vector3::unit_y(), Rad(yaw.to_radians())); + + let rotation = yaw_q * pitch_q; + let rotated = rotation.rotate_vector(dir).normalize(); + + let distance = (self.target - self.eye).magnitude(); + self.eye = self.target - rotated * distance; + } + + pub fn zoom(&mut self, amount: f32) { + let dir = (self.target - self.eye).normalize(); + self.eye += dir * amount; + } + + pub fn set_aspect(&mut self, aspect: f32) { + self.aspect = aspect; + } +} diff --git a/solar_engine/src/lib.rs b/solar_engine/src/lib.rs index dbc9d36..0a8b8a8 100644 --- a/solar_engine/src/lib.rs +++ b/solar_engine/src/lib.rs @@ -4,6 +4,7 @@ mod state; mod render; mod application; mod input; +mod camera; pub use body::Body; diff --git a/solar_engine/src/render.rs b/solar_engine/src/render.rs index 3c7eef4..13c5024 100644 --- a/solar_engine/src/render.rs +++ b/solar_engine/src/render.rs @@ -1,7 +1,7 @@ #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] pub struct Globals { - pub(crate) aspect_ratio: f32, + pub view_proj: [[f32; 4]; 4], } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] diff --git a/solar_engine/src/shader.wgsl b/solar_engine/src/shader.wgsl index b552f3b..324f6da 100644 --- a/solar_engine/src/shader.wgsl +++ b/solar_engine/src/shader.wgsl @@ -17,7 +17,7 @@ struct VSOutput { }; struct Globals { - aspect_ratio: f32, + view_proj: mat4x4, } @group(0) @binding(0) @@ -32,14 +32,7 @@ fn vs_main(vertex: VertexInput, instance: InstanceInput) -> VSOutput { instance.model_row2, instance.model_row3 ); - - let projection = mat4x4( - vec4(1.0 / globals.aspect_ratio, 0.0, 0.0, 0.0), - vec4(0.0, 1.0, 0.0, 0.0), - vec4(0.0, 0.0, 1.0, 0.0), - vec4(0.0, 0.0, 0.0, 1.0), - ); - out.position = projection * model * vec4(vertex.position, 1.0); + out.position = globals.view_proj * model * vec4(vertex.position, 1.0); out.color = instance.color; return out; } diff --git a/solar_engine/src/state.rs b/solar_engine/src/state.rs index 1935816..7444fb9 100644 --- a/solar_engine/src/state.rs +++ b/solar_engine/src/state.rs @@ -1,12 +1,14 @@ use std::cmp::max; use std::collections::HashMap; use std::sync::{Arc}; +use cgmath::{perspective, Deg, Matrix4, Point3, Vector3}; use log::info; use pollster::FutureExt; use wgpu::util::DeviceExt; use wgpu::{Adapter, Device, Instance, PresentMode, Queue, Surface, SurfaceCapabilities, SurfaceConfiguration}; use winit::dpi::PhysicalSize; use winit::window::{Window}; +use crate::camera::Camera; use crate::render::{create_circle_vertices, Geometry, Globals, InstanceRaw, RenderInstance, SampleCount, Shape, Vertex}; pub struct State<'a> { @@ -27,6 +29,8 @@ pub struct State<'a> { geometries: HashMap, global_bind_group: wgpu::BindGroup, global_buffer: wgpu::Buffer, + + camera: Camera, } impl<'a> State<'a> { @@ -44,8 +48,12 @@ impl<'a> State<'a> { let sample_count = SampleCount(Self::probe_msaa_support(&device, &config)); info!("MSAA sample count: {}", sample_count.0); + let aspect = config.width as f32 / config.height as f32; + let camera = Camera::new(aspect); + let view_proj = camera.build_view_projection_matrix(); + let globals = Globals { - aspect_ratio: config.width as f32 / config.height as f32, + view_proj: view_proj.into(), }; let global_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Global Uniform Buffer"), @@ -99,6 +107,7 @@ impl<'a> State<'a> { global_buffer, instances, instance_buffer, + camera, } } @@ -288,8 +297,11 @@ impl<'a> State<'a> { self.surface.configure(&self.device, &self.config); + self.camera.set_aspect(self.config.width as f32 / self.config.height as f32); + let view_proj = self.camera.build_view_projection_matrix(); + let new_globals = Globals { - aspect_ratio: self.config.width as f32 / self.config.height as f32, + view_proj: view_proj.into(), }; self.queue.write_buffer(&self.global_buffer, 0, bytemuck::cast_slice(&[new_globals]));