From 8004132c918a52af3b2e7b32b4fc72c65fde9e3c Mon Sep 17 00:00:00 2001 From: tcmal Date: Sun, 25 Aug 2024 17:44:22 +0100 Subject: feat(draw): attach camera position/settings to entity --- stockton-render/src/draw/camera.rs | 237 ++++++------------------------------ stockton-render/src/draw/context.rs | 38 +----- stockton-render/src/draw/mod.rs | 1 + stockton-render/src/draw/target.rs | 8 +- stockton-render/src/lib.rs | 16 ++- 5 files changed, 61 insertions(+), 239 deletions(-) (limited to 'stockton-render/src') diff --git a/stockton-render/src/draw/camera.rs b/stockton-render/src/draw/camera.rs index 11bf617..aa5efac 100644 --- a/stockton-render/src/draw/camera.rs +++ b/stockton-render/src/draw/camera.rs @@ -17,19 +17,14 @@ //! Things related to converting 3D world space to 2D screen space -use stockton_types::{Matrix4, Vector3}; +use legion::maybe_changed; -use na::{look_at_lh, perspective_lh_zo, Mat4, Vec4}; -use std::f32::consts::PI; +use nalgebra_glm::look_at_lh; +use nalgebra_glm::perspective_lh_zo; -/// 90 degrees in radians -const R89: f32 = (PI / 180.0) * 89.0; - -/// 90 degrees in radians -const R90: f32 = PI / 2.0; - -/// 180 degrees in radians -const R180: f32 = PI; +use crate::Renderer; +use stockton_types::components::{CameraSettings, Transform}; +use stockton_types::Vector3; fn euler_to_direction(euler: &Vector3) -> Vector3 { let pitch = euler.x; @@ -43,193 +38,35 @@ fn euler_to_direction(euler: &Vector3) -> Vector3 { ) } -pub struct CameraSettings { - /// Position of the camera (world units) - pub position: Vector3, - - /// Rotation of the camera (euler angles in radians) - pub rotation: Vector3, - - /// The up direction (normalized) - pub up: Vector3, - - /// FOV (radians) - pub fov: f32, - - /// Near clipping plane (world units) - pub near: f32, - - /// Far clipping plane (world units) - pub far: f32, -} - -/// Holds settings related to the projection of world space to screen space -/// Also holds maths for generating important matrices -pub struct WorkingCamera { - /// Settings for the camera - settings: CameraSettings, - - /// Aspect ratio as a fraction - aspect_ratio: f32, - - /// Cached view projection matrix - vp_matrix: Mat4, - - /// If true, cached value needs updated - is_dirty: bool, -} - -impl WorkingCamera { - /// Return a camera with default settings - pub fn defaults(aspect_ratio: f32) -> WorkingCamera { - WorkingCamera::with_settings( - CameraSettings { - position: Vector3::new(0.0, 0.0, 0.0), - rotation: Vector3::new(0.0, R90, 0.0), - up: Vector3::new(0.0, 1.0, 0.0), - fov: f32::to_radians(90.0), - near: 0.1, - far: 1024.0, - }, - aspect_ratio, - ) - } - - /// Return a camera with the given settings - pub fn with_settings(settings: CameraSettings, aspect_ratio: f32) -> WorkingCamera { - WorkingCamera { - aspect_ratio, - settings, - vp_matrix: Mat4::identity(), - is_dirty: true, - } - } - - /// Get the VP matrix, updating cache if needed - pub fn get_matrix(&mut self) -> &Mat4 { - // Update matrix if needed - if self.is_dirty { - self.vp_matrix = self.calc_vp_matrix(); - self.is_dirty = false; - } - - // Return the matrix - &self.vp_matrix - } - - /// Returns a matrix that transforms from world space to screen space - fn calc_vp_matrix(&self) -> Matrix4 { - // Get look direction from euler angles - let direction = euler_to_direction(&self.settings.rotation); - - // Converts world space to camera space - let view_matrix = look_at_lh( - &self.settings.position, - &(self.settings.position + direction), - &self.settings.up, - ); - - // Converts camera space to screen space - let projection_matrix = { - let mut temp = perspective_lh_zo( - self.aspect_ratio, - self.settings.fov, - self.settings.near, - self.settings.far, - ); - - // Vulkan's co-ord system is different from OpenGLs - temp[(1, 1)] *= -1.0; - - temp - }; - - // Chain them together into a single matrix - projection_matrix * view_matrix - } - - /// Update the aspect ratio - pub fn update_aspect_ratio(&mut self, new: f32) { - self.aspect_ratio = new; - self.is_dirty = true; - } - - /// Apply rotation of the camera - /// `euler` should be euler angles in degrees - pub fn rotate(&mut self, euler: Vector3) { - // TODO - self.settings.rotation += euler; - - // Clamp -pi/2 < pitch < pi/2 - if self.settings.rotation.x > R89 { - self.settings.rotation.x = R89; - } else if self.settings.rotation.x <= -R89 { - self.settings.rotation.x = -R89; - } - - // -pi < yaw <= pi - if self.settings.rotation.y <= -R180 { - self.settings.rotation.y = R180 - self.settings.rotation.y % -R180; - } else if self.settings.rotation.y > 180.0 { - self.settings.rotation.y = -R180 + self.settings.rotation.y % R180; - } - - self.is_dirty = true; - } - - /// Move the camera by `delta`, relative to the camera's rotation - pub fn move_camera_relative(&mut self, delta: Vector3) { - let rot_matrix = Mat4::from_euler_angles( - -self.settings.rotation.x, - self.settings.rotation.y, - self.settings.rotation.z, - ); - - let new = rot_matrix * Vec4::new(delta.x, delta.y, delta.z, 1.0); - self.settings.position.x += new.x; - self.settings.position.y += new.y; - self.settings.position.z += new.z; - - self.is_dirty = true; - } - - pub fn camera_pos(&self) -> Vector3 { - self.settings.position - } -} - -#[cfg(test)] -mod tests { - use crate::draw::camera::WorkingCamera; - use stockton_types::Matrix4; - use stockton_types::Vector3; - - fn contains_nan(mat: &Matrix4) -> bool { - for x in mat.iter() { - if (*x).is_nan() { - return true; - } - } - false - } - - #[test] - fn camera_vp() { - let mut camera = WorkingCamera::defaults(16.0 / 9.0); - - let old = camera.calc_vp_matrix(); - println!("initial vp matrix: {:?}", old); - - assert!(!contains_nan(&old), "No NaNs for initial matrix"); - - // Do a 180 - camera.rotate(Vector3::new(0.0, 180.0, 0.0)); - - let new = camera.calc_vp_matrix(); - assert!(!contains_nan(&new), "No NaNs after rotating"); - - println!("new vp matrix: {:?}", new); - - assert!(old != new, "VP Matrix changes when camera rotates"); - } +#[system(for_each)] +#[filter(maybe_changed::() | maybe_changed::())] +pub fn calc_vp_matrix( + transform: &Transform, + settings: &CameraSettings, + #[resource] renderer: &mut Renderer<'static>, +) { + let ratio = renderer.context.target_chain.properties.extent.width as f32 + / renderer.context.target_chain.properties.extent.height as f32; + // Get look direction from euler angles + let direction = euler_to_direction(&transform.rotation); + + // Converts world space to camera space + let view_matrix = look_at_lh( + &transform.position, + &(transform.position + direction), + &Vector3::new(0.0, 1.0, 0.0), // TODO + ); + + // Converts camera space to screen space + let projection_matrix = { + let mut temp = perspective_lh_zo(ratio, settings.fov, settings.near, settings.far); + + // Vulkan's co-ord system is different from OpenGLs + temp[(1, 1)] *= -1.0; + + temp + }; + + // Chain them together into a single matrix + renderer.context.vp_matrix = projection_matrix * view_matrix } diff --git a/stockton-render/src/draw/context.rs b/stockton-render/src/draw/context.rs index 777a3a9..b569ced 100644 --- a/stockton-render/src/draw/context.rs +++ b/stockton-render/src/draw/context.rs @@ -29,6 +29,7 @@ use std::{ use arrayvec::ArrayVec; use hal::{pool::CommandPoolCreateFlags, prelude::*}; use log::debug; +use na::Mat4; use winit::window::Window; use stockton_levels::prelude::*; @@ -37,7 +38,6 @@ use stockton_types::{Vector2, Vector3}; use super::{ buffer::ModifiableBuffer, - camera::WorkingCamera, draw_buffers::{DrawBuffers, INITIAL_INDEX_SIZE, INITIAL_VERT_SIZE}, target::{SwapchainProperties, TargetChain}, texture::TextureStore, @@ -76,7 +76,7 @@ pub struct RenderingContext<'a> { surface: ManuallyDrop, /// Swapchain and stuff - target_chain: ManuallyDrop, + pub(crate) target_chain: ManuallyDrop, // Pipeline /// Our main render pass @@ -101,8 +101,8 @@ pub struct RenderingContext<'a> { /// Buffers used for drawing draw_buffers: ManuallyDrop>, - /// Our camera settings - camera: WorkingCamera, + /// View projection matrix + pub(crate) vp_matrix: Mat4, /// The vertex shader module vs_module: ManuallyDrop, @@ -246,12 +246,6 @@ impl<'a> RenderingContext<'a> { main_pass: &renderpass, }; - // Camera - // TODO: Settings - let ratio = - swapchain_properties.extent.width as f32 / swapchain_properties.extent.height as f32; - let camera = WorkingCamera::defaults(ratio); - // Vertex and index buffers let draw_buffers = DrawBuffers::new(&mut device, &adapter)?; @@ -309,7 +303,7 @@ impl<'a> RenderingContext<'a> { vs_module: ManuallyDrop::new(vs_module), fs_module: ManuallyDrop::new(fs_module), - camera, + vp_matrix: Mat4::identity(), }) } @@ -322,10 +316,6 @@ impl<'a> RenderingContext<'a> { let properties = SwapchainProperties::find_best(&self.adapter, &self.surface) .map_err(|_| error::CreationError::BadSurface)?; - // Camera settings (aspect ratio) - self.camera - .update_aspect_ratio(properties.extent.width as f32 / properties.extent.height as f32); - use core::ptr::read; // Graphics pipeline @@ -572,7 +562,7 @@ impl<'a> RenderingContext<'a> { &self.renderpass, &self.pipeline, &self.pipeline_layout, - &mut self.camera, + &self.vp_matrix, )?; // Iterate over faces, copying them in and drawing groups that use the same texture chunk all at once. @@ -683,22 +673,6 @@ impl<'a> RenderingContext<'a> { Ok(()) } - - /// Get current position of camera - pub fn camera_pos(&self) -> Vector3 { - self.camera.camera_pos() - } - - /// Move the camera by `delta` relative to its rotation - pub fn move_camera_relative(&mut self, delta: Vector3) { - self.camera.move_camera_relative(delta) - } - - /// Rotate the camera - /// `euler` should be euler angles in radians - pub fn rotate(&mut self, euler: Vector3) { - self.camera.rotate(euler) - } } impl<'a> core::ops::Drop for RenderingContext<'a> { diff --git a/stockton-render/src/draw/mod.rs b/stockton-render/src/draw/mod.rs index 744ad96..88381c5 100644 --- a/stockton-render/src/draw/mod.rs +++ b/stockton-render/src/draw/mod.rs @@ -27,5 +27,6 @@ mod context; mod draw_buffers; mod texture; +pub use self::camera::calc_vp_matrix_system; pub use self::context::RenderingContext; pub use self::context::UVPoint; diff --git a/stockton-render/src/draw/target.rs b/stockton-render/src/draw/target.rs index 65e03a2..19f41dc 100644 --- a/stockton-render/src/draw/target.rs +++ b/stockton-render/src/draw/target.rs @@ -16,7 +16,7 @@ */ //! Resources needed for drawing on the screen, including sync objects -use super::{camera::WorkingCamera, texture::image::LoadedImage}; +use super::texture::image::LoadedImage; use crate::types::*; use core::{iter::once, mem::ManuallyDrop}; @@ -32,6 +32,7 @@ use hal::{ queue::Submission, window::{CompositeAlphaMode, Extent2D, PresentMode, SwapchainConfig}, }; +use na::Mat4; /// Defines the colour range we use. const COLOR_RANGE: hal::image::SubresourceRange = hal::image::SubresourceRange { @@ -266,7 +267,7 @@ impl TargetChain { renderpass: &RenderPass, pipeline: &GraphicsPipeline, pipeline_layout: &PipelineLayout, - camera: &mut WorkingCamera, + vp: &Mat4, ) -> Result<&'a mut crate::types::CommandBuffer, &'static str> { self.last_drawn = (self.last_drawn + 1) % self.targets.len(); @@ -337,8 +338,7 @@ impl TargetChain { target.cmd_buffer.bind_graphics_pipeline(&pipeline); // VP Matrix - let vp = camera.get_matrix().as_slice(); - let vp = &*(vp as *const [f32] as *const [u32]); + let vp = &*(vp.data.as_slice() as *const [f32] as *const [u32]); target.cmd_buffer.push_graphics_constants( &pipeline_layout, diff --git a/stockton-render/src/lib.rs b/stockton-render/src/lib.rs index 4d10ff8..8bdca89 100644 --- a/stockton-render/src/lib.rs +++ b/stockton-render/src/lib.rs @@ -31,12 +31,16 @@ pub mod window; use culling::get_visible_faces; use draw::RenderingContext; +use legion::world::SubWorld; +use legion::IntoQuery; use std::sync::mpsc::{Receiver, Sender}; use std::sync::Arc; use std::sync::RwLock; pub use window::WindowEvent; use stockton_levels::prelude::*; +use stockton_types::components::{CameraSettings, Transform}; +use stockton_types::Vector3; use winit::event_loop::ControlFlow; use winit::window::Window; @@ -75,9 +79,9 @@ impl<'a> Renderer<'a> { } /// Render a single frame of the given map. - fn render>(&mut self, map: &T) { + fn render>(&mut self, map: &T, pos: Vector3) { // Get visible faces - let faces = get_visible_faces(self.context.camera_pos(), map); + let faces = get_visible_faces(pos, map); // Then draw them if self.context.draw_vertices(map, &faces).is_err() { @@ -95,9 +99,15 @@ impl<'a> Renderer<'a> { /// A system that just renders the world. #[system] +#[read_component(Transform)] +#[read_component(CameraSettings)] pub fn do_render>( #[resource] renderer: &mut Renderer<'static>, #[resource] map: &T, + world: &SubWorld, ) { - renderer.render(map); + let mut query = <(&Transform, &CameraSettings)>::query(); + for (transform, _) in query.iter(world) { + renderer.render(map, transform.position); + } } -- cgit v1.2.3