diff --git a/src/client/render/command.rs b/src/client/render/command.rs index cb3dcca..57e0a02 100644 --- a/src/client/render/command.rs +++ b/src/client/render/command.rs @@ -1,6 +1,6 @@ use crate::{ client::camera::Camera, - common::component::{ChunkMesh, ChunkPos}, + common::component::{ChunkMesh, ChunkPos}, util::oct_tree::OctTree, }; use super::{voxel::VoxelColor, Renderer}; @@ -32,6 +32,7 @@ pub struct AddChunk { pub id: Entity, pub pos: ChunkPos, pub mesh: ChunkMesh, + pub tree: OctTree, } #[derive(Debug, Clone)] diff --git a/src/client/render/voxel/mod.rs b/src/client/render/voxel/mod.rs index ed69b62..68f84db 100644 --- a/src/client/render/voxel/mod.rs +++ b/src/client/render/voxel/mod.rs @@ -1,3 +1,3 @@ -mod poly; -// mod ray; -pub use poly::*; +// mod poly; pub use poly::*; +// mod ray; pub use ray::*; +mod ray_oct; pub use ray_oct::*; diff --git a/src/client/render/voxel/poly/mod.rs b/src/client/render/voxel/poly/mod.rs index 8d59510..b425e35 100644 --- a/src/client/render/voxel/poly/mod.rs +++ b/src/client/render/voxel/poly/mod.rs @@ -249,7 +249,7 @@ impl VoxelPipeline { device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, belt: &mut wgpu::util::StagingBelt, - AddChunk { id, pos, mesh }: AddChunk, + AddChunk { id, pos, mesh, .. }: AddChunk, ) { if mesh.faces.iter().all(|f| f.is_empty()) { return; diff --git a/src/client/render/voxel/ray_oct/color.rs b/src/client/render/voxel/ray_oct/color.rs new file mode 100644 index 0000000..4021376 --- /dev/null +++ b/src/client/render/voxel/ray_oct/color.rs @@ -0,0 +1,53 @@ +use rand::distributions::{Distribution, Standard}; + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Zeroable)] +pub struct VoxelColor { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +unsafe impl bytemuck::Pod for VoxelColor {} + +impl VoxelColor { + pub fn none() -> Self { + Self { + r: 0, + g: 0, + b: 0, + a: 0, + } + } + pub fn black() -> Self { + Self { + r: 0, + g: 0, + b: 0, + a: 255, + } + } + pub fn white() -> Self { + Self { + r: 255, + g: 255, + b: 255, + a: 255, + } + } + pub fn random() -> Self { + rand::random() + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> VoxelColor { + VoxelColor { + r: rng.gen(), + g: rng.gen(), + b: rng.gen(), + a: rng.gen(), + } + } +} diff --git a/src/client/render/voxel/ray_oct/grid.rs b/src/client/render/voxel/ray_oct/grid.rs new file mode 100644 index 0000000..95cd870 --- /dev/null +++ b/src/client/render/voxel/ray_oct/grid.rs @@ -0,0 +1,24 @@ +use nalgebra::Matrix4x3; + +// this has cost me more than a couple of hours trying to figure out alignment :skull: +// putting transform at the beginning so I don't have to deal with its alignment +// I should probably look into encase (crate) +#[repr(C, align(16))] +#[derive(Clone, Copy, PartialEq, bytemuck::Zeroable)] +pub struct GridInfo { + pub transform: Matrix4x3, + pub width: u32, + pub height: u32, +} + +unsafe impl bytemuck::Pod for GridInfo {} + +impl Default for GridInfo { + fn default() -> Self { + Self { + transform: Matrix4x3::identity(), + width: 0, + height: 0, + } + } +} diff --git a/src/client/render/voxel/ray_oct/group.rs b/src/client/render/voxel/ray_oct/group.rs new file mode 100644 index 0000000..dce2357 --- /dev/null +++ b/src/client/render/voxel/ray_oct/group.rs @@ -0,0 +1,12 @@ +use nalgebra::{Projective3, Vector3}; + +#[repr(C, align(16))] +#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Zeroable)] +pub struct VoxelGroup { + pub transform: Projective3, + pub transform_inv: Projective3, + pub dimensions: Vector3, + pub offset: u32, +} + +unsafe impl bytemuck::Pod for VoxelGroup {} diff --git a/src/client/render/voxel/ray_oct/light.rs b/src/client/render/voxel/ray_oct/light.rs new file mode 100644 index 0000000..643d06c --- /dev/null +++ b/src/client/render/voxel/ray_oct/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_oct/mod.rs b/src/client/render/voxel/ray_oct/mod.rs new file mode 100644 index 0000000..e69909d --- /dev/null +++ b/src/client/render/voxel/ray_oct/mod.rs @@ -0,0 +1,297 @@ +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, Texture, Uniform}, + AddChunk, CreateVoxelGrid, + }, +}, common::component::chunk, util::oct_tree::OctNode}; +use bevy_ecs::entity::Entity; +use light::GlobalLight; +use nalgebra::{Projective3, Transform3, Translation3, Vector2, Vector3}; +use std::{collections::HashMap, ops::Deref}; + +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, config: &wgpu::SurfaceConfiguration) -> 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: config.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: Some(wgpu::DepthStencilState { + format: Texture::DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), + 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 add_chunk( + &mut self, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + belt: &mut wgpu::util::StagingBelt, + AddChunk { + id, + pos, + tree, + .. + }: AddChunk, + ) { + let offset = self.voxels.len(); + + let data = tree.raw(); + let updates = [ArrBufUpdate { + offset, + data, + }]; + let size = offset + data.len(); + self.voxels.update(device, encoder, belt, size, &updates); + + let proj = Projective3::identity() + * Translation3::from((pos.deref() * chunk::SIDE_LENGTH as i32).cast()) + * Translation3::from(-chunk::DIMENSIONS.cast() / 2.0); + let group = VoxelGroup { + transform: proj, + transform_inv: proj.inverse(), + dimensions: chunk::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/ray_oct/shader.wgsl b/src/client/render/voxel/ray_oct/shader.wgsl new file mode 100644 index 0000000..d3452d4 --- /dev/null +++ b/src/client/render/voxel/ray_oct/shader.wgsl @@ -0,0 +1,284 @@ +// Vertex shader + +struct GlobalLight { + dir: vec3, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, +}; + +struct View { + transform: mat4x4, + width: u32, + height: u32, + zoom: f32, +}; + +struct VoxelGroup { + transform: mat4x4, + transform_inv: mat4x4, + dimensions: vec3, + offset: u32, +}; + +@group(0) @binding(0) +var view: View; +@group(0) @binding(1) +var voxels: array; +@group(0) @binding(2) +var voxel_groups: array; +@group(0) @binding(3) +var global_lights: array; + +@vertex +fn vs_main( + @builtin(vertex_index) vi: u32, + @builtin(instance_index) ii: u32, +) -> VertexOutput { + var out: VertexOutput; + + var pos = vec2( + f32(vi % 2u) * 2.0 - 1.0, + f32(vi / 2u) * 2.0 - 1.0, + ) ; + out.clip_position = vec4(pos.x, pos.y, 0.0, 1.0); + return out; +} + +// Fragment shader + +@fragment +fn fs_main( + in: VertexOutput, +) -> @location(0) vec4 { + // get position of the pixel; eye at origin, pixel on plane z = 1 + let win_dim = vec2(f32(view.width), f32(view.height)); + let aspect = win_dim.y / win_dim.x; + let pixel_pos = vec3( + (in.clip_position.xy / win_dim - vec2(0.5)) * vec2(2.0, -2.0 * aspect), + 1.0 + ); + + // move to position in world + let pos = view.transform * vec4(pixel_pos, 1.0); + let dir = view.transform * vec4(normalize(pixel_pos), 0.0); + + var color = trace_full(pos, dir); + let light_mult = clamp((-dot(dir.xyz, global_lights[0].dir) - 0.99) * 200.0, 0.0, 1.0); + let sky_color = light_mult * vec3(1.0, 1.0, 1.0); + color += vec4(sky_color * (1.0 - color.a), 1.0 - color.a); + color.a = 1.0; + return color; +} + +const ZERO3F = vec3(0.0); +const ZERO2F = vec2(0.0); +const DEPTH = 16u; +const FULL_ALPHA = 0.9999; + +fn trace_full(pos_view: vec4, dir_view: vec4) -> vec4 { + let gi = 0; + let group = voxel_groups[gi]; + if group.dimensions.x == 0 { + return vec4(0.0); + } + let dim_f = vec3(group.dimensions); + let dim_i = vec3(group.dimensions); + + // transform so that group is at 0,0 + let pos_start = (group.transform_inv * pos_view).xyz; + let dir = (group.transform_inv * dir_view).xyz; + + let dir_if = sign(dir); + + + + // calculate normals + var normals = mat3x3( + (group.transform * vec4(dir_if.x, 0.0, 0.0, 0.0)).xyz, + (group.transform * vec4(0.0, dir_if.y, 0.0, 0.0)).xyz, + (group.transform * vec4(0.0, 0.0, dir_if.z, 0.0)).xyz, + ); + var next_normal = vec3(0.0, 0.0, 0.0); + + // find where ray intersects with group + let plane_point = (vec3(1.0) - dir_if) / 2.0 * dim_f; + var pos = pos_start; + var t = 0.0; + if outside3f(pos, ZERO3F, dim_f) { + // time of intersection; x = td + p, solve for t + let t_i = (plane_point - pos) / dir; + // points of intersection + let px = pos + t_i.x * dir; + let py = pos + t_i.y * dir; + let pz = pos + t_i.z * dir; + + // check if point is in bounds + let hit = vec3( + inside2f(px.yz, ZERO2F, dim_f.yz), + inside2f(py.xz, ZERO2F, dim_f.xz), + inside2f(pz.xy, ZERO2F, dim_f.xy), + ) && (t_i > ZERO3F); + if !any(hit) { + return vec4(0.0); + } + pos = select(select(pz, py, hit.y), px, hit.x); + t = select(select(t_i.z, t_i.y, hit.y), t_i.x, hit.x); + next_normal = select(select(normals[2], normals[1], hit.y), normals[0], hit.x); + } + var vox_pos = clamp(vec3(pos), vec3(0), dim_i - vec3(1)); + + + + let dir_i = vec3(dir_if); + let dir_u = ((dir_i + vec3(1)) / 2); + let dir_bits = u32(dir_u.x * 4 + dir_u.y * 2 + dir_u.z); + // time to move 1 unit using dir + let inc_t = abs(1.0 / dir); + var side_len = 256; + // "unsigned" minimum cube coords of current tree + var low_corner = vec3(0); + // time of next 1 unit plane hit in each direction + var color = vec4(0.0); + var data_start = 1u; + var i = 0u; + var axis = 0; + var hits = 0; + for (var safety = 0; safety < 1000; safety += 1) { + let node = voxels[group.offset + i]; + if node >= LEAF_BIT { + hits += 1; + let vcolor = get_color(node & LEAF_MASK); + if vcolor.a > 0.0 { + let diffuse = max(dot(global_lights[0].dir, next_normal) + 0.1, 0.0); + let ambient = 0.2; + let lighting = max(diffuse, ambient); + let new_color = min(vcolor.xyz * lighting, vec3(1.0)); + color += vec4(new_color.xyz * vcolor.a, vcolor.a) * (1.0 - color.a); + if color.a > .999 { + return color; + } + } + + // move to next face of cube + let half_len = f32(side_len) / 2.0; + let corner = vec3(low_corner) + vec3(half_len) + dir_if * half_len; + let next_t = inc_t * abs(corner - pos_start); + axis = select(select(2, 1, next_t.y < next_t.z), 0, next_t.x < next_t.y && next_t.x < next_t.z); + t = next_t[axis]; + next_normal = normals[axis]; + pos = pos_start + t * dir; + let old = vox_pos[axis]; + vox_pos = vec3(pos) - low_corner; + vox_pos[axis] += select(0, dir_i[axis], vox_pos[axis] == 0 || vox_pos[axis] == side_len - 1); + // if hits == 1 { + // // var axis_c = vec3(0.0); + // // axis_c[axis] = 1.0; + // // return vec4(axis_c, 1.0); + // return vec4(vec3(vox_pos), 1.0); + // } + } else if inside3i(vox_pos, vec3(0), vec3(side_len - 1)) { + let node_pos = data_start + node; + side_len /= 2; + let vcorner = vox_pos / side_len; + vox_pos -= vcorner * side_len; + let j = u32(vcorner.x * 4 + vcorner.y * 2 + vcorner.z); + i = node_pos + j; + data_start = node_pos + 9; + + low_corner += vec3(dir_to_vec(j)) * i32(side_len); + + continue; + } + + // idrk what to put here tbh but this prolly works; don't zoom out if max + if side_len == 256 { + return color; + } + + // get parent info and reset "pointers" to parent + let parent_info_i = data_start - 1; + let parent_info = voxels[group.offset + parent_info_i]; + let parent_root = parent_info_i - (parent_info >> 3); + let parent_loc = parent_info & 7; + let loc = 8 - (data_start - 1 - i); + // let test = (parent_root + 9 + voxels[group.offset + parent_root + parent_loc] + loc) == i; + i = parent_root + parent_loc; + data_start = parent_root + 9; + + // adjust corner back to parent + let low_corner_adj = vec3(dir_to_vec(loc)) * i32(side_len); + low_corner -= low_corner_adj; + + // update vox pos to be relative to parent + vox_pos += low_corner_adj; + + side_len *= 2; + // return vec4(vec3(dir_to_vec(parent_loc)) * f32(loc) / 8.0, 1.0); + // return vec4(vec3(f32(test)), 1.0); + } + return color; +} + +const LEAF_BIT = 1u << 31u; +const LEAF_MASK = ~LEAF_BIT; + +// there's no way this is efficient, mod is faster for all I know +fn dir_to_vec(bits: u32) -> vec3 { + return vec3(extractBits(bits, 2u, 1u), extractBits(bits, 1u, 1u), extractBits(bits, 0u, 1u)); +} + +fn get_voxel(offset: u32, pos_: vec3) -> u32 { + var data_start = 1u; + var i = 0u; + var pos = pos_; + var side_len: u32 = 256; + var safety = 0; + while voxels[offset + i] < LEAF_BIT { + let node_pos = data_start + voxels[offset + i]; + side_len /= 2u; + let corner = pos / side_len; + pos -= corner * side_len; + let j = corner.x * 4 + corner.y * 2 + corner.z; + i = node_pos + j; + data_start = node_pos + 8; + if safety == 10 { + return 10u; + } + safety += 1; + } + return voxels[offset + i] & LEAF_MASK; +} + +fn get_color(id: u32) -> vec4 { + switch id { + case 0u: { + return vec4(0.0); + } + case 1u: { + return vec4(0.5, 0.5, 0.5, 1.0); + } + case 2u: { + return vec4(0.5, 1.0, 0.5, 1.0); + } + case 3u: { + return vec4(0.5, 0.5, 1.0, 0.5); + } + default: { + return vec4(1.0, 0.0, 0.0, 1.0); + } + } +} + +fn outside3f(v: vec3, low: vec3, high: vec3) -> bool { + return any(v < low) || any(v > high); +} + +fn inside2f(v: vec2, low: vec2, high: vec2) -> bool { + return all(v >= low) && all(v <= high); +} + +fn inside3i(v: vec3, low: vec3, high: vec3) -> bool { + return all(v >= low) && all(v <= high); +} diff --git a/src/client/render/voxel/ray_oct/shader_broken.wgsl b/src/client/render/voxel/ray_oct/shader_broken.wgsl new file mode 100644 index 0000000..6588f96 --- /dev/null +++ b/src/client/render/voxel/ray_oct/shader_broken.wgsl @@ -0,0 +1,277 @@ +// Vertex shader + +struct GlobalLight { + dir: vec3, +}; + +struct VertexOutput { + @builtin(position) clip_position: vec4, +}; + +struct View { + transform: mat4x4, + width: u32, + height: u32, + zoom: f32, +}; + +struct VoxelGroup { + transform: mat4x4, + transform_inv: mat4x4, + dimensions: vec3, + offset: u32, +}; + +@group(0) @binding(0) +var view: View; +@group(0) @binding(1) +var voxels: array; +@group(0) @binding(2) +var voxel_groups: array; +@group(0) @binding(3) +var global_lights: array; + +@vertex +fn vs_main( + @builtin(vertex_index) vi: u32, + @builtin(instance_index) ii: u32, +) -> VertexOutput { + var out: VertexOutput; + + var pos = vec2( + f32(vi % 2u) * 2.0 - 1.0, + f32(vi / 2u) * 2.0 - 1.0, + ) ; + out.clip_position = vec4(pos.x, pos.y, 0.0, 1.0); + return out; +} + +// Fragment shader + +@fragment +fn fs_main( + in: VertexOutput, +) -> @location(0) vec4 { + // get position of the pixel; eye at origin, pixel on plane z = 1 + let win_dim = vec2(f32(view.width), f32(view.height)); + let aspect = win_dim.y / win_dim.x; + let pixel_pos = vec3( + (in.clip_position.xy / win_dim - vec2(0.5)) * vec2(2.0, -2.0 * aspect), + 1.0 + ); + + // move to position in world + let pos = view.transform * vec4(pixel_pos, 1.0); + let dir = view.transform * vec4(normalize(pixel_pos), 0.0); + + var color = trace_full(pos, dir); + let light_mult = clamp((-dot(dir.xyz, global_lights[0].dir) - 0.99) * 200.0, 0.0, 1.0); + let sky_color = light_mult * vec3(1.0, 1.0, 1.0); + color += vec4(sky_color * (1.0 - color.a), 1.0 - color.a); + color.a = 1.0; + return color; +} + +const ZERO3F = vec3(0.0); +const ZERO2F = vec2(0.0); +const DEPTH = 16u; +const FULL_ALPHA = 0.9999; + +fn trace_full(pos_view: vec4, dir_view: vec4) -> vec4 { + let gi = 0; + let group = voxel_groups[gi]; + if group.dimensions.x == 0 { + return vec4(0.0); + } + let dim_f = vec3(group.dimensions); + let dim_i = vec3(group.dimensions); + + // transform so that group is at 0,0 + let pos_start = (group.transform_inv * pos_view).xyz; + let dir = (group.transform_inv * dir_view).xyz; + + let dir_if = sign(dir); + + + + // calculate normals + var normals = mat3x3( + (group.transform * vec4(dir_if.x, 0.0, 0.0, 0.0)).xyz, + (group.transform * vec4(0.0, dir_if.y, 0.0, 0.0)).xyz, + (group.transform * vec4(0.0, 0.0, dir_if.z, 0.0)).xyz, + ); + var next_normal = vec3(0.0, 0.0, 0.0); + + // find where ray intersects with group + let plane_point = (vec3(1.0) - dir_if) / 2.0 * dim_f; + var pos = pos_start; + var t = 0.0; + if outside3f(pos, ZERO3F, dim_f) { + // time of intersection; x = td + p, solve for t + let t_i = (plane_point - pos) / dir; + // points of intersection + let px = pos + t_i.x * dir; + let py = pos + t_i.y * dir; + let pz = pos + t_i.z * dir; + + // check if point is in bounds + let hit = vec3( + inside2f(px.yz, ZERO2F, dim_f.yz), + inside2f(py.xz, ZERO2F, dim_f.xz), + inside2f(pz.xy, ZERO2F, dim_f.xy), + ) && (t_i > ZERO3F); + if !any(hit) { + return vec4(0.0); + } + pos = select(select(pz, py, hit.y), px, hit.x); + t = select(select(t_i.z, t_i.y, hit.y), t_i.x, hit.x); + next_normal = select(select(normals[2], normals[1], hit.y), normals[0], hit.x); + } + var vox_pos = clamp(vec3(pos), vec3(0), dim_i - vec3(1)); + + + + let dir_i = vec3(dir_if); + let dir_u = ((dir_i + vec3(1)) / 2); + let dir_bits = u32(dir_u.x * 4 + dir_u.y * 2 + dir_u.z); + // time to move 1 unit using dir + let inc_t = abs(1.0 / dir); + var side_len = 256; + // "unsigned" minimum cube coords of current tree + var low_corner = vec3(0); + // time of next 1 unit plane hit in each direction + var color = vec4(0.0); + var data_start = 1u; + var i = 0u; + var axis = 0; + for (var safety = 0; safety < 100; safety += 1) { + let node = voxels[group.offset + i]; + if node >= LEAF_BIT { + let vcolor = get_color(node & LEAF_MASK); + if vcolor.a > 0.0 { + let diffuse = max(dot(global_lights[0].dir, next_normal) + 0.1, 0.0); + let ambient = 0.2; + let lighting = max(diffuse, ambient); + let new_color = min(vcolor.xyz * lighting, vec3(1.0)); + color += vec4(new_color.xyz * vcolor.a, vcolor.a) * (1.0 - color.a); + if color.a > .999 { + return color; + } + } + + // move to next face of cube + let half_len = f32(side_len) / 2.0; + let corner = vec3(low_corner) + vec3(half_len) + dir_if * half_len; + let next_t = inc_t * abs(corner - pos_start); + axis = select(select(2, 1, next_t.y < next_t.z), 0, next_t.x < next_t.y && next_t.x < next_t.z); + t = next_t[axis]; + next_normal = normals[axis]; + pos = pos_start + t * dir; + let old = vox_pos[axis]; + vox_pos = vec3(pos) - low_corner; + vox_pos[axis] = old + side_len * dir_i[axis]; + // var axis_c = vec3(0.0); + // axis_c[axis] = 1.0; + // return vec4(axis_c, 1.0); + } else if inside3i(vox_pos, vec3(0), vec3(side_len)) { + let node_pos = data_start + node; + side_len /= 2; + let vcorner = vox_pos / side_len; + vox_pos -= vcorner * side_len; + let j = u32(vcorner.x * 4 + vcorner.y * 2 + vcorner.z); + i = node_pos + j; + data_start = node_pos + 9; + + low_corner += vec3(dir_to_vec(j)) * i32(side_len); + + continue; + } + + // get parent info and reset "pointers" to parent + let parent_info_i = data_start - 1; + if parent_info_i == 0 { + return color; + } + let parent_info = voxels[group.offset + parent_info_i]; + let parent_root = parent_info_i - (parent_info >> 3); + let parent_loc = parent_info & 7; + let loc = 8 - (data_start - 1 - i); + // let test = (parent_root + 9 + voxels[group.offset + parent_root + parent_loc] + loc) == i; + i = parent_root + parent_loc; + data_start = parent_root + 9; + + // adjust corner back to parent + let low_corner_adj = vec3(dir_to_vec(loc)) * i32(side_len); + low_corner -= low_corner_adj; + + // update vox pos to be relative to parent + vox_pos += low_corner_adj; + + side_len *= 2; + // return vec4(vec3(dir_to_vec(parent_loc)) * f32(loc) / 8.0, 1.0); + // return vec4(vec3(f32(test)), 1.0); + } + return color; +} + +const LEAF_BIT = 1u << 31u; +const LEAF_MASK = ~LEAF_BIT; + +// there's no way this is efficient, mod is faster for all I know +fn dir_to_vec(bits: u32) -> vec3 { + return vec3(extractBits(bits, 2u, 1u), extractBits(bits, 1u, 1u), extractBits(bits, 0u, 1u)); +} + +fn get_voxel(offset: u32, pos_: vec3) -> u32 { + var data_start = 1u; + var i = 0u; + var pos = pos_; + var side_len: u32 = 256; + var safety = 0; + while voxels[offset + i] < LEAF_BIT { + let node_pos = data_start + voxels[offset + i]; + side_len /= 2u; + let corner = pos / side_len; + pos -= corner * side_len; + let j = corner.x * 4 + corner.y * 2 + corner.z; + i = node_pos + j; + data_start = node_pos + 8; + if safety == 10 { + return 10u; + } + safety += 1; + } + return voxels[offset + i] & LEAF_MASK; +} + +fn get_color(id: u32) -> vec4 { + switch id { + case 0u: { + return vec4(0.0); + } + case 1u: { + return vec4(0.5, 0.5, 0.5, 1.0); + } + case 2u: { + return vec4(0.5, 1.0, 0.5, 1.0); + } + case 3u: { + return vec4(0.5, 0.5, 1.0, 0.5); + } + default: { + return vec4(1.0, 0.0, 0.0, 1.0); + } + } +} + +fn outside3f(v: vec3, low: vec3, high: vec3) -> bool { + return any(v < low) || any(v > high); +} + +fn inside2f(v: vec2, low: vec2, high: vec2) -> bool { + return all(v >= low) && all(v <= high); +} + +fn inside3i(v: vec3, low: vec3, high: vec3) -> bool { + return all(v >= low) && all(v <= high); +} diff --git a/src/client/render/voxel/ray_oct/view.rs b/src/client/render/voxel/ray_oct/view.rs new file mode 100644 index 0000000..cb892b2 --- /dev/null +++ b/src/client/render/voxel/ray_oct/view.rs @@ -0,0 +1,23 @@ +use nalgebra::Transform3; + +#[repr(C, align(16))] +#[derive(Clone, Copy, PartialEq, bytemuck::Zeroable)] +pub struct View { + pub transform: Transform3, + pub width: u32, + pub height: u32, + pub zoom: f32, +} + +unsafe impl bytemuck::Pod for View {} + +impl Default for View { + fn default() -> Self { + Self { + width: 1, + height: 1, + zoom: 1.0, + transform: Transform3::identity(), + } + } +} diff --git a/src/client/system/render.rs b/src/client/system/render.rs index a2abb5b..dbe513d 100644 --- a/src/client/system/render.rs +++ b/src/client/system/render.rs @@ -57,14 +57,15 @@ pub fn update_transform( } pub fn add_chunk( - query: Query<(Entity, &ChunkPos, &ChunkMesh), Or<(Added, Added)>>, + query: Query<(Entity, &ChunkPos, &ChunkMesh, &ChunkData), Or<(Added, Added, Added)>>, mut renderer: ResMut, ) { - for (id, pos, mesh) in query.iter() { + for (id, pos, mesh, data) in query.iter() { renderer.push(RenderCommand::AddChunk(AddChunk { id, pos: *pos, - mesh: mesh.clone() + mesh: mesh.clone(), + tree: data.deref().clone(), })); } } diff --git a/src/common/component/chunk.rs b/src/common/component/chunk.rs deleted file mode 100644 index 4cae656..0000000 --- a/src/common/component/chunk.rs +++ /dev/null @@ -1,124 +0,0 @@ -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/common/component/chunk/mesh.rs b/src/common/component/chunk/mesh.rs new file mode 100644 index 0000000..9b5b34f --- /dev/null +++ b/src/common/component/chunk/mesh.rs @@ -0,0 +1,52 @@ +use bevy_ecs::component::Component; +use block_mesh::{ndshape::RuntimeShape, UnitQuadBuffer, RIGHT_HANDED_Y_UP_CONFIG}; +use nalgebra::Vector3; +use ndarray::{ArrayView3, Axis}; + +use crate::client::render::voxel::{VoxelColor, /*VoxelFace*/}; + +#[derive(Debug, Clone, Component)] +pub struct ChunkMesh { + // pub faces: [Vec; 6], +} + +impl ChunkMesh { + pub fn from_data(data: ArrayView3) -> 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*/ } + } +} + diff --git a/src/common/component/chunk/mod.rs b/src/common/component/chunk/mod.rs new file mode 100644 index 0000000..2b7abc6 --- /dev/null +++ b/src/common/component/chunk/mod.rs @@ -0,0 +1,80 @@ +mod mesh; +pub use mesh::*; + +use std::collections::{HashMap, HashSet}; + +use crate::util::oct_tree::OctTree; +use bevy_derive::{Deref, DerefMut}; +use bevy_ecs::{bundle::Bundle, component::Component, entity::Entity, system::Resource}; +use nalgebra::Vector3; + +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 from_tree(t: OctTree) -> Self { + Self { data: t } + } + pub fn empty() -> Self { + Self { + data: OctTree::from_leaf(0, 8), + } + } +} + +#[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, 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/server/chunk/load.rs b/src/server/chunk/load.rs index 0666681..e0ceeaf 100644 --- a/src/server/chunk/load.rs +++ b/src/server/chunk/load.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, HashSet}; use bevy_ecs::{entity::Entity, system::Commands}; use nalgebra::Vector3; @@ -16,23 +16,23 @@ use crate::{ pub struct ChunkManager { handles: Vec>, - i: usize, n: usize, map: HashMap, generating: HashSet, + available: Vec, } impl ChunkManager { pub fn new() -> Self { - let n = 4; + let n = 1; Self { handles: std::iter::repeat_with(|| ThreadHandle::spawn(chunk_loader_main)) .take(n) .collect(), - i: 0, n, map: HashMap::new(), generating: HashSet::new(), + available: (0..n).collect(), } } pub fn entity_at(&self, pos: &ChunkPos) -> Option<&Entity> { @@ -41,29 +41,38 @@ impl ChunkManager { pub fn is_generating(&self, pos: &ChunkPos) -> bool { self.generating.contains(pos) } - pub fn queue(&mut self, pos: ChunkPos) { + pub fn try_load(&mut self, pos: ChunkPos) -> bool { if !self.is_generating(&pos) { - self.handles[self.i].send(ChunkLoaderMsg::Generate(pos)); - self.i = (self.i + 1) % self.n; - self.generating.insert(pos); + if let Some(i) = self.available.pop() { + self.handles[i].send(ChunkLoaderMsg::Generate(pos)); + self.generating.insert(pos); + true + } else { + false + } + } else { + false } } 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); + self.handles.iter_mut().enumerate().for_each(|(i, h)| { + if let Some(msg) = h.recv().next() { + self.available.push(i); + 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); + } } } - } + }); } } @@ -98,45 +107,65 @@ impl ExitType for ChunkLoaderMsg { } fn chunk_loader_main(channel: ThreadChannel) { - let mut to_generate = VecDeque::new(); 'outer: loop { - let msg = channel.recv_wait(); - match msg { + match channel.recv_wait() { ChunkLoaderMsg::Generate(pos) => { - to_generate.push_back(pos); + let start = std::time::Instant::now(); + // let data = 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 + // ]), + // 8, + // )); + let tree = ChunkData::from_tree(generate_tree(pos)); + // let data = ChunkData::empty(); + let tree_time = std::time::Instant::now() - start; + + let start = std::time::Instant::now(); + let mut data = generate(pos); + let data_time = std::time::Instant::now() - start; + + let start = std::time::Instant::now(); + let shape = s![ + 1..data.len_of(Axis(0)) - 1, + 1..data.len_of(Axis(1)) - 1, + 1..data.len_of(Axis(2)) - 1 + ]; + let mut slice = data.slice_mut(shape); + let mut iter = tree.into_iter(); + slice.assign(&Array3::from_shape_fn((256, 256, 256), |_| { + iter.next().unwrap() + })); + let convert_time = std::time::Instant::now() - start; + + let start = std::time::Instant::now(); + let mesh = ChunkMesh::from_data(data.map(|i| COLOR_MAP[*i as usize]).view()); + let mesh_time = std::time::Instant::now() - start; + + println!( + "data: {:<5?} mesh: {:<5?} convert: {:<5?} tree: {:<5?}", + data_time, mesh_time, convert_time, tree_time + ); + + channel.send(ServerChunkMsg::ChunkGenerated(GeneratedChunk { + pos, + data: tree, + mesh, + })); } 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 { +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()); + if pos.y > 0 || pos.y < -1 { + return Array3::from_elem(shape, 0); } 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); @@ -154,29 +183,80 @@ fn generate(pos: ChunkPos) -> Array3 { 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, + if y > n { + 3 + } else { + 1 } } else if y < c { - VoxelColor { - r: 100, - g: 255, - b: 100, - a: 255, - } + 2 } else { - VoxelColor { - r: 150, - g: 150, - b: 150, - a: 255, - } + 1 } } else { - VoxelColor::none() + 0 } }) } + +fn generate_tree(pos: ChunkPos) -> OctTree { + if pos.y > 0 || pos.y < -1 { + return OctTree::from_leaf(0, 8); + } + let posf: Vector3 = pos.cast() * chunk::SIDE_LENGTH as f32; + let (a, b, c, d) = (0.0, 50.0, 100.0, 127.0); + let (noise, ..) = + NoiseBuilder::gradient_2d_offset(posf.x, chunk::SIDE_LENGTH, posf.z, chunk::SIDE_LENGTH) + .with_seed(0) + .with_freq(0.005) + .generate(); + OctTree::from_fn( + &mut |p| { + let y = p.y as f32 + posf.y; + let n = (noise[p.x + p.z * chunk::SIDE_LENGTH] + 0.022) * (1.0 / 0.044) * d; + if y < n.max(b) { + if y < b { + if y > n { + 3 + } else { + 1 + } + } else if y < c { + 2 + } else { + 1 + } + } else { + 0 + } + }, + 8, + ) +} + +const COLOR_MAP: [VoxelColor; 4] = [ + VoxelColor { + r: 0, + g: 0, + b: 0, + a: 0, + }, + VoxelColor { + r: 150, + g: 150, + b: 150, + a: 255, + }, + VoxelColor { + r: 100, + g: 255, + b: 100, + a: 255, + }, + VoxelColor { + r: 100, + g: 100, + b: 255, + a: 200, + }, +]; diff --git a/src/server/system/sync.rs b/src/server/system/sync.rs index cddf9cb..c931a8e 100644 --- a/src/server/system/sync.rs +++ b/src/server/system/sync.rs @@ -35,7 +35,7 @@ pub fn chunks( fp.y.floor() as i32, fp.z.floor() as i32, ); - let radius: i32 = 5; + let radius: i32 = 1; let width = radius * 2 - 1; let mut desired = Vec::new(); for i in 0..width.pow(3) { @@ -47,6 +47,7 @@ pub fn chunks( } } desired.sort_by(|(da, ..), (db, ..)| da.total_cmp(db)); + let mut to_load = Vec::new(); for (_, pos) in desired { let coords = pos - player_chunk; let pos = ChunkPos(coords); @@ -63,10 +64,15 @@ pub fn chunks( )); loaded.insert(*pos); } else { - loader.queue(pos); + to_load.push(pos); } } } + for pos in to_load { + if !loader.try_load(pos) { + break; + } + } } loader.update(&mut commands); } diff --git a/src/util/oct_tree.rs b/src/util/oct_tree.rs index a99399b..6232f60 100644 --- a/src/util/oct_tree.rs +++ b/src/util/oct_tree.rs @@ -1,42 +1,205 @@ -use std::fmt::Debug; +use std::{collections::VecDeque, fmt::Debug}; use nalgebra::Vector3; -use ndarray::{Array3, ArrayView3, Axis}; +use ndarray::ArrayView3; + +const LEAF_BIT: u32 = 1 << 31; +const DATA_OFFSET: usize = 9; + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +pub struct OctNode(u32); +impl OctNode { + pub fn new_node(addr: u32) -> Self { + Self(addr) + } + pub fn new_leaf(data: u32) -> Self { + Self(data | LEAF_BIT) + } + pub fn new_parent(offset: u32, corner: u32) -> Self { + Self((offset << 3) + corner) + } + pub fn is_leaf(&self) -> bool { + self.0 >= LEAF_BIT + } + pub fn is_node(&self) -> bool { + self.0 < LEAF_BIT + } + pub fn node_data(&self) -> u32 { + self.0 + } + pub fn leaf_data(&self) -> u32 { + self.0 & !LEAF_BIT + } +} #[derive(Debug, Clone)] -pub enum OctTree { - Leaf(T), - Node(Box<[OctTree; 8]>), +pub struct OctTree { + data: Vec, + levels: u32, + side_length: usize, } -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(); +const CORNERS: [Vector3; 8] = [ + Vector3::new(0, 0, 0), + Vector3::new(0, 0, 1), + Vector3::new(0, 1, 0), + Vector3::new(0, 1, 1), + Vector3::new(1, 0, 0), + Vector3::new(1, 0, 1), + Vector3::new(1, 1, 0), + Vector3::new(1, 1, 1), +]; + +impl OctTree { + pub fn from_leaf(val: u32, levels: u32) -> Self { + Self { + data: vec![OctNode::new_leaf(val)], + side_length: 2usize.pow(levels), + levels, } - node_arr[[0; 3]].clone() + } + pub fn from_fn(f: &mut impl FnMut(Vector3) -> u32, levels: u32) -> OctTree { + Self::from_fn_offset(f, levels, Vector3::from_element(0)) + } + pub fn from_fn_offset( + f: &mut impl FnMut(Vector3) -> u32, + levels: u32, + offset: Vector3, + ) -> Self { + let mut data = Vec::new(); + data.push(OctNode::new_node(0)); + // #######N P SSSSSSSS P + // --------------------| 17 + // -------| 7 + Self::from_fn_offset_inner(f, &mut data, levels, offset, OctNode::new_parent(17, 7)); + if data.len() == 2 { + data.remove(0); + } + Self { + data, + side_length: 2usize.pow(levels), + levels, + } + } + fn from_fn_offset_inner( + f: &mut impl FnMut(Vector3) -> u32, + accumulator: &mut Vec, + level: u32, + offset: Vector3, + parent: OctNode, + ) { + if level == 0 { + accumulator.push(OctNode::new_leaf(f(offset))); + return; + } else if level == 1 { + let leaves: [OctNode; 8] = + core::array::from_fn(|i| OctNode::new_leaf(f(offset + CORNERS[i]))); + if leaves.iter().all(|l| *l == leaves[0]) { + accumulator.push(leaves[0]); + } else { + accumulator.extend_from_slice(&leaves); + accumulator.push(parent); + } + return; + } + let i = accumulator.len(); + accumulator.resize(i + 8, OctNode::new_node(0)); + accumulator.push(parent); + let mut data_start = 0; + for (j, corner_offset) in CORNERS.iter().enumerate() { + let sub_start = accumulator.len(); + let sub_parent_offset = 9 + data_start + 8; + Self::from_fn_offset_inner( + f, + accumulator, + level - 1, + offset + corner_offset * 2usize.pow(level - 1), + OctNode::new_parent(sub_parent_offset as u32, j as u32), + ); + let len = accumulator.len() - sub_start; + if len == 1 { + accumulator[i + j] = accumulator[sub_start]; + accumulator.pop(); + } else { + accumulator[i + j] = OctNode::new_node(data_start as u32); + data_start += len; + } + } + if data_start == 0 { + let first = accumulator[i]; + if accumulator[i..i + 8].iter().all(|l| *l == first) { + accumulator.truncate(i); + accumulator.push(first) + } + } + } + pub fn from_arr(arr: ArrayView3, levels: u32) -> Self { + Self::from_fn(&mut |p| arr[(p.x, p.y, p.z)], levels) + } + pub fn get(&self, mut pos: Vector3) -> u32 { + let mut data_start = 1; + let mut i = 0; + let mut half_len = self.side_length / 2; + while self.data[i].is_node() { + let node_pos = data_start + self.data[i].node_data() as usize; + let corner = pos / half_len; + pos -= corner * half_len; + half_len /= 2; + let j = corner.x * 4 + corner.y * 2 + corner.z; + i = node_pos + j; + data_start = node_pos + DATA_OFFSET; + } + self.data[i].leaf_data() + } + pub fn raw(&self) -> &[OctNode] { + &self.data } } -impl OctTree { - fn get(i: Vector3) { +pub struct OctTreeIter<'a> { + queue: Vec, + levels: Vec, + pos: usize, + cur: u32, + run: usize, + data: &'a [OctNode], +} +impl<'a> Iterator for OctTreeIter<'a> { + type Item = u32; + fn next(&mut self) -> Option { + if self.run != 0 { + self.run -= 1; + return Some(self.cur); + } + let node = self.queue.pop()?; + let level = self.levels.pop()?; + if node.is_leaf() { + self.run = 8usize.pow(level); + self.cur = node.leaf_data(); + } else { + let pos = 0; + let add = &self.data[pos..pos + 8]; + self.data = &self.data[pos + DATA_OFFSET..]; + self.queue.extend(add.iter().rev()); + self.levels.resize(self.levels.len() + 8, level - 1); + } + self.next() + } +} + +impl<'a> IntoIterator for &'a OctTree { + type Item = u32; + type IntoIter = OctTreeIter<'a>; + fn into_iter(self) -> Self::IntoIter { + OctTreeIter { + data: &self.data[1..], + pos: 0, + cur: 0, + levels: vec![self.levels], + run: 0, + queue: vec![self.data[0]], + } } }