CHUNK GENERATION IS REAL

This commit is contained in:
2024-06-21 00:36:37 -04:00
parent aa466a248c
commit b8ed5297fa
43 changed files with 1341 additions and 517 deletions

124
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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<VoxelColor>,
}
#[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(
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(
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 {

View File

@@ -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> {

View File

@@ -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<View>,
bind_group_layout: wgpu::BindGroupLayout,
bind_group: wgpu::BindGroup,
voxel_groups: Storage<VoxelGroup>,
voxels: Storage<VoxelColor>,
global_lights: Storage<GlobalLight>,
id_map: HashMap<Entity, (usize, VoxelGroup)>,
}
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<u32>,
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::*;

View File

@@ -1,3 +1,4 @@
use block_mesh::Voxel;
use rand::distributions::{Distribution, Standard};
#[repr(C)]
@@ -46,3 +47,13 @@ impl Distribution<VoxelColor> 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,
}
}
}

View File

@@ -1,6 +1,6 @@
use bytemuck::Zeroable;
use crate::client::render::voxel::VoxelColor;
use super::VoxelColor;
#[repr(C)]
#[derive(Copy, Clone, Debug, Zeroable)]

View File

@@ -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<wgpu::BindGroup>,
vertices: Vec<Instances<VoxelFace>>,
global_lights: Storage<GlobalLight>,
}
const INSTANCE_ATTRS: [wgpu::VertexAttribute; 2] = [
@@ -52,12 +58,21 @@ impl VoxelPipeline {
let example_faces =
Instances::<VoxelFace>::init(device, "voxel groups", 0, &INSTANCE_ATTRS);
let example_group = Uniform::<FaceGroup>::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<u32> = dimensions.cast();
let dim = Vector3::new(dim.z, dim.y, dim.x);
let shape = RuntimeShape::<u32, 3>::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<VoxelFace> = 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);
}
}

View File

@@ -8,6 +8,7 @@ struct InstanceInput {
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) normal: vec3<f32>,
};
struct VoxelFace {
@@ -28,16 +29,16 @@ struct VoxelGroup {
face: u32,
};
struct GlobalLight {
dir: vec3<f32>,
};
@group(0) @binding(0)
var<uniform> view: View;
@group(0) @binding(1)
var<uniform> group: VoxelGroup;
const DIRECTIONS = array(
vec3<f32>(1.0, 1.0, 0.0),
vec3<f32>(0.0, 1.0, 1.0),
vec3<f32>(1.0, 0.0, 1.0),
);
@group(0) @binding(3)
var<storage, read> global_lights: array<GlobalLight>;
@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>(
f32(vi % 2u),
invert + invert_mult * f32(vi / 2u),
);
var cube_pos = vec3<f32>(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>(
f32(face_axis == 0),
f32(face_axis % 2),
f32(face_axis / 2),
);
out.normal = (group.transform * vec4<f32>(cube_normal, 0.0)).xyz;
var pos = vec4<f32>(
cube_pos,
1.0,
@@ -78,5 +88,9 @@ fn vs_main(
fn fs_main(
in: VertexOutput,
) -> @location(0) vec4<f32> {
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<f32>(1.0));
return vec4<f32>(new_rgb, in.color.a);
}

View File

@@ -0,0 +1,9 @@
use nalgebra::Vector3;
#[repr(C, align(16))]
#[derive(Clone, Copy, PartialEq, bytemuck::Zeroable)]
pub struct GlobalLight {
pub direction: Vector3<f32>,
}
unsafe impl bytemuck::Pod for GlobalLight {}

View File

@@ -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<View>,
bind_group_layout: wgpu::BindGroupLayout,
bind_group: wgpu::BindGroup,
voxel_groups: Storage<VoxelGroup>,
voxels: Storage<VoxelColor>,
global_lights: Storage<GlobalLight>,
id_map: HashMap<Entity, (usize, VoxelGroup)>,
}
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<u32>,
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);
}
}

View File

@@ -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,
};

View File

@@ -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<RenderCommands>,
) {
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<ChunkPos>, Added<ChunkMesh>)>>,
mut renderer: ResMut<RenderCommands>,
) {
for (id, pos, mesh) in query.iter() {
renderer.push(RenderCommand::AddChunk(AddChunk {
id,
pos: *pos,
mesh: mesh.clone()
}));
}
}

View File

@@ -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<usize> = 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<VoxelColor>,
}
impl ChunkData {
pub fn empty() -> Self {
Self {
data: OctTree::Leaf(VoxelColor::none()),
}
}
pub fn from_tree(t: OctTree<VoxelColor>) -> Self {
Self { data: t }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Component, Default, Deref, DerefMut)]
pub struct ChunkPos(pub Vector3<i32>);
impl ChunkPos {
pub fn new(x: i32, y: i32, z: i32) -> Self {
Self(Vector3::new(x, y, z))
}
}
impl From<Vector3<i32>> for ChunkPos {
fn from(val: Vector3<i32>) -> Self {
ChunkPos(val)
}
}
#[derive(Debug, Clone, Component)]
pub struct ChunkMesh {
pub faces: [Vec<VoxelFace>; 6],
}
impl ChunkMesh {
pub fn from_data(data: &Array3<VoxelColor>) -> 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::<u32, 3>::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<ChunkPos>,
}
impl LoadedChunks {
pub fn new() -> Self {
Self {
loaded: HashSet::new(),
}
}
}
#[derive(Resource, Deref, DerefMut)]
pub struct ChunkMap {
#[deref]
map: HashMap<ChunkPos, Entity>,
pub generating: HashSet<ChunkPos>,
}
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,
}

