diff --git a/Cargo.lock b/Cargo.lock index 417e9f5..5b0e22d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,6 +437,31 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "evenio" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ead7361e14542019e9f883d2022399ef419ac240085b29ed23715f7746094e78" +dependencies = [ + "ahash", + "bumpalo", + "evenio_macros", + "hashbrown", + "indexmap", + "slab", +] + +[[package]] +name = "evenio_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa8c1192efdb4d5734fbdb3cbab6f018807c335eb52b01b3d1b964467cf0097" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -837,6 +862,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ndarray" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + [[package]] name = "ndk" version = "0.9.0" @@ -1089,7 +1127,9 @@ name = "pixelgame" version = "0.1.0" dependencies = [ "bytemuck", + "evenio", "nalgebra", + "ndarray", "pollster", "rand", "simba", diff --git a/Cargo.toml b/Cargo.toml index ca376b4..2233ec9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[build] +rustflags = ["-Z", "threads=14"] + [dependencies] bytemuck = {version="1.14.0", features=["derive"]} +evenio = "0.6.0" nalgebra = {version="0.32.5", features=["bytemuck"]} +ndarray = "0.15.6" pollster = "0.3" rand = "0.8.5" simba = "0.8.1" diff --git a/src/client/window.rs b/src/client/app.rs similarity index 50% rename from src/client/window.rs rename to src/client/app.rs index bffbac1..ccccf0c 100644 --- a/src/client/window.rs +++ b/src/client/app.rs @@ -5,19 +5,32 @@ use winit::{ window::WindowAttributes, }; -use super::{render::Renderer, Client}; +use super::Client; -impl ApplicationHandler for Client<'_> { +pub struct ClientApp { + client: Option, +} + +impl ClientApp { + fn client(&mut self) -> &mut Client { + self.client.as_mut().expect("bruh") + } + + pub fn new() -> Self { + Self { client: None } + } +} + +impl ApplicationHandler for ClientApp { fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - if self.window.is_none() { + if self.client.is_none() { let window = Arc::new( event_loop .create_window(WindowAttributes::default()) .expect("Failed to create window"), ); - self.renderer = Some(Renderer::new(window.clone(), false)); - self.window = Some(window); - self.start(); + let client = Client::new(window); + self.client = Some(client); } event_loop.set_control_flow(ControlFlow::Poll); } @@ -28,27 +41,20 @@ impl ApplicationHandler for Client<'_> { _window_id: winit::window::WindowId, event: WindowEvent, ) { - let renderer = self.renderer.as_mut().unwrap(); - - match event { - WindowEvent::CloseRequested => self.exit = true, - WindowEvent::Resized(size) => renderer.resize(size), - WindowEvent::RedrawRequested => renderer.draw(), - _ => self.input.update_window(event), - } + self.client().window_event(event); } fn device_event( - &mut self, - _event_loop: &winit::event_loop::ActiveEventLoop, - _device_id: winit::event::DeviceId, - event: winit::event::DeviceEvent, - ) { - self.input.update_device(event); + &mut self, + _event_loop: &winit::event_loop::ActiveEventLoop, + _device_id: winit::event::DeviceId, + event: winit::event::DeviceEvent, + ) { + self.client().input.update_device(event); } fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { - if self.update() { + if self.client().update() { event_loop.exit(); } } diff --git a/src/client/camera.rs b/src/client/camera.rs index 136a60c..306d9a9 100644 --- a/src/client/camera.rs +++ b/src/client/camera.rs @@ -1,21 +1,17 @@ -use nalgebra::{Point3, Rotation3, UnitVector3, Vector3}; +use nalgebra::{Rotation3, UnitVector3, Vector3}; -const DEFAULT_ASPECT_RATIO: f32 = 16. / 9.; - -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Camera { - pub pos: Point3, + pub pos: Vector3, pub orientation: Rotation3, - pub aspect: f32, pub scale: f32, } impl Default for Camera { fn default() -> Self { Self { - pos: Point3::origin(), + pos: Vector3::zeros(), orientation: Rotation3::identity(), - aspect: DEFAULT_ASPECT_RATIO, scale: 1.0, } } @@ -41,4 +37,3 @@ impl Camera { self.orientation * Vector3::z_axis() } } - diff --git a/src/client/component.rs b/src/client/component.rs index e336776..0ba0f60 100644 --- a/src/client/component.rs +++ b/src/client/component.rs @@ -1,10 +1,19 @@ -use bevy_ecs::bundle::Bundle; +use std::ops::{Deref, DerefMut}; -use crate::world::{component::{Position, Rotation}, grid::TileGrid}; +use evenio::component::Component; -#[derive(Bundle)] -pub struct ClientGrid { - pub grid: TileGrid, - pub position: Position, - pub rotation: Rotation, +use super::render::RendererChannel; + +#[derive(Component)] +pub struct RenderComponent(pub RendererChannel); +impl Deref for RenderComponent { + type Target = RendererChannel; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for RenderComponent { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } } diff --git a/src/client/handle_input.rs b/src/client/handle_input.rs index 3e68c43..ac51452 100644 --- a/src/client/handle_input.rs +++ b/src/client/handle_input.rs @@ -1,48 +1,57 @@ use std::time::Duration; use nalgebra::Rotation3; +use ndarray::Array3; use winit::{dpi::PhysicalPosition, keyboard::KeyCode as Key, window::CursorGrabMode}; -use super::Client; +use crate::world::component::VoxelGrid; -impl Client<'_> { +use super::{render::voxel::VoxelColor, system::voxel_grid::SpawnVoxelGrid, Client}; + +impl Client { pub fn handle_input(&mut self, dt: &Duration) { let dt = dt.as_secs_f32(); - let Client { input, state, .. } = self; + let Client { + input, + state, + window, + .. + } = self; + + // cursor lock if input.just_pressed(Key::Escape) { - if let Some(window) = &self.window { - self.grabbed_cursor = !self.grabbed_cursor; - if self.grabbed_cursor { - window.set_cursor_visible(false); - window - .set_cursor_grab(CursorGrabMode::Locked) - .map(|_| { - self.keep_cursor = false; - }) - .or_else(|_| { - self.keep_cursor = true; - window.set_cursor_grab(CursorGrabMode::Confined) - }) - .expect("cursor lock"); - } else { - self.keep_cursor = false; - window.set_cursor_visible(true); - window - .set_cursor_grab(CursorGrabMode::None) - .expect("cursor unlock"); - }; - } + self.grabbed_cursor = !self.grabbed_cursor; + if self.grabbed_cursor { + window.set_cursor_visible(false); + window + .set_cursor_grab(CursorGrabMode::Locked) + .map(|_| { + self.keep_cursor = false; + }) + .or_else(|_| { + self.keep_cursor = true; + window.set_cursor_grab(CursorGrabMode::Confined) + }) + .expect("cursor lock"); + } else { + self.keep_cursor = false; + window.set_cursor_visible(true); + window + .set_cursor_grab(CursorGrabMode::None) + .expect("cursor unlock"); + }; return; } + if self.keep_cursor { + let size = window.inner_size(); + window + .set_cursor_position(PhysicalPosition::new(size.width / 2, size.height / 2)) + .expect("cursor move"); + } + + // camera orientation + let old_camera = state.camera; if self.grabbed_cursor { - if let Some(window) = &self.window { - if self.keep_cursor { - let size = window.inner_size(); - window - .set_cursor_position(PhysicalPosition::new(size.width / 2, size.height / 2)) - .expect("cursor move"); - } - } let delta = input.mouse_delta * 0.003; if delta.x != 0.0 { state.camera.orientation = Rotation3::from_axis_angle(&state.camera.up(), delta.x) @@ -70,8 +79,9 @@ impl Client<'_> { state.camera_scroll += input.scroll_delta; state.camera.scale = (state.camera_scroll * 0.2).exp(); } - let move_dist = 10.0 * dt; + // camera position + let move_dist = 10.0 * dt; if input.pressed(Key::KeyW) { state.camera.pos += *state.camera.forward() * move_dist; } @@ -90,5 +100,21 @@ impl Client<'_> { if input.pressed(Key::ShiftLeft) { state.camera.pos += *state.camera.down() * move_dist; } + if state.camera != old_camera { + self.renderer + .send(super::render::RenderMessage::ViewUpdate(state.camera)) + .expect("AAAAAAA"); + } + + // fun + if input.just_pressed(Key::KeyF) { + self.world.send(SpawnVoxelGrid { + pos: state.camera.pos + 135.0 * 2.0 * *state.camera.forward(), + orientation: state.camera.orientation, + grid: VoxelGrid::new(Array3::from_shape_fn((135, 135, 135), |(..)| { + VoxelColor::white() + })), + }) + } } } diff --git a/src/client/init.rs b/src/client/init.rs new file mode 100644 index 0000000..8d8b410 --- /dev/null +++ b/src/client/init.rs @@ -0,0 +1,73 @@ +use crate::world::component::VoxelGrid; +use evenio::world::World; +use nalgebra::{Rotation3, UnitVector3, Vector3}; +use ndarray::Array3; + +use crate::client::render::voxel::VoxelColor; + +use super::system::voxel_grid::SpawnVoxelGrid; + +pub fn init_world(world: &mut World) { + let dim = (15, 10, 10); + world.send(SpawnVoxelGrid { + pos: Vector3::new(0.0, 0.0, 20.0), + orientation: Rotation3::from_axis_angle(&Vector3::y_axis(), 0.5), + grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(x, y, z)| { + if x == z && x == y { + VoxelColor::white() + } else if z == 3 { + VoxelColor { + r: (x as f32 / dim.0 as f32 * 255.0) as u8, + g: (y as f32 / dim.1 as f32 * 255.0) as u8, + b: 100, + a: 255, + } + } else if z == 0 { + VoxelColor { + r: (x as f32 / dim.0 as f32 * 255.0) as u8, + g: (y as f32 / dim.1 as f32 * 255.0) as u8, + b: 0, + a: 100, + } + } else { + VoxelColor::none() + } + })), + }); + + let dim = (1000, 2, 1000); + world.send(SpawnVoxelGrid { + pos: Vector3::new(0.0, -2.1, 0.0), + orientation: Rotation3::identity(), + grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(x, y, z)| { + if y == 0 { + rand::random() + } else if (y == dim.1 - 1) && (x == 0 || x == dim.0 - 1 || z == 0 || z == dim.2 - 1) { + VoxelColor { + r: 255, + g: 0, + b: 255, + a: 255, + } + } else { + VoxelColor::none() + } + })), + }); + + let dim = (3, 3, 3); + world.send(SpawnVoxelGrid { + pos: Vector3::new(0.0, 0.0, 16.5), + orientation: Rotation3::from_axis_angle(&Vector3::y_axis(), std::f32::consts::PI / 4.0) + * Rotation3::from_axis_angle( + &UnitVector3::new_normalize(Vector3::new(1.0, 0.0, 1.0)), + std::f32::consts::PI / 4.0, + ), + grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(..)| VoxelColor { + r: 255, + g: 0, + b: 255, + a: 255, + })), + }); +} diff --git a/src/client/input.rs b/src/client/input.rs index f762652..85ff769 100644 --- a/src/client/input.rs +++ b/src/client/input.rs @@ -1,15 +1,14 @@ use std::collections::HashSet; +use nalgebra::Vector2; use winit::{ event::{DeviceEvent, ElementState, MouseButton, MouseScrollDelta, WindowEvent}, keyboard::{KeyCode, PhysicalKey}, }; -use crate::util::math::{Pos2f, Vec2f}; - pub struct Input { - pub mouse_pixel_pos: Pos2f, - pub mouse_delta: Vec2f, + pub mouse_pixel_pos: Vector2, + pub mouse_delta: Vector2, pressed: HashSet, just_pressed: HashSet, @@ -24,8 +23,8 @@ pub struct Input { impl Input { pub fn new() -> Self { Self { - mouse_pixel_pos: Pos2f::origin(), - mouse_delta: Vec2f::zeros(), + mouse_pixel_pos: Vector2::zeros(), + mouse_delta: Vector2::zeros(), pressed: HashSet::new(), just_pressed: HashSet::new(), mouse_pressed: HashSet::new(), @@ -43,7 +42,7 @@ impl Input { }; } DeviceEvent::MouseMotion { delta } => { - self.mouse_delta += Vec2f::new(delta.0 as f32, delta.1 as f32); + self.mouse_delta += Vector2::new(delta.0 as f32, delta.1 as f32); } _ => (), } @@ -72,7 +71,7 @@ impl Input { self.mouse_pressed.clear(); } WindowEvent::CursorMoved { position, .. } => { - self.mouse_pixel_pos = Pos2f::new(position.x as f32, position.y as f32); + self.mouse_pixel_pos = Vector2::new(position.x as f32, position.y as f32); } WindowEvent::MouseInput { button, state, .. } => match state { ElementState::Pressed => { @@ -90,12 +89,18 @@ impl Input { pub fn end(&mut self) { self.scroll_delta = 0.0; - self.mouse_delta = Vec2f::zeros(); + self.mouse_delta = Vector2::zeros(); self.just_pressed.clear(); self.mouse_just_pressed.clear(); self.mouse_just_released.clear(); } + pub fn clear(&mut self) { + self.pressed.clear(); + self.mouse_pressed.clear(); + self.end(); + } + #[allow(dead_code)] pub fn pressed(&self, key: KeyCode) -> bool { self.pressed.contains(&key) diff --git a/src/client/mod.rs b/src/client/mod.rs index 4c0463c..902f687 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,53 +1,65 @@ +mod app; mod camera; +mod component; mod handle_input; +mod init; mod input; -mod render; +pub mod render; mod rsc; mod state; -mod window; +mod system; +pub use app::*; +use component::RenderComponent; +use evenio::world::World; +use init::init_world; +use render::{RenderMessage, RendererChannel}; pub use state::*; -use self::{input::Input, render::Renderer, rsc::FRAME_TIME, ClientState}; -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; -use winit::window::Window; +use self::{input::Input, render::Renderer, ClientState}; +use std::{sync::Arc, thread::JoinHandle, time::Instant}; +use winit::{event::WindowEvent, window::Window}; -pub struct Client<'a> { - window: Option>, - renderer: Option>, - frame_time: Duration, +pub struct Client { + window: Arc, state: ClientState, + render_handle: Option>, + renderer: RendererChannel, exit: bool, input: Input, - target: Instant, - prev_frame: Instant, prev_update: Instant, grabbed_cursor: bool, keep_cursor: bool, + world: World, } -impl Client<'_> { - pub fn new() -> Self { +impl Client { + pub fn new(window: Arc) -> Self { + let mut world = World::new(); + + let (render_channel, render_handle) = Renderer::spawn(window.clone()); + let e = world.spawn(); + world.insert(e, RenderComponent(render_channel.clone())); + world.add_handler(system::voxel_grid::handle_create_grid); + + init_world(&mut world); + let state = ClientState::new(); + // render_channel.send(RenderMessage::ViewUpdate(state.camera)).expect("GRRRR"); + Self { - window: None, - renderer: None, + window, exit: false, - frame_time: FRAME_TIME, - state: ClientState::new(), + render_handle: Some(render_handle), + renderer: render_channel, + state, input: Input::new(), - prev_frame: Instant::now(), prev_update: Instant::now(), - target: Instant::now(), grabbed_cursor: false, keep_cursor: false, + world, } } - pub fn start(&mut self) {} - pub fn update(&mut self) -> bool { let now = Instant::now(); let dt = now - self.prev_update; @@ -56,15 +68,32 @@ impl Client<'_> { self.handle_input(&dt); self.input.end(); - if now >= self.target { - self.target += self.frame_time; - self.prev_frame = now; - - let renderer = self.renderer.as_mut().unwrap(); - renderer.update(&self.state); - renderer.draw(); + if self.exit { + self.renderer.send(RenderMessage::Exit).expect("AAAA"); + self.render_handle + .take() + .expect("uh oh") + .join() + .expect("bruh"); } - self.exit } + + pub fn window_event(&mut self, event: WindowEvent) { + match event { + WindowEvent::CloseRequested => self.exit = true, + WindowEvent::Resized(size) => self + .renderer + .send(RenderMessage::Resize(size)) + .expect("render broke"), + WindowEvent::RedrawRequested => self + .renderer + .send(RenderMessage::Draw) + .expect("render broke"), + WindowEvent::CursorLeft { .. } => { + self.input.clear(); + } + _ => self.input.update_window(event), + } + } } diff --git a/src/client/render/mod.rs b/src/client/render/mod.rs index 6e0be11..6871a13 100644 --- a/src/client/render/mod.rs +++ b/src/client/render/mod.rs @@ -1,8 +1,183 @@ -mod buf; -mod instance; -mod renderer; -mod storage; +mod thread; +mod util; pub mod voxel; -mod uniform; -pub use renderer::*; +pub use thread::*; + +use super::camera::Camera; +use crate::client::rsc::{CLEAR_COLOR, FRAME_TIME}; +use nalgebra::Vector2; +use smaa::{SmaaMode, SmaaTarget}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use voxel::VoxelPipeline; +use winit::{ + dpi::PhysicalSize, + window::{Fullscreen, Window}, +}; + +pub struct Renderer<'a> { + size: Vector2, + surface: wgpu::Surface<'a>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + staging_belt: wgpu::util::StagingBelt, + voxel_pipeline: VoxelPipeline, + smaa_target: SmaaTarget, + camera: Camera, + + frame_time: Duration, + target: Instant, +} + +impl<'a> Renderer<'a> { + pub fn new(window: Arc) -> Self { + let fullscreen = false; + if fullscreen { + window.set_fullscreen(Some(Fullscreen::Borderless(None))); + } + + let size = window.inner_size(); + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + + let surface = instance + .create_surface(window) + .expect("Could not create window surface!"); + + let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + })) + .expect("Could not get adapter!"); + + let (device, queue) = pollster::block_on(adapter.request_device( + &wgpu::DeviceDescriptor { + label: None, + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + }, + None, // Trace path + )) + .expect("Could not get device!"); + + // TODO: use a logger + let info = adapter.get_info(); + println!("Adapter: {}", info.name); + println!("Backend: {:?}", info.backend); + + let surface_caps = surface.get_capabilities(&adapter); + // Set surface format to srbg + let surface_format = surface_caps + .formats + .iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(surface_caps.formats[0]); + + // create surface config + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + + surface.configure(&device, &config); + // not exactly sure what this number should be, + // doesn't affect performance much and depends on "normal" zoom + let staging_belt = wgpu::util::StagingBelt::new(4096 * 4); + + let smaa_target = SmaaTarget::new( + &device, + &queue, + size.width, + size.height, + surface_format, + SmaaMode::Smaa1X, + ); + + Self { + camera: Camera::default(), + size: Vector2::new(size.width, size.height), + voxel_pipeline: VoxelPipeline::new(&device, &config.format), + staging_belt, + surface, + device, + config, + queue, + smaa_target, + frame_time: FRAME_TIME, + target: Instant::now(), + } + } + + fn create_encoder(&mut self) -> wgpu::CommandEncoder { + self.device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }) + } + + pub fn draw(&mut self, encoder: &mut wgpu::CommandEncoder) { + let mut encoder = std::mem::replace(encoder, self.create_encoder()); + let output = self.surface.get_current_texture().unwrap(); + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let smaa_frame = self + .smaa_target + .start_frame(&self.device, &self.queue, &view); + { + let render_pass = &mut encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &smaa_frame, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(CLEAR_COLOR), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + self.voxel_pipeline.draw(render_pass); + } + smaa_frame.resolve(); + + self.staging_belt.finish(); + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + self.staging_belt.recall(); + } + + pub fn resize(&mut self, size: PhysicalSize, encoder: &mut wgpu::CommandEncoder) { + self.size = Vector2::new(size.width, size.height); + self.config.width = size.width; + self.config.height = size.height; + self.surface.configure(&self.device, &self.config); + self.smaa_target + .resize(&self.device, size.width, size.height); + + self.voxel_pipeline.update_view( + &self.device, + encoder, + &mut self.staging_belt, + self.size, + &self.camera, + ); + } +} diff --git a/src/client/render/renderer.rs b/src/client/render/renderer.rs deleted file mode 100644 index e3a7c2a..0000000 --- a/src/client/render/renderer.rs +++ /dev/null @@ -1,184 +0,0 @@ -use smaa::{SmaaTarget, SmaaMode}; -use super::voxel::VoxelPipeline; -use crate::client::{rsc::CLEAR_COLOR, ClientState}; -use std::sync::Arc; -use winit::{ - dpi::PhysicalSize, - window::{Fullscreen, Window}, -}; - -pub struct Renderer<'a> { - size: PhysicalSize, - surface: wgpu::Surface<'a>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - adapter: wgpu::Adapter, - encoder: Option, - staging_belt: wgpu::util::StagingBelt, - voxel_pipeline: VoxelPipeline, - smaa_target: SmaaTarget, -} - -impl<'a> Renderer<'a> { - pub fn new(window: Arc, fullscreen: bool) -> Self { - if fullscreen { - window.set_fullscreen(Some(Fullscreen::Borderless(None))); - } - - let size = window.inner_size(); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - - let surface = instance - .create_surface(window) - .expect("Could not create window surface!"); - - let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::default(), - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .expect("Could not get adapter!"); - - let (device, queue) = pollster::block_on(adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - }, - None, // Trace path - )) - .expect("Could not get device!"); - - // TODO: use a logger - let info = adapter.get_info(); - println!("Adapter: {}", info.name); - println!("Backend: {:?}", info.backend); - - let surface_caps = surface.get_capabilities(&adapter); - // Set surface format to srbg - let surface_format = surface_caps - .formats - .iter() - .copied() - .find(|f| f.is_srgb()) - .unwrap_or(surface_caps.formats[0]); - - // create surface config - let config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: surface_format, - width: size.width, - height: size.height, - present_mode: surface_caps.present_modes[0], - alpha_mode: surface_caps.alpha_modes[0], - view_formats: vec![], - desired_maximum_frame_latency: 2, - }; - - surface.configure(&device, &config); - // not exactly sure what this number should be, - // doesn't affect performance much and depends on "normal" zoom - let staging_belt = wgpu::util::StagingBelt::new(4096 * 4); - - let smaa_target = SmaaTarget::new( - &device, - &queue, - size.width, - size.height, - surface_format, - SmaaMode::Smaa1X, - ); - - Self { - size, - voxel_pipeline: VoxelPipeline::new(&device, &config.format), - encoder: None, - staging_belt, - surface, - device, - adapter, - config, - queue, - smaa_target, - } - } - - fn create_encoder(&mut self) -> wgpu::CommandEncoder { - self.device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }) - } - - pub fn draw(&mut self) { - let output = self.surface.get_current_texture().unwrap(); - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - let mut encoder = self.encoder.take().unwrap_or(self.create_encoder()); - let smaa_frame = self.smaa_target.start_frame(&self.device, &self.queue, &view); - { - let render_pass = &mut encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &smaa_frame, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(CLEAR_COLOR), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - timestamp_writes: None, - occlusion_query_set: None, - }); - self.voxel_pipeline.draw(render_pass); - } - smaa_frame.resolve(); - - self.staging_belt.finish(); - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); - self.staging_belt.recall(); - } - - pub fn update(&mut self, state: &ClientState) { - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - self.voxel_pipeline.update( - &self.device, - &mut encoder, - &mut self.staging_belt, - &RenderUpdateData { - state, - size: &self.size, - }, - ); - self.encoder = Some(encoder); - } - - pub fn resize(&mut self, size: PhysicalSize) { - self.size = size; - self.config.width = size.width; - self.config.height = size.height; - self.surface.configure(&self.device, &self.config); - self.smaa_target.resize(&self.device, size.width, size.height); - } - - pub fn size(&self) -> &PhysicalSize { - &self.size - } -} - -pub struct RenderUpdateData<'a> { - pub state: &'a ClientState, - pub size: &'a PhysicalSize, -} diff --git a/src/client/render/thread.rs b/src/client/render/thread.rs new file mode 100644 index 0000000..0f06c53 --- /dev/null +++ b/src/client/render/thread.rs @@ -0,0 +1,91 @@ +use crate::client::camera::Camera; + +use super::{voxel::VoxelColor, Renderer}; +use nalgebra::{Rotation3, Vector3}; +use std::{ + sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, + }, + thread::JoinHandle, + time::Instant, +}; +use winit::{dpi::PhysicalSize, window::Window}; + +#[derive(Debug)] +pub enum RenderMessage { + Resize(PhysicalSize), + Draw, + CreateVoxelGrid(CreateVoxelGrid), + ViewUpdate(Camera), + Exit, +} + +pub type RendererChannel = Sender; + +#[derive(Debug)] +pub struct CreateVoxelGrid { + pub pos: Vector3, + pub orientation: Rotation3, + pub dimensions: Vector3, + pub grid: Vec, +} + +impl Renderer<'_> { + pub fn spawn(window: Arc) -> (RendererChannel, JoinHandle<()>) { + let (s, mut r) = channel(); + ( + s, + std::thread::spawn(move || { + Self::new(window.clone()).start(&mut r); + }), + ) + } + + pub fn start(&mut self, reciever: &mut Receiver) { + let mut encoder = self.create_encoder(); + let mut new_camera = false; + 'main: loop { + let now = Instant::now(); + while let Ok(msg) = reciever.try_recv() { + match msg { + RenderMessage::CreateVoxelGrid(desc) => { + self.voxel_pipeline.add_group( + &self.device, + &mut encoder, + &mut self.staging_belt, + desc, + ); + } + RenderMessage::Draw => { + self.draw(&mut encoder); + } + RenderMessage::Resize(size) => { + self.resize(size, &mut encoder); + } + RenderMessage::Exit => { + break 'main; + } + RenderMessage::ViewUpdate(camera) => { + new_camera = true; + self.camera = camera; + } + } + } + if now >= self.target { + self.target = now + self.frame_time; + if new_camera { + self.voxel_pipeline.update_view( + &self.device, + &mut encoder, + &mut self.staging_belt, + self.size, + &self.camera, + ); + new_camera = false; + } + self.draw(&mut encoder); + } + } + } +} diff --git a/src/client/render/buf.rs b/src/client/render/util/buf.rs similarity index 91% rename from src/client/render/buf.rs rename to src/client/render/util/buf.rs index 799a85e..9a4801b 100644 --- a/src/client/render/buf.rs +++ b/src/client/render/util/buf.rs @@ -53,13 +53,11 @@ impl ArrBuf { &self.buffer, (update.offset * std::mem::size_of::()) as BufferAddress, unsafe { - std::num::NonZeroU64::new_unchecked( - (update.data.len() * std::mem::size_of::()) as u64, - ) + std::num::NonZeroU64::new_unchecked(std::mem::size_of_val(update.data) as u64) }, device, ); - view.copy_from_slice(bytemuck::cast_slice(&update.data)); + view.copy_from_slice(bytemuck::cast_slice(update.data)); } resized } @@ -100,11 +98,15 @@ impl ArrBuf { pub fn mov(&mut self, mov: BufMove) { self.moves.push(mov); } + + pub fn len(&self) -> usize { + self.len + } } -pub struct ArrBufUpdate { +pub struct ArrBufUpdate<'a, T> { pub offset: usize, - pub data: Vec, + pub data: &'a [T], } #[derive(Clone, Copy, Debug)] diff --git a/src/client/render/instance.rs b/src/client/render/util/instance.rs similarity index 100% rename from src/client/render/instance.rs rename to src/client/render/util/instance.rs diff --git a/src/client/render/util/mod.rs b/src/client/render/util/mod.rs new file mode 100644 index 0000000..66b2637 --- /dev/null +++ b/src/client/render/util/mod.rs @@ -0,0 +1,9 @@ +mod buf; +mod instance; +mod storage; +mod uniform; + +pub use buf::*; +pub use instance::*; +pub use storage::*; +pub use uniform::*; diff --git a/src/client/render/storage.rs b/src/client/render/util/storage.rs similarity index 95% rename from src/client/render/storage.rs rename to src/client/render/util/storage.rs index 891bab7..6bd6a94 100644 --- a/src/client/render/storage.rs +++ b/src/client/render/util/storage.rs @@ -52,4 +52,8 @@ impl Storage { pub fn mov(&mut self, mov: BufMove) { self.buf.mov(mov); } + + pub fn len(&mut self) -> usize { + self.buf.len() + } } diff --git a/src/client/render/uniform.rs b/src/client/render/util/uniform.rs similarity index 56% rename from src/client/render/uniform.rs rename to src/client/render/util/uniform.rs index 7bc4836..6c806ee 100644 --- a/src/client/render/uniform.rs +++ b/src/client/render/util/uniform.rs @@ -1,33 +1,28 @@ +use std::marker::PhantomData; + use wgpu::util::DeviceExt; -use super::RenderUpdateData; - -pub trait UniformData { - fn update(&mut self, data: &RenderUpdateData) -> bool; -} - -pub struct Uniform { - data: T, +pub struct Uniform { buffer: wgpu::Buffer, binding: u32, + ty: PhantomData, } -impl Uniform { +impl Uniform { pub fn init(device: &wgpu::Device, name: &str, binding: u32) -> Self { - let data = T::default(); Self { - data, buffer: device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some(&(name.to_owned() + " Uniform Buf")), - contents: bytemuck::cast_slice(&[data]), + contents: bytemuck::cast_slice(&[T::default()]), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }), binding, + ty: PhantomData, } } } -impl Uniform { +impl Uniform { pub fn bind_group_layout_entry(&self) -> wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry { binding: self.binding, @@ -51,22 +46,18 @@ impl Uniform { device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, - update_data: &RenderUpdateData, + data: T, ) { - if self.data.update(update_data) { - let slice = &[self.data]; - let mut view = belt.write_buffer( - encoder, - &self.buffer, - 0, - unsafe { - std::num::NonZeroU64::new_unchecked( - (slice.len() * std::mem::size_of::()) as u64, - ) - }, - device, - ); - view.copy_from_slice(bytemuck::cast_slice(slice)); - } + let slice = &[data]; + let mut view = belt.write_buffer( + encoder, + &self.buffer, + 0, + unsafe { + std::num::NonZeroU64::new_unchecked((slice.len() * std::mem::size_of::()) as u64) + }, + device, + ); + view.copy_from_slice(bytemuck::cast_slice(slice)); } } diff --git a/src/client/render/voxel/mod.rs b/src/client/render/voxel/mod.rs index ca7ecb4..d6a1d50 100644 --- a/src/client/render/voxel/mod.rs +++ b/src/client/render/voxel/mod.rs @@ -1,7 +1,189 @@ mod grid; mod view; -mod pipeline; mod color; mod group; -pub use pipeline::*; +pub use color::*; + +use nalgebra::{Projective3, Transform3, Translation3, Vector2}; + +use {group::VoxelGroup, view::View}; +use crate::client::{ + camera::Camera, + render::{ + util::{ArrBufUpdate, Storage, Uniform}, + CreateVoxelGrid, + }, +}; + +pub struct VoxelPipeline { + pipeline: wgpu::RenderPipeline, + view: Uniform, + bind_group_layout: wgpu::BindGroupLayout, + bind_group: wgpu::BindGroup, + voxel_groups: Storage, + voxels: Storage, +} + +impl VoxelPipeline { + pub fn new(device: &wgpu::Device, format: &wgpu::TextureFormat) -> Self { + // shaders + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Tile Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + }); + + let view = Uniform::::init(device, "view", 0); + let voxels = Storage::init(device, "voxels", 1); + let voxel_groups = Storage::init(device, "voxel groups", 2); + + // bind groups + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + entries: &[ + view.bind_group_layout_entry(), + voxels.bind_group_layout_entry(), + voxel_groups.bind_group_layout_entry(), + ], + label: Some("tile_bind_group_layout"), + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &bind_group_layout, + entries: &[ + view.bind_group_entry(), + voxels.bind_group_entry(), + voxel_groups.bind_group_entry(), + ], + label: Some("tile_bind_group"), + }); + + // pipeline + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Tile Pipeline Layout"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Voxel Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: *format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: None, + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: true, + }, + multiview: None, + }); + + Self { + pipeline: render_pipeline, + view, + bind_group, + bind_group_layout, + voxels, + voxel_groups, + } + } + + pub fn add_group( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + CreateVoxelGrid { + pos, + orientation, + dimensions, + grid, + }: CreateVoxelGrid, + ) { + let offset = self.voxels.len(); + let updates = [ArrBufUpdate { + offset, + data: &grid, + }]; + let size = offset + grid.len(); + self.voxels.update(device, encoder, belt, size, &updates); + + let proj = Projective3::identity() + * Translation3::from(pos) + * orientation + * Translation3::from(-dimensions.cast() / 2.0); + let group = VoxelGroup { + transform: proj, + transform_inv: proj.inverse(), + dimensions: dimensions.cast(), + offset: offset as u32, + }; + let updates = [ArrBufUpdate { + offset: self.voxel_groups.len(), + data: &[group], + }]; + let size = self.voxel_groups.len() + 1; + self.voxel_groups + .update(device, encoder, belt, size, &updates); + + self.bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.bind_group_layout, + entries: &[ + self.view.bind_group_entry(), + self.voxels.bind_group_entry(), + self.voxel_groups.bind_group_entry(), + ], + label: Some("tile_bind_group"), + }); + } + + pub fn update_view( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + size: Vector2, + camera: &Camera, + ) { + let transform = + Transform3::identity() * Translation3::from(camera.pos) * camera.orientation; + let data = View { + width: size.x, + height: size.y, + zoom: camera.scale, + padding: 0, + transform, + }; + self.view.update(device, encoder, belt, data) + } + + pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.draw(0..4, 0..1); + } +} diff --git a/src/client/render/voxel/shader.wgsl b/src/client/render/voxel/shader.wgsl index 09e48e0..fd22094 100644 --- a/src/client/render/voxel/shader.wgsl +++ b/src/client/render/voxel/shader.wgsl @@ -167,7 +167,7 @@ fn apply_group( var depth = 0u; var prev_a = 0.0; loop { - let i = u32(vox_pos.x + vox_pos.y * dim_i.x + vox_pos.z * dim_i.x * dim_i.y) + group.offset; + let i = u32(vox_pos.x * dim_i.y * dim_i.z + vox_pos.y * dim_i.z + vox_pos.z) + group.offset; var vcolor = unpack4x8unorm(voxels[i]); let normal = next_normal; @@ -204,7 +204,7 @@ fn apply_group( // lighting let light = trace_light(full_pos); - let diffuse = max(dot(norm_light, normal) * 1.3 + 0.1, 0.0); + let diffuse = max(dot(norm_light, normal) * ((dot(dir_view.xyz, normal) + 1.0) / 2.0 * .7 + .3) * 1.3 + 0.1, 0.0); let ambient = 0.2; let specular = (exp(max( -(dot(reflect(dir_view.xyz, normal), norm_light) + 0.90) * 4.0, 0.0 @@ -297,7 +297,7 @@ fn trace_one(gi: u32, pos_view: vec4, dir_view: vec4) -> vec4 { var next_t = inc_t * abs(pos - corner); var color = vec4(0.0); loop { - let i = u32(vox_pos.x + vox_pos.y * dim_i.x + vox_pos.z * dim_i.x * dim_i.y) + group.offset; + let i = u32(vox_pos.x * dim_i.y * dim_i.z + vox_pos.y * dim_i.z + vox_pos.z) + group.offset; var vcolor = unpack4x8unorm(voxels[i]); // select next voxel to move to next based on least time diff --git a/src/client/render/voxel/view.rs b/src/client/render/voxel/view.rs index 0e0667a..909571d 100644 --- a/src/client/render/voxel/view.rs +++ b/src/client/render/voxel/view.rs @@ -1,6 +1,4 @@ -use nalgebra::{Transform3, Translation3}; - -use crate::client::render::uniform::UniformData; +use nalgebra::Transform3; #[repr(C, align(16))] #[derive(Clone, Copy, PartialEq, bytemuck::Zeroable)] @@ -25,26 +23,3 @@ impl Default for View { } } } - -impl UniformData for View { - fn update(&mut self, data: &crate::client::render::RenderUpdateData) -> bool { - let camera = data.state.camera; - let new = Transform3::identity() * Translation3::from(camera.pos) * camera.orientation; - if new == self.transform - && data.size.width == self.width - && data.size.height == self.height - && camera.scale == self.zoom - { - false - } else { - *self = Self { - width: data.size.width, - height: data.size.height, - zoom: camera.scale, - padding: 0, - transform: new, - }; - true - } - } -} diff --git a/src/client/render/voxel_poly/color.rs b/src/client/render/voxel_poly/color.rs new file mode 100644 index 0000000..819cd72 --- /dev/null +++ b/src/client/render/voxel_poly/color.rs @@ -0,0 +1,48 @@ +use rand::distributions::{Distribution, Standard}; + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Zeroable, bytemuck::Pod)] +pub struct VoxelColor { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl VoxelColor { + pub fn none() -> Self { + Self { + r: 0, + g: 0, + b: 0, + a: 0, + } + } + pub fn black() -> Self { + Self { + r: 0, + g: 0, + b: 0, + a: 255, + } + } + pub fn white() -> Self { + Self { + r: 255, + g: 255, + b: 255, + a: 255, + } + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> VoxelColor { + VoxelColor { + r: rng.gen(), + g: rng.gen(), + b: rng.gen(), + a: rng.gen(), + } + } +} diff --git a/src/client/render/voxel/pipeline.rs b/src/client/render/voxel_poly/mod.rs similarity index 97% rename from src/client/render/voxel/pipeline.rs rename to src/client/render/voxel_poly/mod.rs index 7d0ec05..00729ca 100644 --- a/src/client/render/voxel/pipeline.rs +++ b/src/client/render/voxel_poly/mod.rs @@ -1,9 +1,10 @@ -use nalgebra::{Projective3, Rotation3, Transform3, Translation3, UnitVector3, Vector3}; +use super::uniform::Uniform; +use nalgebra::{Projective3, Translation3, Rotation3}; -use super::{color::VoxelColor, group::VoxelGroup, view::View}; -use crate::client::render::{ - buf::ArrBufUpdate, storage::Storage, uniform::Uniform, RenderUpdateData, -}; +mod view; +mod color; +mod vertex; +mod square; pub struct VoxelPipeline { pipeline: wgpu::RenderPipeline, diff --git a/src/client/render/voxel_poly/shader.wgsl b/src/client/render/voxel_poly/shader.wgsl new file mode 100644 index 0000000..acecf17 --- /dev/null +++ b/src/client/render/voxel_poly/shader.wgsl @@ -0,0 +1,69 @@ +// Vertex shader + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, +}; + +struct View { + transform: mat4x4, + width: u32, + height: u32, + zoom: f32, +}; + +struct VoxelGroup { + transform: mat4x4, + transform_inv: mat4x4, + dimensions: vec3, + offset: u32, +}; + +@group(0) @binding(0) +var view: View; +@group(0) @binding(1) +var voxels: array; +@group(0) @binding(2) +var voxel_groups: array; + +@vertex +fn vs_main( + @builtin(vertex_index) vi: u32, + @builtin(instance_index) ii: u32, +) -> VertexOutput { + var out: VertexOutput; + + var pos = vec2( + f32(vi % 2u) * 2.0 - 1.0, + f32(vi / 2u) * 2.0 - 1.0, + ) ; + out.clip_position = vec4(pos.x, pos.y, 0.0, 1.0); + out.tex_coords = pos; + return out; +} + +// Fragment shader + +@fragment +fn fs_main( + in: VertexOutput, +) -> @location(0) vec4 { + // get position of the pixel; eye at origin, pixel on plane z = 1 + let win_dim = vec2(f32(view.width), f32(view.height)); + let aspect = win_dim.y / win_dim.x; + let pixel_pos = vec3( + (in.clip_position.xy / win_dim - vec2(0.5)) * vec2(2.0, -2.0 * aspect), + 1.0 + ); + + // move to position in world + let pos = view.transform * vec4(pixel_pos, 1.0); + let dir = view.transform * vec4(normalize(pixel_pos), 0.0); + + var color = trace_full(pos, dir); + let light_mult = clamp((-dot(dir.xyz, normalize(GLOBAL_LIGHT)) - 0.99) * 200.0, 0.0, 1.0); + let sky_color = light_mult * vec3(1.0, 1.0, 1.0); + color += vec4(sky_color * (1.0 - color.a), 1.0 - color.a); + color.a = 1.0; + return color; +} diff --git a/src/client/render/voxel_poly/square.rs b/src/client/render/voxel_poly/square.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/client/render/voxel_poly/vertex.rs b/src/client/render/voxel_poly/vertex.rs new file mode 100644 index 0000000..e43134d --- /dev/null +++ b/src/client/render/voxel_poly/vertex.rs @@ -0,0 +1,6 @@ +#[repr(C)] +#[derive(Copy, Clone, Debug)] +struct Vertex { + position: [f32; 3], + color: [f32; 3], +} diff --git a/src/client/render/voxel_poly/view.rs b/src/client/render/voxel_poly/view.rs new file mode 100644 index 0000000..91c5a37 --- /dev/null +++ b/src/client/render/voxel_poly/view.rs @@ -0,0 +1,47 @@ +use nalgebra::{Transform3, Translation3}; + +use crate::client::render::uniform::UniformData; + +#[repr(C, align(16))] +#[derive(Clone, Copy, PartialEq, bytemuck::Zeroable)] +pub struct View { + pub transform: Transform3, + pub width: u32, + pub height: u32, + pub zoom: f32, +} + +unsafe impl bytemuck::Pod for View {} + +impl Default for View { + fn default() -> Self { + Self { + width: 1, + height: 1, + zoom: 1.0, + transform: Transform3::identity(), + } + } +} + +impl UniformData for View { + fn update(&mut self, data: &crate::client::render::RenderUpdateData) -> bool { + let camera = data.state.camera; + let new = Transform3::identity() * Translation3::from(camera.pos) * camera.orientation; + if new == self.transform + && data.size.width == self.width + && data.size.height == self.height + && camera.scale == self.zoom + { + false + } else { + *self = Self { + width: data.size.width, + height: data.size.height, + zoom: camera.scale, + transform: new, + }; + true + } + } +} diff --git a/src/client/rsc.rs b/src/client/rsc.rs index 6ac9319..a7a35b4 100644 --- a/src/client/rsc.rs +++ b/src/client/rsc.rs @@ -1,6 +1,6 @@ use std::time::Duration; -pub const FPS: u32 = 30; +pub const FPS: u32 = 60; pub const FRAME_TIME: Duration = Duration::from_millis(1000 / FPS as u64); pub const CLEAR_COLOR: wgpu::Color = wgpu::Color { diff --git a/src/client/system/mod.rs b/src/client/system/mod.rs new file mode 100644 index 0000000..8fef9bd --- /dev/null +++ b/src/client/system/mod.rs @@ -0,0 +1 @@ +pub mod voxel_grid; diff --git a/src/client/system/voxel_grid.rs b/src/client/system/voxel_grid.rs new file mode 100644 index 0000000..1effec8 --- /dev/null +++ b/src/client/system/voxel_grid.rs @@ -0,0 +1,49 @@ +use evenio::{ + event::{EventMut, GlobalEvent, Insert, ReceiverMut, Sender, Spawn}, + fetch::Single, +}; +use nalgebra::{Rotation3, Vector3}; +use ndarray::Axis; + +use crate::{ + client::{ + component::RenderComponent, + render::{CreateVoxelGrid, RenderMessage}, + }, + world::component::{Orientation, Pos, VoxelGrid}, +}; + +#[derive(GlobalEvent)] +pub struct SpawnVoxelGrid { + pub pos: Vector3, + pub orientation: Rotation3, + pub grid: VoxelGrid, +} + +pub fn handle_create_grid( + r: ReceiverMut, + renderer: Single<&RenderComponent>, + mut s: Sender<(Spawn, Insert, Insert, Insert)>, +) { + let SpawnVoxelGrid { + pos, + orientation, + grid, + } = EventMut::take(r.event); + renderer + .send(RenderMessage::CreateVoxelGrid(CreateVoxelGrid { + pos, + orientation, + dimensions: Vector3::new( + grid.len_of(Axis(0)), + grid.len_of(Axis(1)), + grid.len_of(Axis(2)), + ), + grid: grid.iter().cloned().collect(), + })) + .expect("render broke"); + let e = s.spawn(); + s.insert(e, Pos(pos)); + s.insert(e, Orientation(orientation)); + s.insert(e, grid); +} diff --git a/src/main.rs b/src/main.rs index ab7a09a..3f7c5ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,13 @@ -use client::Client; +use client::ClientApp; use winit::event_loop::EventLoop; mod client; mod util; +mod world; fn main() { let event_loop = EventLoop::new().expect("Failed to create event loop"); event_loop - .run_app(&mut Client::new()) + .run_app(&mut ClientApp::new()) .expect("Failed to run event loop"); } diff --git a/src/util/math.rs b/src/util/math.rs deleted file mode 100644 index 3a4839e..0000000 --- a/src/util/math.rs +++ /dev/null @@ -1,29 +0,0 @@ -use nalgebra::{Matrix4x3, Point2, Projective2, Transform2, Vector2, Vector3}; - -pub type Vec2f = Vector2; -pub type Vec3f = Vector3; -pub type Pos2f = Point2; -pub type Vec2us = Vector2; - -// hahaha.. HAHAHAAAAAAAAAAA... it's over now, surely I'll remember this lesson -pub trait Bruh { - fn gpu_mat3(&self) -> Matrix4x3; -} - -impl Bruh for Transform2 { - fn gpu_mat3(&self) -> Matrix4x3 { - let mut a = Matrix4x3::identity(); - // I LOVE GPU DATA STRUCTURE ALIGNMENT (it makes sense tho) - a.view_mut((0,0), (3,3)).copy_from(self.matrix()); - a - } -} - -impl Bruh for Projective2 { - fn gpu_mat3(&self) -> Matrix4x3 { - let mut a = Matrix4x3::identity(); - // I LOVE GPU DATA STRUCTURE ALIGNMENT (it makes sense tho) - a.view_mut((0,0), (3,3)).copy_from(self.matrix()); - a - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs index 884d869..7e7b477 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,2 +1 @@ pub mod swap_buf; -pub mod math; diff --git a/src/util/tracked_grid.rs b/src/util/tracked_grid.rs deleted file mode 100644 index b090840..0000000 --- a/src/util/tracked_grid.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::util::math::Vec2us; -use bevy_ecs::component::Component; -use nalgebra::{DMatrix, DimRange, Dyn}; -use std::ops::{Deref, Range}; - -pub type GridRegion = (Range, Range); -pub type GridView<'a, T> = nalgebra::Matrix< - T, - Dyn, - Dyn, - nalgebra::ViewStorageMut<'a, T, Dyn, Dyn, nalgebra::Const<1>, Dyn>, ->; - -#[derive(Clone, Component)] -pub struct TrackedGrid { - data: DMatrix, - changes: Vec, -} - -impl TrackedGrid { - pub fn new(data: DMatrix) -> Self { - Self { - data, - changes: Vec::new(), - } - } - pub fn width(&self) -> usize { - self.data.ncols() - } - pub fn height(&self) -> usize { - self.data.nrows() - } - pub fn view_range_mut, ColRange: DimRange>( - &mut self, - x_range: ColRange, - y_range: RowRange, - ) -> GridView<'_, T> { - let shape = self.data.shape(); - let r = Dyn(shape.0); - let rows = y_range.begin(r)..y_range.end(r); - let c = Dyn(shape.1); - let cols = x_range.begin(c)..x_range.end(c); - self.changes.push((rows.clone(), cols.clone())); - self.data.view_range_mut(rows, cols) - } - pub fn take_changes(&mut self) -> Vec { - std::mem::replace(&mut self.changes, Vec::new()) - } - pub fn change(&mut self, index: Vec2us) -> Option<&mut T> { - if let Some(d) = self.data.get_mut((index.y, index.x)) { - self.changes - .push((index.y..index.y + 1, index.x..index.x + 1)); - Some(d) - } else { - None - } - } -} - -impl Deref for TrackedGrid { - type Target = DMatrix; - fn deref(&self) -> &Self::Target { - &self.data - } -} - -// pub fn tile_pos(&self, pos: Pos2f) -> Option { -// let mut pos = self.orientation.inverse() * pos; -// pos += Vec2f::new( -// (self.size.x / 2) as f32 + 0.5, -// (self.size.y / 2) as f32 + 0.5, -// ); -// if pos.x < 0.0 || pos.y < 0.0 { -// return None; -// } -// let truncated = Vec2us::new(pos.x as usize, pos.y as usize); -// if truncated.x > self.size.x - 1 || truncated.y > self.size.y - 1 { -// return None; -// } -// Some(truncated) -// } diff --git a/src/world/component.rs b/src/world/component.rs new file mode 100644 index 0000000..d4c530e --- /dev/null +++ b/src/world/component.rs @@ -0,0 +1,93 @@ +use std::ops::{Deref, DerefMut, Range}; + +use evenio::component::Component; +use nalgebra::{Rotation3, Vector3}; +use ndarray::{Array3, ArrayBase, Dim, SliceArg}; + +use crate::client::render::voxel::VoxelColor; + +#[derive(Debug, Component, Default)] +pub struct Pos(pub Vector3); +#[derive(Debug, Component, Default)] +pub struct Orientation(pub Rotation3); + +pub type VoxelGrid = TrackedGrid; +pub type GridRegion = (Range, Range, Range); +#[derive(Debug, Clone, Component)] +pub struct TrackedGrid { + data: Array3, + changes: Vec, +} + +impl TrackedGrid { + pub fn new(data: Array3) -> Self { + Self { + data, + changes: Vec::new(), + } + } + pub fn view_slice_mut>>( + &mut self, + slice: I, + ) -> ArrayBase, >>::OutDim> { + self.data.slice_mut(slice) + } + pub fn take_changes(&mut self) -> Vec { + std::mem::take(&mut self.changes) + } +} + +impl Pos { + pub fn new(x: f32, y: f32, z: f32) -> Self { + Self(Vector3::new(x, y, z)) + } +} +impl Orientation { + pub fn from_axis_angle>>( + axis: &nalgebra::Unit, nalgebra::Const<1>, SB>>, + angle: f32, + ) -> Self { + Self(Rotation3::from_axis_angle(axis, angle)) + } +} +impl From> for Pos { + fn from(val: Vector3) -> Self { + Pos(val) + } +} +impl From> for Orientation { + fn from(val: Rotation3) -> Self { + Orientation(val) + } +} + +// reref boilerplate :pensive: + +impl Deref for Pos { + type Target = Vector3; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for Pos { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl Deref for Orientation { + type Target = Rotation3; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for Orientation { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl Deref for TrackedGrid { + type Target = Array3; + fn deref(&self) -> &Self::Target { + &self.data + } +} diff --git a/src/world/mod.rs b/src/world/mod.rs new file mode 100644 index 0000000..9cea807 --- /dev/null +++ b/src/world/mod.rs @@ -0,0 +1 @@ +pub mod component;