diff --git a/Cargo.lock b/Cargo.lock index f3a4dff..5267aae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,7 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "getrandom", "once_cell", "version_check", @@ -319,6 +319,17 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-mesh" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c0345520b5aa77d7d154bd578a18e7a974350931b5f41f641a7844e951b978f" +dependencies = [ + "ilattice", + "ndcopy", + "ndshape", +] + [[package]] name = "block2" version = "0.5.1" @@ -403,6 +414,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -713,7 +730,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "libc", "wasi", @@ -731,6 +748,12 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" + [[package]] name = "glow" version = "0.13.1" @@ -842,6 +865,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "ilattice" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3db9046391c0e0658a18e23877706d16e48cc8b4711702b9f703776410c2b84" +dependencies = [ + "glam", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -859,7 +891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", - "cfg-if", + "cfg-if 1.0.0", "combine", "jni-sys", "log", @@ -921,7 +953,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "winapi", ] @@ -931,7 +963,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "windows-targets 0.52.5", ] @@ -1020,7 +1052,7 @@ dependencies = [ "foreign-types", "log", "objc", - "paste", + "paste 1.0.15", ] [[package]] @@ -1086,6 +1118,15 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "ndcopy" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f4e41b81e3f7a4b742755be1bb368a6faafc43af555fe135c36b0efe095c59" +dependencies = [ + "ndshape", +] + [[package]] name = "ndk" version = "0.9.0" @@ -1125,6 +1166,15 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "ndshape" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975bce586ad637e27f6dc26ee907c07050686a588695bfd64b7873a9d48a700c" +dependencies = [ + "static_assertions", +] + [[package]] name = "nonmax" version = "0.5.5" @@ -1447,19 +1497,38 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall 0.5.1", "smallvec", "windows-targets 0.52.5", ] +[[package]] +name = "paste" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880" +dependencies = [ + "paste-impl", + "proc-macro-hack", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "paste-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6" +dependencies = [ + "proc-macro-hack", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1508,12 +1577,14 @@ version = "0.1.0" dependencies = [ "bevy_derive", "bevy_ecs", + "block-mesh", "bytemuck", "nalgebra", "ndarray", "pollster", "rand", "simba", + "simdnoise", "smaa", "wgpu", "winit", @@ -1531,7 +1602,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "concurrent-queue", "hermit-abi", "pin-project-lite", @@ -1576,6 +1647,12 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.82" @@ -1772,10 +1849,29 @@ dependencies = [ "approx", "num-complex", "num-traits", - "paste", + "paste 1.0.15", "wide", ] +[[package]] +name = "simdeez" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ec898e1be717eee4b54a84ff2fc94ecb5a1b992d4ad148ce30575b45745662" +dependencies = [ + "cfg-if 0.1.10", + "paste 0.1.18", +] + +[[package]] +name = "simdnoise" +version = "3.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f69a3fa031fc2906ffe27aecc55bdd2c9cb95327c4695ea814a083145fa462" +dependencies = [ + "simdeez", +] + [[package]] name = "slab" version = "0.4.9" @@ -1924,7 +2020,7 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", ] @@ -1937,7 +2033,7 @@ dependencies = [ "arrayref", "arrayvec", "bytemuck", - "cfg-if", + "cfg-if 1.0.0", "log", "tiny-skia-path", ] @@ -2069,7 +2165,7 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] @@ -2094,7 +2190,7 @@ version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", @@ -2275,7 +2371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e37c7b9921b75dfd26dd973fdcbce36f13dfa6e2dc82aece584e0ed48c355c" dependencies = [ "arrayvec", - "cfg-if", + "cfg-if 1.0.0", "cfg_aliases 0.1.1", "document-features", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 31e6447..9e892c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,8 +15,10 @@ ndarray = "0.15.6" pollster = "0.3" rand = "0.8.5" simba = "0.8.1" +simdnoise = "3.1.6" smaa = "0.14.0" wgpu = "0.20.1" bevy_ecs = "0.13.2" bevy_derive = "0.13.2" winit = {version="0.30.1", features=["serde"]} +block-mesh = "0.2.0" diff --git a/src/client/handle_input.rs b/src/client/handle_input.rs index edad11e..b956473 100644 --- a/src/client/handle_input.rs +++ b/src/client/handle_input.rs @@ -5,8 +5,8 @@ use ndarray::Array3; use winit::{dpi::PhysicalPosition, keyboard::KeyCode as Key, window::CursorGrabMode}; use crate::{ - sync::ServerMessage, - world::component::{VoxelGrid, VoxelGridBundle}, + common::ServerMessage, + common::component::{VoxelGrid, VoxelGridBundle}, }; use super::{render::voxel::VoxelColor, Client}; @@ -84,7 +84,7 @@ impl Client<'_> { } // camera position - let move_dist = 10.0 * dt; + let move_dist = 2.0 * 16.0 * dt; if input.pressed(Key::KeyW) { state.camera.pos += *state.camera.forward() * move_dist; } diff --git a/src/client/mod.rs b/src/client/mod.rs index 7c41803..3f888ca 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -18,7 +18,7 @@ use system::render::add_grid; use crate::{ server::Server, - sync::{ClientMessage, ServerHandle, ServerMessage}, + common::{ClientMessage, ServerHandle, ServerMessage}, }; use self::{input::Input, render::Renderer, ClientState}; @@ -54,6 +54,7 @@ pub struct Client<'a> { pub struct ClientSystems { render_add_grid: SystemId, render_update_transform: SystemId, + render_add_chunk: SystemId, } impl Client<'_> { @@ -70,7 +71,7 @@ impl Client<'_> { let state = ClientState::new(); let server = ServerHandle::spawn(Server::start); - server.send(ServerMessage::LoadWorld); + server.send(ServerMessage::Join); Self { window, @@ -85,6 +86,7 @@ impl Client<'_> { systems: ClientSystems { render_add_grid: world.register_system(add_grid), render_update_transform: world.register_system(system::render::update_transform), + render_add_chunk: world.register_system(system::render::add_chunk), }, world, server, @@ -110,6 +112,9 @@ impl Client<'_> { self.world .run_system(self.systems.render_update_transform) .expect("WHAT"); + self.world + .run_system(self.systems.render_add_chunk) + .expect("WHAT v3"); self.world.clear_trackers(); if self.state.camera.pos.y < -10.0 { @@ -129,8 +134,6 @@ impl Client<'_> { } if self.exit { - self.server.send(ServerMessage::Stop); - self.server.join(); event_loop.exit(); } } @@ -139,8 +142,12 @@ impl Client<'_> { for msg in self.server.recv() { match msg { ClientMessage::SpawnVoxelGrid(entity, grid) => { - let cid = self.world.spawn(grid).id(); - self.server_id_map.insert(entity, cid); + let id = self.world.spawn(grid).id(); + self.server_id_map.insert(entity, id); + } + ClientMessage::LoadChunk(entity, chunk) => { + let id = self.world.spawn(chunk).id(); + self.server_id_map.insert(entity, id); } ClientMessage::PosUpdate(e, pos) => { if let Some(id) = self.server_id_map.get(&e) { diff --git a/src/client/render/command.rs b/src/client/render/command.rs index b8d2a75..cb3dcca 100644 --- a/src/client/render/command.rs +++ b/src/client/render/command.rs @@ -1,4 +1,7 @@ -use crate::client::camera::Camera; +use crate::{ + client::camera::Camera, + common::component::{ChunkMesh, ChunkPos}, +}; use super::{voxel::VoxelColor, Renderer}; use bevy_ecs::entity::Entity; @@ -10,6 +13,7 @@ use winit::window::Window; #[derive(Debug, Clone)] pub enum RenderCommand { CreateVoxelGrid(CreateVoxelGrid), + AddChunk(AddChunk), UpdateGridTransform(UpdateGridTransform), ViewUpdate(Camera), } @@ -23,6 +27,13 @@ pub struct CreateVoxelGrid { pub grid: Array3, } +#[derive(Debug, Clone)] +pub struct AddChunk { + pub id: Entity, + pub pos: ChunkPos, + pub mesh: ChunkMesh, +} + #[derive(Debug, Clone)] pub struct UpdateGridTransform { pub id: Entity, @@ -49,26 +60,28 @@ impl<'a> Renderer<'a> { let mut new_camera = false; for cmd in commands { match cmd { - RenderCommand::CreateVoxelGrid(desc) => { - self.voxel_pipeline.add_group( - &self.device, - &mut self.encoder, - &mut self.staging_belt, - desc, - ); - } + RenderCommand::CreateVoxelGrid(desc) => self.voxel_pipeline.add_group( + &self.device, + &mut self.encoder, + &mut self.staging_belt, + desc, + ), RenderCommand::ViewUpdate(camera) => { new_camera = true; self.camera = camera; } - RenderCommand::UpdateGridTransform(update) => { - self.voxel_pipeline.update_transform( - &self.device, - &mut self.encoder, - &mut self.staging_belt, - update, - ); - } + RenderCommand::UpdateGridTransform(update) => self.voxel_pipeline.update_transform( + &self.device, + &mut self.encoder, + &mut self.staging_belt, + update, + ), + RenderCommand::AddChunk(desc) => self.voxel_pipeline.add_chunk( + &self.device, + &mut self.encoder, + &mut self.staging_belt, + desc, + ), } } if new_camera { diff --git a/src/client/render/mod.rs b/src/client/render/mod.rs index afc77c5..67856f8 100644 --- a/src/client/render/mod.rs +++ b/src/client/render/mod.rs @@ -1,16 +1,14 @@ mod command; mod util; pub mod voxel; -pub mod voxel_poly; - pub use command::*; -use util::Texture; use super::camera::Camera; use crate::client::rsc::CLEAR_COLOR; use nalgebra::Vector2; use smaa::{SmaaMode, SmaaTarget}; -use voxel_poly::VoxelPipeline; +use util::Texture; +use voxel::VoxelPipeline; use winit::dpi::PhysicalSize; pub struct Renderer<'a> { diff --git a/src/client/render/voxel/mod.rs b/src/client/render/voxel/mod.rs index 35ca5a7..ed69b62 100644 --- a/src/client/render/voxel/mod.rs +++ b/src/client/render/voxel/mod.rs @@ -1,239 +1,3 @@ -mod color; -mod grid; -mod group; -mod light; -mod view; - -use std::collections::HashMap; - -use bevy_ecs::entity::Entity; -pub use color::*; - -use light::GlobalLight; -use nalgebra::{Projective3, Transform3, Translation3, Vector2, Vector3}; - -use super::UpdateGridTransform; -use crate::client::{ - camera::Camera, - render::{ - util::{ArrBufUpdate, Storage, Uniform}, - CreateVoxelGrid, - }, -}; - -use {group::VoxelGroup, view::View}; - -pub struct VoxelPipeline { - pipeline: wgpu::RenderPipeline, - view: Uniform, - bind_group_layout: wgpu::BindGroupLayout, - bind_group: wgpu::BindGroup, - voxel_groups: Storage, - voxels: Storage, - global_lights: Storage, - id_map: HashMap, -} - -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); - let global_lights = Storage::init_with( - device, - "global lights", - 3, - &[GlobalLight { - direction: Vector3::new(-0.5, -4.0, 2.0).normalize(), - }], - ); - - // 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(), - global_lights.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(), - global_lights.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, - global_lights, - id_map: HashMap::new(), - } - } - - pub fn add_group( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - CreateVoxelGrid { - id, - pos, - orientation, - dimensions, - grid, - }: CreateVoxelGrid, - ) { - let offset = self.voxels.len(); - - let updates = [ArrBufUpdate { - offset, - data: &grid.as_slice().unwrap(), - }]; - 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 i = self.voxel_groups.len(); - let size = i + 1; - self.voxel_groups - .update(device, encoder, belt, size, &updates); - - self.id_map.insert(id, (i, group)); - - 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(), - self.global_lights.bind_group_entry(), - ], - label: Some("tile_bind_group"), - }); - } - - pub fn update_transform( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - belt: &mut wgpu::util::StagingBelt, - update: UpdateGridTransform, - ) { - if let Some((i, group)) = self.id_map.get_mut(&update.id) { - let proj = Projective3::identity() - * Translation3::from(update.pos) - * update.orientation - * Translation3::from(-group.dimensions.cast() / 2.0); - group.transform = proj; - group.transform_inv = proj.inverse(); - let updates = [ArrBufUpdate { - offset: *i, - data: &[*group], - }]; - let size = self.voxel_groups.len(); - self.voxel_groups - .update(device, encoder, belt, size, &updates); - } - } - - 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, - 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); - } -} +mod poly; +// mod ray; +pub use poly::*; diff --git a/src/client/render/voxel_poly/color.rs b/src/client/render/voxel/poly/color.rs similarity index 73% rename from src/client/render/voxel_poly/color.rs rename to src/client/render/voxel/poly/color.rs index 819cd72..0347f24 100644 --- a/src/client/render/voxel_poly/color.rs +++ b/src/client/render/voxel/poly/color.rs @@ -1,3 +1,4 @@ +use block_mesh::Voxel; use rand::distributions::{Distribution, Standard}; #[repr(C)] @@ -46,3 +47,13 @@ impl Distribution for Standard { } } } + +impl block_mesh::Voxel for VoxelColor { + fn get_visibility(&self) -> block_mesh::VoxelVisibility { + match self.a { + 0 => block_mesh::VoxelVisibility::Empty, + 255 => block_mesh::VoxelVisibility::Opaque, + _ => block_mesh::VoxelVisibility::Translucent, + } + } +} diff --git a/src/client/render/voxel_poly/instance.rs b/src/client/render/voxel/poly/face.rs similarity index 80% rename from src/client/render/voxel_poly/instance.rs rename to src/client/render/voxel/poly/face.rs index 4c29adb..a587c57 100644 --- a/src/client/render/voxel_poly/instance.rs +++ b/src/client/render/voxel/poly/face.rs @@ -1,6 +1,6 @@ use bytemuck::Zeroable; -use crate::client::render::voxel::VoxelColor; +use super::VoxelColor; #[repr(C)] #[derive(Copy, Clone, Debug, Zeroable)] diff --git a/src/client/render/voxel_poly/group.rs b/src/client/render/voxel/poly/group.rs similarity index 100% rename from src/client/render/voxel_poly/group.rs rename to src/client/render/voxel/poly/group.rs diff --git a/src/client/render/voxel/light.rs b/src/client/render/voxel/poly/light.rs similarity index 100% rename from src/client/render/voxel/light.rs rename to src/client/render/voxel/poly/light.rs diff --git a/src/client/render/voxel_poly/mod.rs b/src/client/render/voxel/poly/mod.rs similarity index 65% rename from src/client/render/voxel_poly/mod.rs rename to src/client/render/voxel/poly/mod.rs index 3b1545d..8d59510 100644 --- a/src/client/render/voxel_poly/mod.rs +++ b/src/client/render/voxel/poly/mod.rs @@ -1,21 +1,26 @@ mod color; +mod face; mod group; -mod instance; -mod square; +mod light; mod view; -use core::panic; +use block_mesh::{ndshape::RuntimeShape, UnitQuadBuffer, RIGHT_HANDED_Y_UP_CONFIG}; +pub use color::*; +pub use face::*; use group::FaceGroup; -use instance::VoxelFace; +use light::GlobalLight; use nalgebra::{Perspective3, Transform3, Translation3, Vector2, Vector3}; use view::View; use wgpu::{SurfaceConfiguration, VertexAttribute, VertexFormat}; -use crate::client::camera::Camera; +use crate::{ + client::{camera::Camera, render::AddChunk}, + common::component::{chunk, ChunkData}, +}; -use super::{ - util::{Instances, Texture, Uniform}, +use super::super::{ + util::{Instances, Storage, Texture, Uniform}, CreateVoxelGrid, UpdateGridTransform, }; @@ -25,6 +30,7 @@ pub struct VoxelPipeline { bind_group_layout: wgpu::BindGroupLayout, bind_groups: Vec, vertices: Vec>, + global_lights: Storage, } const INSTANCE_ATTRS: [wgpu::VertexAttribute; 2] = [ @@ -52,12 +58,21 @@ impl VoxelPipeline { let example_faces = Instances::::init(device, "voxel groups", 0, &INSTANCE_ATTRS); let example_group = Uniform::::init(device, "voxel group", 1); + let global_lights = Storage::init_with( + device, + "global lights", + 3, + &[GlobalLight { + direction: Vector3::new(-0.5, -4.0, 2.0).normalize(), + }], + ); // bind groups let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ view.bind_group_layout_entry(), example_group.bind_group_layout_entry(), + global_lights.bind_group_layout_entry(), ], label: Some("tile_bind_group_layout"), }); @@ -84,7 +99,7 @@ impl VoxelPipeline { entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { format: config.format, - blend: Some(wgpu::BlendState::REPLACE), + blend: Some(wgpu::BlendState::ALPHA_BLENDING), write_mask: wgpu::ColorWrites::ALL, })], compilation_options: wgpu::PipelineCompilationOptions::default(), @@ -119,6 +134,7 @@ impl VoxelPipeline { bind_group_layout, bind_groups: Vec::new(), vertices: Vec::new(), + global_lights, } } @@ -138,7 +154,7 @@ impl VoxelPipeline { size.x as f32 / size.y as f32, std::f32::consts::PI / 2.0, 0.1, - 1000.0, + 10000.0, ); transform = projection.as_matrix() * transform; let data = View { @@ -177,46 +193,31 @@ impl VoxelPipeline { * Translation3::from(pos) * orientation * Translation3::from(-dimensions.cast() / 2.0); - for face in 0..6 { - let group = FaceGroup { - dimensions: dimensions.cast(), - transform: proj, - face, - }; - let uniform = Uniform::init_with(device, "voxel group", 1, &[group]); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.bind_group_layout, - entries: &[self.view.bind_group_entry(), uniform.bind_group_entry()], - label: Some("voxel bind group"), - }); - self.bind_groups.push(bind_group); - let mut data = Vec::new(); - let n_offset = match face % 3 { - 0 => 1, - 1 => dimensions.z * dimensions.y, - 2 => dimensions.z, - _ => 0, - } as i32 - * ((face as i32 / 3) * 2 - 1); - let face_dir = (face as i32 / 3) * 2 - 1; - for (i, ((x, y, z), color)) in grid.indexed_iter().enumerate() { - let neighbor = match face { - 0 => if z > 0 {Some((x, y, z - 1))} else {None}, - 2 => if y > 0 {Some((x, y - 1, z))} else {None}, - 1 => if x > 0 {Some((x - 1, y, z))} else {None}, - 3 => if z < dimensions.z - 1 {Some((x, y, z + 1))} else {None}, - 5 => if y < dimensions.y - 1 {Some((x, y + 1, z))} else {None}, - 4 => if x < dimensions.x - 1 {Some((x + 1, y, z))} else {None}, - _ => panic!("what"), - }.map(|p| grid.get(p).unwrap()); - if color.a > 0 && !neighbor.is_some_and(|c| c.a == color.a) { - data.push(VoxelFace { - index: i as u32, - color: *color, - }); - } - } + let mut buffer = UnitQuadBuffer::new(); + let dim: Vector3 = dimensions.cast(); + let dim = Vector3::new(dim.z, dim.y, dim.x); + let shape = RuntimeShape::::new(dim.into()); + let slice = grid.as_slice().unwrap(); + block_mesh::visible_block_faces( + slice, + &shape, + [0; 3], + (dim - Vector3::new(1, 1, 1)).into(), + &RIGHT_HANDED_Y_UP_CONFIG.faces, + &mut buffer, + ); + for (face, group) in buffer.groups.iter().enumerate() { + let data: Vec = group + .iter() + .map(|a| { + let i = a.minimum[0] + a.minimum[1] * dim.y + a.minimum[2] * dim.y * dim.x; + VoxelFace { + index: i, + color: slice[i as usize], + } + }) + .collect(); self.vertices.push(Instances::init_with( device, "vvvvv", @@ -224,6 +225,64 @@ impl VoxelPipeline { &INSTANCE_ATTRS, &data, )); + let group = FaceGroup { + dimensions: dimensions.cast(), + transform: proj, + face: ((8 - face) % 6) as u32, + }; + let uniform = Uniform::init_with(device, "voxel group", 1, &[group]); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.bind_group_layout, + entries: &[ + self.view.bind_group_entry(), + uniform.bind_group_entry(), + self.global_lights.bind_group_entry(), + ], + label: Some("voxel bind group"), + }); + self.bind_groups.push(bind_group); + } + } + + pub fn add_chunk( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + AddChunk { id, pos, mesh }: AddChunk, + ) { + if mesh.faces.iter().all(|f| f.is_empty()) { + return; + } + + let proj = Transform3::identity() + * Translation3::from(pos.cast() * crate::common::component::chunk::SIDE_LENGTH as f32) + * Translation3::from(-chunk::DIMENSIONS.cast() / 2.0); + + for (face, meshes) in mesh.faces.iter().enumerate() { + self.vertices.push(Instances::init_with( + device, + "vvvvv", + 0, + &INSTANCE_ATTRS, + meshes, + )); + let group = FaceGroup { + dimensions: chunk::DIMENSIONS.cast(), + transform: proj, + face: face as u32, + }; + let uniform = Uniform::init_with(device, "voxel group", 1, &[group]); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.bind_group_layout, + entries: &[ + self.view.bind_group_entry(), + uniform.bind_group_entry(), + self.global_lights.bind_group_entry(), + ], + label: Some("voxel bind group"), + }); + self.bind_groups.push(bind_group); } } diff --git a/src/client/render/voxel_poly/shader.wgsl b/src/client/render/voxel/poly/shader.wgsl similarity index 66% rename from src/client/render/voxel_poly/shader.wgsl rename to src/client/render/voxel/poly/shader.wgsl index 9206e92..4dbaeae 100644 --- a/src/client/render/voxel_poly/shader.wgsl +++ b/src/client/render/voxel/poly/shader.wgsl @@ -8,6 +8,7 @@ struct InstanceInput { struct VertexOutput { @builtin(position) clip_position: vec4, @location(0) color: vec4, + @location(1) normal: vec3, }; struct VoxelFace { @@ -28,16 +29,16 @@ struct VoxelGroup { face: u32, }; +struct GlobalLight { + dir: vec3, +}; + @group(0) @binding(0) var view: View; @group(0) @binding(1) var group: VoxelGroup; - -const DIRECTIONS = array( - vec3(1.0, 1.0, 0.0), - vec3(0.0, 1.0, 1.0), - vec3(1.0, 0.0, 1.0), -); +@group(0) @binding(3) +var global_lights: array; @vertex fn vs_main( @@ -48,14 +49,23 @@ fn vs_main( let invert = select(0.0, 1.0, group.face / 3 == 1); let invert_mult = 1.0 - invert * 2.0; + let face_axis = group.face % 3; var square_pos = vec2( f32(vi % 2u), invert + invert_mult * f32(vi / 2u), ); var cube_pos = vec3(invert); square_pos *= invert_mult; - cube_pos[(group.face) % 3] += square_pos.x; - cube_pos[(group.face + 1) % 3] += square_pos.y; + cube_pos[(group.face + 1) % 3] += square_pos.x; + cube_pos[(group.face + 2) % 3] += square_pos.y; + + let cube_normal = invert_mult * vec3( + f32(face_axis == 0), + f32(face_axis % 2), + f32(face_axis / 2), + ); + out.normal = (group.transform * vec4(cube_normal, 0.0)).xyz; + var pos = vec4( cube_pos, 1.0, @@ -78,5 +88,9 @@ fn vs_main( fn fs_main( in: VertexOutput, ) -> @location(0) vec4 { - return in.color; + let diffuse = max(dot(global_lights[0].dir, in.normal) + 0.1, 0.0); + let ambient = 0.2; + let lighting = max(diffuse, ambient); + let new_rgb = min(in.color.xyz * lighting, vec3(1.0)); + return vec4(new_rgb, in.color.a); } diff --git a/src/client/render/voxel_poly/view.rs b/src/client/render/voxel/poly/view.rs similarity index 100% rename from src/client/render/voxel_poly/view.rs rename to src/client/render/voxel/poly/view.rs diff --git a/src/client/render/voxel/color.rs b/src/client/render/voxel/ray/color.rs similarity index 100% rename from src/client/render/voxel/color.rs rename to src/client/render/voxel/ray/color.rs diff --git a/src/client/render/voxel/grid.rs b/src/client/render/voxel/ray/grid.rs similarity index 100% rename from src/client/render/voxel/grid.rs rename to src/client/render/voxel/ray/grid.rs diff --git a/src/client/render/voxel/group.rs b/src/client/render/voxel/ray/group.rs similarity index 100% rename from src/client/render/voxel/group.rs rename to src/client/render/voxel/ray/group.rs diff --git a/src/client/render/voxel/ray/light.rs b/src/client/render/voxel/ray/light.rs new file mode 100644 index 0000000..643d06c --- /dev/null +++ b/src/client/render/voxel/ray/light.rs @@ -0,0 +1,9 @@ +use nalgebra::Vector3; + +#[repr(C, align(16))] +#[derive(Clone, Copy, PartialEq, bytemuck::Zeroable)] +pub struct GlobalLight { + pub direction: Vector3, +} + +unsafe impl bytemuck::Pod for GlobalLight {} diff --git a/src/client/render/voxel/ray/mod.rs b/src/client/render/voxel/ray/mod.rs new file mode 100644 index 0000000..731a5cf --- /dev/null +++ b/src/client/render/voxel/ray/mod.rs @@ -0,0 +1,237 @@ +mod color; +mod grid; +mod group; +mod light; +mod view; + +pub use color::*; + +use super::super::UpdateGridTransform; +use crate::client::{ + camera::Camera, + render::{ + util::{ArrBufUpdate, Storage, Uniform}, + CreateVoxelGrid, + }, +}; +use bevy_ecs::entity::Entity; +use light::GlobalLight; +use nalgebra::{Projective3, Transform3, Translation3, Vector2, Vector3}; +use std::collections::HashMap; + +use {group::VoxelGroup, view::View}; + +pub struct VoxelPipeline { + pipeline: wgpu::RenderPipeline, + view: Uniform, + bind_group_layout: wgpu::BindGroupLayout, + bind_group: wgpu::BindGroup, + voxel_groups: Storage, + voxels: Storage, + global_lights: Storage, + id_map: HashMap, +} + +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); + let global_lights = Storage::init_with( + device, + "global lights", + 3, + &[GlobalLight { + direction: Vector3::new(-0.5, -4.0, 2.0).normalize(), + }], + ); + + // 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(), + global_lights.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(), + global_lights.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, + global_lights, + id_map: HashMap::new(), + } + } + + pub fn add_group( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + CreateVoxelGrid { + id, + pos, + orientation, + dimensions, + grid, + }: CreateVoxelGrid, + ) { + let offset = self.voxels.len(); + + let updates = [ArrBufUpdate { + offset, + data: &grid.as_slice().unwrap(), + }]; + 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 i = self.voxel_groups.len(); + let size = i + 1; + self.voxel_groups + .update(device, encoder, belt, size, &updates); + + self.id_map.insert(id, (i, group)); + + 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(), + self.global_lights.bind_group_entry(), + ], + label: Some("tile_bind_group"), + }); + } + + pub fn update_transform( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + update: UpdateGridTransform, + ) { + if let Some((i, group)) = self.id_map.get_mut(&update.id) { + let proj = Projective3::identity() + * Translation3::from(update.pos) + * update.orientation + * Translation3::from(-group.dimensions.cast() / 2.0); + group.transform = proj; + group.transform_inv = proj.inverse(); + let updates = [ArrBufUpdate { + offset: *i, + data: &[*group], + }]; + let size = self.voxel_groups.len(); + self.voxel_groups + .update(device, encoder, belt, size, &updates); + } + } + + 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, + 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/ray/shader.wgsl similarity index 100% rename from src/client/render/voxel/shader.wgsl rename to src/client/render/voxel/ray/shader.wgsl diff --git a/src/client/render/voxel/shader_stuff.wgsl b/src/client/render/voxel/ray/shader_stuff.wgsl similarity index 100% rename from src/client/render/voxel/shader_stuff.wgsl rename to src/client/render/voxel/ray/shader_stuff.wgsl diff --git a/src/client/render/voxel/view.rs b/src/client/render/voxel/ray/view.rs similarity index 100% rename from src/client/render/voxel/view.rs rename to src/client/render/voxel/ray/view.rs diff --git a/src/client/render/voxel_poly/square.rs b/src/client/render/voxel_poly/square.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/client/rsc.rs b/src/client/rsc.rs index a7a35b4..9be80c3 100644 --- a/src/client/rsc.rs +++ b/src/client/rsc.rs @@ -4,9 +4,9 @@ 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 { - r: 0.1, - g: 0.1, - b: 0.1, + r: 0.5, + g: 0.8, + b: 1.0, a: 1.0, }; diff --git a/src/client/system/render.rs b/src/client/system/render.rs index fa6f36f..a2abb5b 100644 --- a/src/client/system/render.rs +++ b/src/client/system/render.rs @@ -5,15 +5,15 @@ use bevy_ecs::{ query::{Added, Changed, Or}, system::{Query, ResMut}, }; -use nalgebra::Vector3; -use ndarray::Axis; +use nalgebra::{AbstractRotation, Rotation3, Vector3}; +use ndarray::{Array3, Axis}; use crate::{ client::{ component::RenderCommands, - render::{CreateVoxelGrid, RenderCommand, UpdateGridTransform}, + render::{voxel::VoxelColor, AddChunk, CreateVoxelGrid, RenderCommand, UpdateGridTransform}, }, - world::component::{Orientation, Pos, VoxelGrid}, + common::component::{ChunkData, ChunkMesh, ChunkPos, Orientation, Pos, VoxelGrid}, }; pub fn add_grid( @@ -24,16 +24,21 @@ pub fn add_grid( mut renderer: ResMut, ) { for (id, pos, orientation, grid) in query.iter() { + let dims = Vector3::new( + grid.len_of(Axis(0)) + 2, + grid.len_of(Axis(1)) + 2, + grid.len_of(Axis(2)) + 2, + ); + let mut padded = Array3::from_elem((dims.x, dims.y, dims.z), VoxelColor::none()); + padded + .slice_mut(ndarray::s![1..dims.x - 1, 1..dims.y - 1, 1..dims.z - 1]) + .assign(grid); renderer.push(RenderCommand::CreateVoxelGrid(CreateVoxelGrid { id, pos: **pos, orientation: **orientation, - dimensions: Vector3::new( - grid.len_of(Axis(0)), - grid.len_of(Axis(1)), - grid.len_of(Axis(2)), - ), - grid: grid.deref().clone(), + dimensions: dims, + grid: padded, })); } } @@ -50,3 +55,16 @@ pub fn update_transform( })); } } + +pub fn add_chunk( + query: Query<(Entity, &ChunkPos, &ChunkMesh), Or<(Added, Added)>>, + mut renderer: ResMut, +) { + for (id, pos, mesh) in query.iter() { + renderer.push(RenderCommand::AddChunk(AddChunk { + id, + pos: *pos, + mesh: mesh.clone() + })); + } +} diff --git a/src/common/component/chunk.rs b/src/common/component/chunk.rs new file mode 100644 index 0000000..4cae656 --- /dev/null +++ b/src/common/component/chunk.rs @@ -0,0 +1,124 @@ +use std::collections::{HashMap, HashSet}; + +use crate::{ + client::render::voxel::{VoxelColor, VoxelFace}, + util::oct_tree::OctTree, +}; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{bundle::Bundle, component::Component, entity::Entity, system::Resource}; +use block_mesh::{ndshape::RuntimeShape, UnitQuadBuffer, RIGHT_HANDED_Y_UP_CONFIG}; +use nalgebra::Vector3; +use ndarray::{s, Array3, Axis}; + +pub const SIDE_LENGTH: usize = 16 * 16; +pub const SHAPE: (usize, usize, usize) = (SIDE_LENGTH, SIDE_LENGTH, SIDE_LENGTH); +pub const DIMENSIONS: Vector3 = Vector3::new(SIDE_LENGTH, SIDE_LENGTH, SIDE_LENGTH); +pub const LEN: usize = SHAPE.0 * SHAPE.1 * SHAPE.2; + +#[derive(Debug, Component, Clone, Deref, DerefMut)] +pub struct ChunkData { + #[deref] + data: OctTree, +} + +impl ChunkData { + pub fn empty() -> Self { + Self { + data: OctTree::Leaf(VoxelColor::none()), + } + } + + pub fn from_tree(t: OctTree) -> Self { + Self { data: t } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Component, Default, Deref, DerefMut)] +pub struct ChunkPos(pub Vector3); +impl ChunkPos { + pub fn new(x: i32, y: i32, z: i32) -> Self { + Self(Vector3::new(x, y, z)) + } +} +impl From> for ChunkPos { + fn from(val: Vector3) -> Self { + ChunkPos(val) + } +} + +#[derive(Debug, Clone, Component)] +pub struct ChunkMesh { + pub faces: [Vec; 6], +} + +impl ChunkMesh { + pub fn from_data(data: &Array3) -> Self { + let dim_pad = Vector3::new( + data.len_of(Axis(0)) as u32, + data.len_of(Axis(1)) as u32, + data.len_of(Axis(2)) as u32, + ); + let dim = dim_pad - Vector3::from_element(2); + let mut buffer = UnitQuadBuffer::new(); + let shape = RuntimeShape::::new(dim_pad.into()); + let slice = data.as_slice().unwrap(); + block_mesh::visible_block_faces( + slice, + &shape, + [0; 3], + (dim_pad - Vector3::new(1, 1, 1)).into(), + &RIGHT_HANDED_Y_UP_CONFIG.faces, + &mut buffer, + ); + let faces = [2, 1, 0, 5, 4, 3].map(|f| { + buffer.groups[f] + .iter() + .map(|a| { + let i = (a.minimum[0]-1) + (a.minimum[1]-1) * dim.y + (a.minimum[2]-1) * dim.y * dim.x; + let i_pad = a.minimum[0] + a.minimum[1] * dim_pad.y + a.minimum[2] * dim_pad.y * dim_pad.x; + VoxelFace { + index: i, + color: slice[i_pad as usize], + } + }) + .collect() + }); + Self { faces } + } +} + +#[derive(Debug, Clone, Component, Deref, DerefMut)] +pub struct LoadedChunks { + loaded: HashSet, +} + +impl LoadedChunks { + pub fn new() -> Self { + Self { + loaded: HashSet::new(), + } + } +} + +#[derive(Resource, Deref, DerefMut)] +pub struct ChunkMap { + #[deref] + map: HashMap, + pub generating: HashSet, +} + +impl ChunkMap { + pub fn new() -> Self { + Self { + map: HashMap::new(), + generating: HashSet::new(), + } + } +} + +#[derive(Bundle, Clone)] +pub struct ChunkBundle { + pub pos: ChunkPos, + pub data: ChunkData, + pub mesh: ChunkMesh, +} diff --git a/src/world/component.rs b/src/common/component/grid.rs similarity index 54% rename from src/world/component.rs rename to src/common/component/grid.rs index 229a7ef..f134faf 100644 --- a/src/world/component.rs +++ b/src/common/component/grid.rs @@ -1,19 +1,9 @@ +use crate::client::render::voxel::VoxelColor; +use bevy_ecs::{bundle::Bundle, component::Component}; +use ndarray::{Array3, ArrayBase, Dim, SliceArg}; use std::ops::Range; -use bevy_derive::{Deref, DerefMut}; -use bevy_ecs::{bundle::Bundle, component::Component}; -use nalgebra::{Rotation3, Vector3}; -use ndarray::{Array3, ArrayBase, Dim, SliceArg}; - -use crate::client::render::voxel::VoxelColor; - -#[derive(Debug, Clone, Copy, Component, Default)] -pub struct Synced; - -#[derive(Debug, Clone, Copy, Component, Default, Deref, DerefMut)] -pub struct Pos(pub Vector3); -#[derive(Debug, Clone, Copy, Component, Default, Deref, DerefMut)] -pub struct Orientation(pub Rotation3); +use super::{Orientation, Pos}; pub type VoxelGrid = TrackedGrid; pub type GridRegion = (Range, Range, Range); @@ -41,27 +31,10 @@ impl TrackedGrid { } } -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) +impl std::ops::Deref for TrackedGrid { + type Target = Array3; + fn deref(&self) -> &Self::Target { + &self.data } } @@ -71,10 +44,3 @@ pub struct VoxelGridBundle { pub orientation: Orientation, pub grid: VoxelGrid, } - -impl std::ops::Deref for TrackedGrid { - type Target = Array3; - fn deref(&self) -> &Self::Target { - &self.data - } -} diff --git a/src/common/component/mod.rs b/src/common/component/mod.rs new file mode 100644 index 0000000..8c162ad --- /dev/null +++ b/src/common/component/mod.rs @@ -0,0 +1,62 @@ +pub mod chunk; +mod grid; + +use chunk::LoadedChunks; +pub use chunk::{ChunkBundle, ChunkData, ChunkMap, ChunkMesh, ChunkPos}; +pub use grid::*; + +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{bundle::Bundle, component::Component}; +use nalgebra::{Rotation3, Vector3}; + +#[derive(Debug, Clone, Copy, Component, Default, Deref, DerefMut)] +pub struct Pos(pub Vector3); + +impl Pos { + pub fn new(x: f32, y: f32, z: f32) -> Self { + Self(Vector3::new(x, y, z)) + } +} +impl From> for Pos { + fn from(val: Vector3) -> Self { + Pos(val) + } +} + +#[derive(Debug, Clone, Copy, Component, Default, Deref, DerefMut)] +pub struct Orientation(pub Rotation3); +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 Orientation { + fn from(val: Rotation3) -> Self { + Orientation(val) + } +} + +#[derive(Debug, Clone, Copy, Component)] +pub struct Player; + +#[derive(Debug, Clone, Bundle)] +pub struct PlayerBundle { + pub player: Player, + pub loaded_chunks: LoadedChunks, + pub pos: Pos, + pub orientation: Orientation, +} + +impl PlayerBundle { + pub fn new() -> Self { + Self { + player: Player, + loaded_chunks: LoadedChunks::new(), + pos: Pos::default(), + orientation: Orientation::default(), + } + } +} diff --git a/src/common/message.rs b/src/common/message.rs new file mode 100644 index 0000000..ce6e30a --- /dev/null +++ b/src/common/message.rs @@ -0,0 +1,28 @@ +use crate::{ + common::component::{ChunkBundle, Pos, VoxelGridBundle}, + util::thread::{ExitType, ThreadChannel, ThreadHandle}, +}; +use bevy_ecs::entity::Entity; + +#[derive(Clone)] +pub enum ServerMessage { + Stop, + Join, + SpawnVoxelGrid(VoxelGridBundle), +} + +impl ExitType for ServerMessage { + fn exit() -> Self { + ServerMessage::Stop + } +} + +#[derive(Clone)] +pub enum ClientMessage { + SpawnVoxelGrid(Entity, VoxelGridBundle), + LoadChunk(Entity, ChunkBundle), + PosUpdate(Entity, Pos), +} + +pub type ClientChannel = ThreadChannel; +pub type ServerHandle = ThreadHandle; diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 0000000..1437130 --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1,4 @@ +pub mod component; +mod message; + +pub use message::*; diff --git a/src/main.rs b/src/main.rs index 02e6d0f..8174069 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,9 @@ use client::ClientApp; use winit::event_loop::EventLoop; mod client; -mod world; +mod common; mod server; -mod sync; +mod util; fn main() { let event_loop = EventLoop::new().expect("Failed to create event loop"); diff --git a/src/server/chunk/load.rs b/src/server/chunk/load.rs new file mode 100644 index 0000000..0666681 --- /dev/null +++ b/src/server/chunk/load.rs @@ -0,0 +1,182 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + +use bevy_ecs::{entity::Entity, system::Commands}; +use nalgebra::Vector3; +use ndarray::{s, Array3, Axis}; +use simdnoise::NoiseBuilder; + +use crate::{ + client::render::voxel::VoxelColor, + common::component::{chunk, ChunkBundle, ChunkData, ChunkMesh, ChunkPos}, + util::{ + oct_tree::OctTree, + thread::{ExitType, ThreadChannel, ThreadHandle}, + }, +}; + +pub struct ChunkManager { + handles: Vec>, + i: usize, + n: usize, + map: HashMap, + generating: HashSet, +} + +impl ChunkManager { + pub fn new() -> Self { + let n = 4; + Self { + handles: std::iter::repeat_with(|| ThreadHandle::spawn(chunk_loader_main)) + .take(n) + .collect(), + i: 0, + n, + map: HashMap::new(), + generating: HashSet::new(), + } + } + pub fn entity_at(&self, pos: &ChunkPos) -> Option<&Entity> { + self.map.get(pos) + } + pub fn is_generating(&self, pos: &ChunkPos) -> bool { + self.generating.contains(pos) + } + pub fn queue(&mut self, pos: ChunkPos) { + if !self.is_generating(&pos) { + self.handles[self.i].send(ChunkLoaderMsg::Generate(pos)); + self.i = (self.i + 1) % self.n; + self.generating.insert(pos); + } + } + pub fn update(&mut self, commands: &mut Commands) { + for msg in self.handles.iter_mut().flat_map(|h| h.recv()) { + match msg { + ServerChunkMsg::ChunkGenerated(chunk) => { + let id = commands + .spawn(ChunkBundle { + pos: chunk.pos, + data: chunk.data, + mesh: chunk.mesh, + }) + .id(); + self.map.insert(chunk.pos, id); + self.generating.remove(&chunk.pos); + } + } + } + } +} + +pub struct GeneratedChunk { + pub pos: ChunkPos, + pub data: ChunkData, + pub mesh: ChunkMesh, +} + +impl Drop for ChunkManager { + fn drop(&mut self) { + for h in &mut self.handles { + h.send(ChunkLoaderMsg::Exit); + h.join(); + } + } +} + +enum ServerChunkMsg { + ChunkGenerated(GeneratedChunk), +} + +enum ChunkLoaderMsg { + Generate(ChunkPos), + Exit, +} + +impl ExitType for ChunkLoaderMsg { + fn exit() -> Self { + Self::Exit + } +} + +fn chunk_loader_main(channel: ThreadChannel) { + let mut to_generate = VecDeque::new(); + 'outer: loop { + let msg = channel.recv_wait(); + match msg { + ChunkLoaderMsg::Generate(pos) => { + to_generate.push_back(pos); + } + ChunkLoaderMsg::Exit => { + break 'outer; + } + } + if let Some(pos) = to_generate.pop_front() { + let data = generate(pos); + let mesh = ChunkMesh::from_data(&data); + let data = if pos.y > 0 || pos.y < -1 { + ChunkData::empty() + } else { + ChunkData::from_tree(OctTree::from_arr(data.slice(s![ + 1..data.len_of(Axis(0)) - 1, + 1..data.len_of(Axis(1)) - 1, + 1..data.len_of(Axis(2)) - 1 + ]))) + }; + channel.send(ServerChunkMsg::ChunkGenerated(GeneratedChunk { + pos, + data, + mesh, + })); + } + } +} + +fn generate(pos: ChunkPos) -> Array3 { + let shape = [chunk::SIDE_LENGTH + 2; 3]; + if pos.y > 0 { + return Array3::from_elem(shape, VoxelColor::none()); + } + if pos.y < -1 { + return Array3::from_elem(shape, VoxelColor::none()); + } + let posf: Vector3 = (pos.cast() * chunk::SIDE_LENGTH as f32) - Vector3::from_element(1.0); + let (a, b, c, d) = (0.0, 50.0, 100.0, 127.0); + let (noise, ..) = NoiseBuilder::gradient_2d_offset( + posf.x, + chunk::SIDE_LENGTH + 2, + posf.z, + chunk::SIDE_LENGTH + 2, + ) + .with_seed(0) + .with_freq(0.005) + .generate(); + Array3::from_shape_fn(shape, |(x, y, z)| { + let y = y as f32 + posf.y; + let n = (noise[x + z * (chunk::SIDE_LENGTH + 2)] + 0.022) * (1.0 / 0.044) * d; + if y < n.max(b) { + if y < b { + VoxelColor { + r: 100, + g: 100, + b: 255, + a: 255, + } + } else if y < c { + VoxelColor { + r: 100, + g: 255, + b: 100, + a: 255, + } + } else { + VoxelColor { + r: 150, + g: 150, + b: 150, + a: 255, + } + } + } else { + VoxelColor::none() + } + }) +} diff --git a/src/server/chunk/mod.rs b/src/server/chunk/mod.rs new file mode 100644 index 0000000..c846229 --- /dev/null +++ b/src/server/chunk/mod.rs @@ -0,0 +1,2 @@ +mod load; +pub use load::*; diff --git a/src/server/client.rs b/src/server/client.rs new file mode 100644 index 0000000..76b1046 --- /dev/null +++ b/src/server/client.rs @@ -0,0 +1,80 @@ +use std::collections::{hash_map, HashMap}; + +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{component::Component, entity::Entity, system::Resource}; + +use crate::common::{ClientChannel, ClientMessage, ServerMessage}; + +pub enum ServerClient { + Local(ClientChannel), +} + +impl ServerClient { + pub fn recv(&mut self) -> Vec { + match self { + Self::Local(ch) => ch.recv().collect(), + } + } + pub fn send(&self, msg: ClientMessage) { + match self { + Self::Local(ch) => ch.send(msg), + } + } +} + +#[derive(Deref, DerefMut)] +pub struct ServerClients { + map: HashMap, +} + +impl ServerClients { + pub fn new() -> Self { + Self { + map: HashMap::new(), + } + } + pub fn add(&mut self, id: Entity, client: ServerClient) { + self.map.insert(id, client); + } +} + +impl<'a> IntoIterator for &'a mut ServerClients { + type Item = (&'a Entity, &'a mut ServerClient); + type IntoIter = hash_map::IterMut<'a, Entity, ServerClient>; + fn into_iter(self) -> Self::IntoIter { + self.map.iter_mut() + } +} + +// I don't think it's worth putting a reciever in here rn +// and moving that stuff into the ecs but we'll see +#[derive(Component)] +pub struct ClientComponent { + send: Vec, +} + +impl ClientComponent { + pub fn new() -> Self { + Self { send: Vec::new() } + } + pub fn send(&mut self, msg: ClientMessage) { + self.send.push(msg); + } + pub fn take(&mut self) -> Vec { + std::mem::take(&mut self.send) + } +} + +#[derive(Resource)] +pub struct ClientBroadcast(Vec); +impl ClientBroadcast { + pub fn new() -> Self { + Self(Vec::new()) + } + pub fn send(&mut self, msg: ClientMessage) { + self.0.push(msg); + } + pub fn take(&mut self) -> Vec { + std::mem::take(&mut self.0) + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index c9c496a..be71472 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,21 +1,29 @@ +mod chunk; +mod client; mod rsc; mod system; +mod test; -use crate::{ - sync::{ClientChannel, ClientMessage, ClientSender, ServerMessage}, - world::{ - component::{Orientation, Pos, Synced, VoxelGrid, VoxelGridBundle}, - generation::generate, +pub use client::*; + +use crate::common::{ + component::{ + ChunkBundle, ChunkData, ChunkMap, ChunkMesh, ChunkPos, Orientation, PlayerBundle, Pos, + VoxelGrid, VoxelGridBundle, }, + ClientChannel, ClientMessage, ServerMessage, }; -use bevy_ecs::{entity::Entity, query::With, system::SystemId, world::World}; +use bevy_ecs::{entity::Entity, system::SystemId, world::World}; +use chunk::ChunkManager; +use client::{ClientBroadcast, ServerClient, ServerClients}; use rsc::UPDATE_TIME; use std::time::{Duration, Instant}; +use test::spawn_test_stuff; pub struct Server { update_time: Duration, target: Instant, - client: ClientChannel, + clients: ServerClients, world: World, systems: ServerSystems, mov: Vec, @@ -24,23 +32,27 @@ pub struct Server { pub struct ServerSystems { sync_pos: SystemId, + sync_chunks: SystemId, } impl ServerSystems { pub fn new(world: &mut World) -> Self { Self { sync_pos: world.register_system(system::sync::pos), + sync_chunks: world.register_system(system::sync::chunks), } } } impl Server { - pub fn new(client: ClientChannel) -> Self { + pub fn new() -> Self { let mut world = World::new(); - world.insert_resource(ClientSender(client.sender())); + world.insert_resource(ClientBroadcast::new()); + world.insert_resource(ChunkMap::new()); + world.insert_non_send_resource(ChunkManager::new()); let systems = ServerSystems::new(&mut world); Self { - client, + clients: ServerClients::new(), world, systems, target: Instant::now(), @@ -50,58 +62,110 @@ impl Server { } } + pub fn from_client(client: ClientChannel) -> Self { + let mut s = Self::new(); + s.add_client(ServerClient::Local(client)); + s + } + + pub fn add_client(&mut self, client: ServerClient) { + let id = self.world.spawn(ClientComponent::new()).id(); + self.clients.add(id, client); + } + pub fn start(ch: ClientChannel) { - Self::new(ch).run(); + Self::from_client(ch).run(); } pub fn run(&mut self) { - generate(&mut self.world); + spawn_test_stuff(&mut self.world); loop { self.recv(); let now = Instant::now(); if now >= self.target { self.target += self.update_time; - let mut q = self.world.query::<(Entity, &mut Pos)>(); - for (e, mut p) in q.iter_mut(&mut self.world) { - if self.mov.contains(&e) { - p.x += 0.1; - } - } - self.world.run_system(self.systems.sync_pos).unwrap(); - self.world.clear_trackers(); + self.tick(); } if self.stop { break; } + self.send(); } } + pub fn tick(&mut self) { + let mut q = self.world.query::<(Entity, &mut Pos)>(); + for (e, mut p) in q.iter_mut(&mut self.world) { + if self.mov.contains(&e) { + p.x += 0.1; + } + } + self.world.run_system(self.systems.sync_pos).unwrap(); + self.world.run_system(self.systems.sync_chunks).unwrap(); + self.world.clear_trackers(); + } + pub fn recv(&mut self) { - for msg in self.client.recv() { - match msg { - ServerMessage::LoadWorld => { - let mut q = self - .world - .query_filtered::<(Entity, &Pos, &Orientation, &VoxelGrid), With>(); - // ePOG - for (e, p, o, g) in q.iter(&self.world) { - self.client.send(ClientMessage::SpawnVoxelGrid( - e, - VoxelGridBundle { - pos: *p, - orientation: *o, - grid: g.clone(), - }, - )) + for (id, client) in &mut self.clients { + for msg in client.recv() { + match msg { + ServerMessage::Join => { + let mut q = self + .world + .query::<(Entity, &Pos, &Orientation, &VoxelGrid)>(); + // ePOG + for (e, p, o, g) in q.iter(&self.world) { + client.send(ClientMessage::SpawnVoxelGrid( + e, + VoxelGridBundle { + pos: *p, + orientation: *o, + grid: g.clone(), + }, + )) + } + let mut q = self + .world + .query::<(Entity, &ChunkPos, &ChunkData, &ChunkMesh)>(); + for (e, p, c, m) in q.iter(&self.world) { + client.send(ClientMessage::LoadChunk( + e, + ChunkBundle { + pos: *p, + data: c.clone(), + mesh: m.clone(), + }, + )) + } + self.world.entity_mut(*id).insert(PlayerBundle::new()); + } + ServerMessage::SpawnVoxelGrid(grid) => { + let e = self.world.spawn(grid.clone()).id(); + self.mov.push(e); + self.world + .resource_mut::() + .send(ClientMessage::SpawnVoxelGrid(e, grid)); + } + ServerMessage::Stop => { + self.stop = true; } } - ServerMessage::SpawnVoxelGrid(grid) => { - let e = self.world.spawn((grid.clone(), Synced)).id(); - self.mov.push(e); - self.client.send(ClientMessage::SpawnVoxelGrid(e, grid)); - } - ServerMessage::Stop => { - self.stop = true; + } + } + } + + pub fn send(&mut self) { + let msgs = self.world.resource_mut::().take(); + for msg in &msgs { + for (_, client) in &mut self.clients { + client.send(msg.clone()); + } + } + let mut q = self.world.query::<(Entity, &mut ClientComponent)>(); + for (e, mut c) in q.iter_mut(&mut self.world) { + if let Some(sc) = self.clients.get(&e) { + for msg in c.take() { + sc.send(msg); } } } diff --git a/src/server/system/sync.rs b/src/server/system/sync.rs index 0533f84..cddf9cb 100644 --- a/src/server/system/sync.rs +++ b/src/server/system/sync.rs @@ -1,9 +1,72 @@ -use bevy_ecs::{entity::Entity, query::Changed, system::{Query, Res}}; +use bevy_ecs::{ + entity::Entity, + query::{Changed, With}, + system::{Commands, NonSendMut, Query, ResMut}, +}; +use nalgebra::Vector3; -use crate::{sync::{ClientMessage, ClientSender}, world::component::{Pos, Synced}}; +use crate::{ + common::{ + component::{ + chunk::{self, ChunkBundle, LoadedChunks}, + ChunkData, ChunkMesh, ChunkPos, Player, Pos, + }, + ClientMessage, + }, + server::{chunk::ChunkManager, client::ClientBroadcast, ClientComponent}, +}; -pub fn pos(query: Query<(Entity, &Synced, &Pos), Changed>, client: Res) { - for (e, _, pos) in query.iter() { - client.send(ClientMessage::PosUpdate(e, *pos)); +pub fn pos(query: Query<(Entity, &Pos), Changed>, mut clients: ResMut) { + for (e, pos) in query.iter() { + clients.send(ClientMessage::PosUpdate(e, *pos)); } } + +pub fn chunks( + mut players: Query<(&Pos, &mut LoadedChunks, &mut ClientComponent), With>, + chunks: Query<(&ChunkPos, &ChunkData, &ChunkMesh)>, + mut loader: NonSendMut, + mut commands: Commands, +) { + for (pos, mut loaded, mut client) in &mut players { + let fp = **pos / chunk::SIDE_LENGTH as f32; + let player_chunk = Vector3::new( + fp.x.floor() as i32, + fp.y.floor() as i32, + fp.z.floor() as i32, + ); + let radius: i32 = 5; + let width = radius * 2 - 1; + let mut desired = Vec::new(); + for i in 0..width.pow(3) { + let pos = Vector3::new(i % width, (i / width) % width, i / (width.pow(2))) + - Vector3::from_element(radius - 1); + let dist = pos.cast::().norm(); + if dist < radius as f32 { + desired.push((dist, pos)); + } + } + desired.sort_by(|(da, ..), (db, ..)| da.total_cmp(db)); + for (_, pos) in desired { + let coords = pos - player_chunk; + let pos = ChunkPos(coords); + if !loaded.contains(&pos) { + if let Some(id) = loader.entity_at(&pos) { + let (pos, data, mesh) = chunks.get(*id).unwrap(); + client.send(ClientMessage::LoadChunk( + *id, + ChunkBundle { + pos: *pos, + data: data.clone(), + mesh: mesh.clone(), + }, + )); + loaded.insert(*pos); + } else { + loader.queue(pos); + } + } + } + } + loader.update(&mut commands); +} diff --git a/src/world/generation/mod.rs b/src/server/test.rs similarity index 60% rename from src/world/generation/mod.rs rename to src/server/test.rs index 5ea038c..13f9729 100644 --- a/src/world/generation/mod.rs +++ b/src/server/test.rs @@ -1,15 +1,12 @@ -use crate::world::component::VoxelGrid; +use crate::client::render::voxel::VoxelColor; +use crate::common::component::{VoxelGrid, VoxelGridBundle}; use bevy_ecs::world::World; use nalgebra::{Rotation3, UnitVector3, Vector3}; use ndarray::Array3; -use crate::client::render::voxel::VoxelColor; - -use super::component::{Synced, VoxelGridBundle}; - -pub fn generate(world: &mut World) { +pub fn spawn_test_stuff(world: &mut World) { let dim = (15, 10, 10); - world.spawn((VoxelGridBundle { + world.spawn(VoxelGridBundle { pos: Vector3::new(0.0, 0.0, 20.0).into(), orientation: Rotation3::from_axis_angle(&Vector3::y_axis(), 0.5).into(), grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(x, y, z)| { @@ -33,30 +30,34 @@ pub fn generate(world: &mut World) { VoxelColor::none() } })), - }, Synced)); + }); - let dim = (1000, 2, 1000); - world.spawn((VoxelGridBundle { - pos: Vector3::new(0.0, -2.1, 0.0).into(), - orientation: Rotation3::identity().into(), - grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(x, y, z)| { - if y == 0 { - VoxelColor::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() - } - })), - }, Synced)); + // let dim = (1000, 2, 1000); + // world.spawn(( + // VoxelGridBundle { + // pos: Vector3::new(0.0, -2.1, 0.0).into(), + // orientation: Rotation3::identity().into(), + // grid: VoxelGrid::new(Array3::from_shape_fn(dim, |(x, y, z)| { + // if y == 0 { + // VoxelColor::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() + // } + // })), + // }, + // Synced, + // )); let dim = (3, 3, 3); - world.spawn((VoxelGridBundle { + world.spawn(VoxelGridBundle { pos: Vector3::new(0.0, 0.0, 16.5).into(), orientation: (Rotation3::from_axis_angle(&Vector3::y_axis(), std::f32::consts::PI / 4.0) * Rotation3::from_axis_angle( @@ -70,5 +71,5 @@ pub fn generate(world: &mut World) { b: 255, a: 255, })), - }, Synced)); + }); } diff --git a/src/sync/mod.rs b/src/sync/mod.rs deleted file mode 100644 index abfdfe5..0000000 --- a/src/sync/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -pub mod thread; - -use crate::world::component::{Pos, VoxelGridBundle}; -use bevy_ecs::{entity::Entity, system::Resource}; -use std::sync::mpsc::Sender; -use thread::{ThreadChannel, ThreadHandle}; - -pub enum ServerMessage { - Stop, - LoadWorld, - SpawnVoxelGrid(VoxelGridBundle), -} - -pub enum ClientMessage { - SpawnVoxelGrid(Entity, VoxelGridBundle), - PosUpdate(Entity, Pos), -} - -pub type ClientChannel = ThreadChannel; -pub type ServerHandle = ThreadHandle; - -#[derive(Resource, Clone)] -pub struct ClientSender(pub Sender); -impl ClientSender { - pub fn send(&self, msg: ClientMessage) { - self.0.send(msg).expect("YOU HAVE FAILED THE MISSION"); - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..ff0e7bc --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,2 @@ +pub mod thread; +pub mod oct_tree; diff --git a/src/util/oct_tree.rs b/src/util/oct_tree.rs new file mode 100644 index 0000000..a99399b --- /dev/null +++ b/src/util/oct_tree.rs @@ -0,0 +1,42 @@ +use std::fmt::Debug; + +use nalgebra::Vector3; +use ndarray::{Array3, ArrayView3, Axis}; + +#[derive(Debug, Clone)] +pub enum OctTree { + Leaf(T), + Node(Box<[OctTree; 8]>), +} + +impl OctTree { + pub fn from_arr(arr: ArrayView3) -> OctTree { + let mut node_arr = arr.map(|x| OctTree::Leaf(x.clone())); + while node_arr.len() > 1 { + let new_data = node_arr.exact_chunks([2; 3]).into_iter().map(|chunk| { + let vec: Vec> = chunk.iter().cloned().collect(); + let vec: [OctTree; 8] = vec.try_into().unwrap(); + if let OctTree::Leaf(first) = &chunk[[0; 3]] { + if vec.iter().all(|n| { + if let OctTree::Leaf(d) = n { + *d == *first + } else { + false + } + }) { + return OctTree::Leaf(first.clone()) + } + } + OctTree::Node(Box::new(vec)) + }).collect(); + node_arr = Array3::from_shape_vec([node_arr.len_of(Axis(0)) / 2; 3], new_data).unwrap(); + } + node_arr[[0; 3]].clone() + } +} + +impl OctTree { + fn get(i: Vector3) { + + } +} diff --git a/src/sync/thread.rs b/src/util/thread.rs similarity index 66% rename from src/sync/thread.rs rename to src/util/thread.rs index be89537..27db929 100644 --- a/src/sync/thread.rs +++ b/src/util/thread.rs @@ -3,12 +3,16 @@ use std::{ thread::JoinHandle, }; -pub struct ThreadHandle { - pub channel: ThreadChannel, - pub handle: Option>, +pub trait ExitType { + fn exit() -> Self; } -impl ThreadHandle { +pub struct ThreadHandle { + channel: ThreadChannel, + handle: Option>, +} + +impl ThreadHandle { pub fn send(&self, msg: SendMsg) { self.channel.send(msg); } @@ -34,6 +38,13 @@ impl ThreadHandle Drop for ThreadHandle { + fn drop(&mut self) { + let _ = self.channel.send.send(SendMsg::exit()); + self.join(); + } +} + pub struct ThreadChannel { send: Sender, recv: Receiver, @@ -50,4 +61,7 @@ impl ThreadChannel { pub fn recv(&self) -> TryIter { self.recv.try_iter() } + pub fn recv_wait(&self) -> RecvMsg { + self.recv.recv().expect("OOOAAAAAAA") + } } diff --git a/src/world/chunk.rs b/src/world/chunk.rs deleted file mode 100644 index bf727f9..0000000 --- a/src/world/chunk.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::client::render::voxel::VoxelColor; - -use super::component::TrackedGrid; - -pub struct Chunk { - grid: TrackedGrid -} diff --git a/src/world/mod.rs b/src/world/mod.rs deleted file mode 100644 index 6289ee5..0000000 --- a/src/world/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod component; -pub mod generation; -pub mod chunk;