View File

@@ -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<f32>);
#[derive(Debug, Clone, Copy, Component, Default, Deref, DerefMut)]
pub struct Orientation(pub Rotation3<f32>);
use super::{Orientation, Pos};
pub type VoxelGrid = TrackedGrid<VoxelColor>;
pub type GridRegion = (Range<usize>, Range<usize>, Range<usize>);
@@ -41,27 +31,10 @@ impl<T> TrackedGrid<T> {
}
}
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<SB: nalgebra::Storage<f32, nalgebra::Const<3>>>(
axis: &nalgebra::Unit<nalgebra::Matrix<f32, nalgebra::Const<3>, nalgebra::Const<1>, SB>>,
angle: f32,
) -> Self {
Self(Rotation3::from_axis_angle(axis, angle))
}
}
impl From<Vector3<f32>> for Pos {
fn from(val: Vector3<f32>) -> Self {
Pos(val)
}
}
impl From<Rotation3<f32>> for Orientation {
fn from(val: Rotation3<f32>) -> Self {
Orientation(val)
impl<T> std::ops::Deref for TrackedGrid<T> {
type Target = Array3<T>;
fn deref(&self) -> &Self::Target {
&self.data
}
}
@@ -71,10 +44,3 @@ pub struct VoxelGridBundle {
pub orientation: Orientation,
pub grid: VoxelGrid,
}
impl<T> std::ops::Deref for TrackedGrid<T> {
type Target = Array3<T>;
fn deref(&self) -> &Self::Target {
&self.data
}
}

View File

@@ -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<f32>);
impl Pos {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self(Vector3::new(x, y, z))
}
}
impl From<Vector3<f32>> for Pos {
fn from(val: Vector3<f32>) -> Self {
Pos(val)
}
}
#[derive(Debug, Clone, Copy, Component, Default, Deref, DerefMut)]
pub struct Orientation(pub Rotation3<f32>);
impl Orientation {
pub fn from_axis_angle<SB: nalgebra::Storage<f32, nalgebra::Const<3>>>(
axis: &nalgebra::Unit<nalgebra::Matrix<f32, nalgebra::Const<3>, nalgebra::Const<1>, SB>>,
angle: f32,
) -> Self {
Self(Rotation3::from_axis_angle(axis, angle))
}
}
impl From<Rotation3<f32>> for Orientation {
fn from(val: Rotation3<f32>) -> 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(),
}
}
}

28
src/common/message.rs Normal file
View File

@@ -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<ClientMessage, ServerMessage>;
pub type ServerHandle = ThreadHandle<ServerMessage, ClientMessage>;

4
src/common/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod component;
mod message;
pub use message::*;

View File

@@ -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");

182
src/server/chunk/load.rs Normal file
View File

@@ -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<ThreadHandle<ChunkLoaderMsg, ServerChunkMsg>>,
i: usize,
n: usize,
map: HashMap<ChunkPos, Entity>,
generating: HashSet<ChunkPos>,
}
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<ServerChunkMsg, ChunkLoaderMsg>) {
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<VoxelColor> {
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<f32> = (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()
}
})
}

2
src/server/chunk/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
mod load;
pub use load::*;

80
src/server/client.rs Normal file
View File

@@ -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<ServerMessage> {
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<Entity, ServerClient>,
}
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<ClientMessage>,
}
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<ClientMessage> {
std::mem::take(&mut self.send)
}
}
#[derive(Resource)]
pub struct ClientBroadcast(Vec<ClientMessage>);
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<ClientMessage> {
std::mem::take(&mut self.0)
}
}

View File

@@ -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<Entity>,
@@ -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,17 +62,38 @@ 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;
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) {
@@ -68,24 +101,21 @@ impl Server {
}
}
self.world.run_system(self.systems.sync_pos).unwrap();
self.world.run_system(self.systems.sync_chunks).unwrap();
self.world.clear_trackers();
}
if self.stop {
break;
}
}
}
pub fn recv(&mut self) {
for msg in self.client.recv() {
for (id, client) in &mut self.clients {
for msg in client.recv() {
match msg {
ServerMessage::LoadWorld => {
ServerMessage::Join => {
let mut q = self
.world
.query_filtered::<(Entity, &Pos, &Orientation, &VoxelGrid), With<Synced>>();
.query::<(Entity, &Pos, &Orientation, &VoxelGrid)>();
// ePOG
for (e, p, o, g) in q.iter(&self.world) {
self.client.send(ClientMessage::SpawnVoxelGrid(
client.send(ClientMessage::SpawnVoxelGrid(
e,
VoxelGridBundle {
pos: *p,
@@ -94,11 +124,27 @@ impl Server {
},
))
}
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(), Synced)).id();
let e = self.world.spawn(grid.clone()).id();
self.mov.push(e);
self.client.send(ClientMessage::SpawnVoxelGrid(e, grid));
self.world
.resource_mut::<ClientBroadcast>()
.send(ClientMessage::SpawnVoxelGrid(e, grid));
}
ServerMessage::Stop => {
self.stop = true;
@@ -107,3 +153,21 @@ impl Server {
}
}
}
pub fn send(&mut self) {
let msgs = self.world.resource_mut::<ClientBroadcast>().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);
}
}
}
}
}

View File

@@ -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<Pos>>, client: Res<ClientSender>) {
for (e, _, pos) in query.iter() {
client.send(ClientMessage::PosUpdate(e, *pos));
pub fn pos(query: Query<(Entity, &Pos), Changed<Pos>>, mut clients: ResMut<ClientBroadcast>) {
for (e, pos) in query.iter() {
clients.send(ClientMessage::PosUpdate(e, *pos));
}
}
pub fn chunks(
mut players: Query<(&Pos, &mut LoadedChunks, &mut ClientComponent), With<Player>>,
chunks: Query<(&ChunkPos, &ChunkData, &ChunkMesh)>,
mut loader: NonSendMut<ChunkManager>,
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::<f32>().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);
}

View File

@@ -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));
});
}

View File

@@ -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<ClientMessage, ServerMessage>;
pub type ServerHandle = ThreadHandle<ServerMessage, ClientMessage>;
#[derive(Resource, Clone)]
pub struct ClientSender(pub Sender<ClientMessage>);
impl ClientSender {
pub fn send(&self, msg: ClientMessage) {
self.0.send(msg).expect("YOU HAVE FAILED THE MISSION");
}
}

2
src/util/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod thread;
pub mod oct_tree;

42
src/util/oct_tree.rs Normal file
View File

@@ -0,0 +1,42 @@
use std::fmt::Debug;
use nalgebra::Vector3;
use ndarray::{Array3, ArrayView3, Axis};
#[derive(Debug, Clone)]
pub enum OctTree<T> {
Leaf(T),
Node(Box<[OctTree<T>; 8]>),
}
impl<T: PartialEq + Clone + Debug> OctTree<T> {
pub fn from_arr(arr: ArrayView3<T>) -> OctTree<T> {
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<OctTree<T>> = chunk.iter().cloned().collect();
let vec: [OctTree<T>; 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<T> OctTree<T> {
fn get(i: Vector3<usize>) {
}
}

View File

@@ -3,12 +3,16 @@ use std::{
thread::JoinHandle,
};
pub struct ThreadHandle<SendMsg, RecvMsg> {
pub channel: ThreadChannel<SendMsg, RecvMsg>,
pub handle: Option<JoinHandle<()>>,
pub trait ExitType {
fn exit() -> Self;
}
impl<SendMsg: Send + 'static, RecvMsg: Send + 'static> ThreadHandle<SendMsg, RecvMsg> {
pub struct ThreadHandle<SendMsg: Send + 'static + ExitType, RecvMsg: Send + 'static> {
channel: ThreadChannel<SendMsg, RecvMsg>,
handle: Option<JoinHandle<()>>,
}
impl<SendMsg: Send + 'static + ExitType, RecvMsg: Send + 'static> ThreadHandle<SendMsg, RecvMsg> {
pub fn send(&self, msg: SendMsg) {
self.channel.send(msg);
}
@@ -34,6 +38,13 @@ impl<SendMsg: Send + 'static, RecvMsg: Send + 'static> ThreadHandle<SendMsg, Rec
}
}
impl<SendMsg: ExitType + Send, RecvMsg: Send + 'static> Drop for ThreadHandle<SendMsg, RecvMsg> {
fn drop(&mut self) {
let _ = self.channel.send.send(SendMsg::exit());
self.join();
}
}
pub struct ThreadChannel<SendMsg, RecvMsg> {
send: Sender<SendMsg>,
recv: Receiver<RecvMsg>,
@@ -50,4 +61,7 @@ impl<SendMsg, RecvMsg> ThreadChannel<SendMsg, RecvMsg> {
pub fn recv(&self) -> TryIter<RecvMsg> {
self.recv.try_iter()
}
pub fn recv_wait(&self) -> RecvMsg {
self.recv.recv().expect("OOOAAAAAAA")
}
}

View File

@@ -1,7 +0,0 @@
use crate::client::render::voxel::VoxelColor;
use super::component::TrackedGrid;
pub struct Chunk {
grid: TrackedGrid<VoxelColor>
}

View File

@@ -1,3 +0,0 @@
pub mod component;
pub mod generation;
pub mod chunk;