Start clean
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "cache"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
bytemuck = "1.24.0"
|
||||
crevice = {version = "0.18.0", features = ["cgmath"]}
|
||||
egui = "0.33.3"
|
||||
egui-wgpu = "0.33.3"
|
||||
egui-winit = "0.33.3"
|
||||
env_logger = "0.11.8"
|
||||
pollster = "0.4.0"
|
||||
wgpu = {version = "27.0.1", features = ["spirv"]}
|
||||
winit = "0.30.12"
|
||||
gpu_shared = {path = "gpu_shared/"}
|
||||
glam = {version = "0.30.10", features = ["bytemuck"]}
|
||||
itertools = "0.14.0"
|
||||
|
||||
[build-dependencies]
|
||||
spirv-builder = {git = "https://github.com/rust-gpu/rust-gpu"}
|
||||
7
rust-toolchain.toml
Normal file
7
rust-toolchain.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2025-06-30"
|
||||
components = ["rust-src", "rustc-dev", "llvm-tools"]
|
||||
# commit_hash = 35f6036521777bdc0dcea1f980be4c192962a168
|
||||
|
||||
# Whenever changing the nightly channel, update the commit hash above, and
|
||||
# change `REQUIRED_RUST_TOOLCHAIN` in `crates/rustc_codegen_spirv/build.rs` too.
|
||||
137
src/egui_renderer.rs
Normal file
137
src/egui_renderer.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use egui::{Context, PaintCallbackInfo};
|
||||
use egui_wgpu::{CallbackResources, CallbackTrait, Renderer, RendererOptions, ScreenDescriptor};
|
||||
use egui_winit::State;
|
||||
use wgpu::{CommandEncoder, Device, Queue, RenderPass, TextureFormat, TextureView};
|
||||
use winit::{event::WindowEvent, window::Window};
|
||||
|
||||
pub struct EguiState {
|
||||
pub state: State,
|
||||
pub renderer: Renderer,
|
||||
pub frame_started: bool,
|
||||
//pub msaa_texture: TextureView,
|
||||
pub color_format: TextureFormat,
|
||||
}
|
||||
|
||||
impl EguiState {
|
||||
pub fn context(&self) -> &Context {
|
||||
self.state.egui_ctx()
|
||||
}
|
||||
|
||||
pub fn new(device: &Device, output_color_format: TextureFormat, window: &Window) -> EguiState {
|
||||
let egui_context = Context::default();
|
||||
|
||||
let egui_state = egui_winit::State::new(
|
||||
egui_context,
|
||||
egui::ViewportId::ROOT,
|
||||
&window,
|
||||
Some(window.scale_factor() as f32),
|
||||
None,
|
||||
Some(2 * 1024),
|
||||
);
|
||||
|
||||
let options = RendererOptions {
|
||||
//msaa_samples: SAMPLE_COUNT,
|
||||
//depth_stencil_format: Some(TextureFormat::Depth24PlusStencil8),
|
||||
..Default::default()
|
||||
};
|
||||
let egui_renderer = Renderer::new(device, output_color_format, options);
|
||||
|
||||
EguiState {
|
||||
state: egui_state,
|
||||
renderer: egui_renderer,
|
||||
frame_started: false,
|
||||
color_format: output_color_format,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, _device: &Device, _width: u32, _height: u32) {}
|
||||
|
||||
pub fn handle_event(&mut self, window: &Window, event: &WindowEvent) {
|
||||
let _ = self.state.on_window_event(window, event);
|
||||
}
|
||||
|
||||
pub fn begin_frame(&mut self, window: &Window) {
|
||||
let input = self.state.take_egui_input(window);
|
||||
self.state.egui_ctx().begin_pass(input);
|
||||
self.frame_started = true;
|
||||
}
|
||||
|
||||
pub fn end_frame_and_draw(
|
||||
&mut self,
|
||||
device: &Device,
|
||||
queue: &Queue,
|
||||
encoder: &mut CommandEncoder,
|
||||
window: &Window,
|
||||
window_surface_view: &TextureView,
|
||||
screen_descriptor: ScreenDescriptor,
|
||||
) {
|
||||
if !self.frame_started {
|
||||
panic!("begin_frame must be called before end_frame_and_draw can be called!");
|
||||
}
|
||||
|
||||
//self.ppp(screen_descriptor.pixels_per_point);
|
||||
|
||||
let full_output = self.state.egui_ctx().end_pass();
|
||||
|
||||
self.state
|
||||
.handle_platform_output(window, full_output.platform_output);
|
||||
|
||||
let tris = self
|
||||
.state
|
||||
.egui_ctx()
|
||||
.tessellate(full_output.shapes, self.state.egui_ctx().pixels_per_point());
|
||||
for (id, image_delta) in &full_output.textures_delta.set {
|
||||
self.renderer
|
||||
.update_texture(device, queue, *id, image_delta);
|
||||
}
|
||||
self.renderer
|
||||
.update_buffers(device, queue, encoder, &tris, &screen_descriptor);
|
||||
let rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: window_surface_view,
|
||||
resolve_target: None,
|
||||
ops: egui_wgpu::wgpu::Operations {
|
||||
load: egui_wgpu::wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
depth_slice: None,
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
label: Some("egui main render pass"),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
self.renderer
|
||||
.render(&mut rpass.forget_lifetime(), &tris, &screen_descriptor);
|
||||
for x in &full_output.textures_delta.free {
|
||||
self.renderer.free_texture(x)
|
||||
}
|
||||
|
||||
self.frame_started = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CallbackFn<P>
|
||||
where
|
||||
P: Fn(PaintCallbackInfo, &mut RenderPass<'static>, &CallbackResources),
|
||||
{
|
||||
//pub : FnOnce(&Device, &Queue, &ScreenDescriptor, &mut CommandEncoder, &mut CallbackResources) -> Vec<>
|
||||
pub paint_fn: P,
|
||||
}
|
||||
|
||||
impl<P> CallbackTrait for CallbackFn<P>
|
||||
where
|
||||
P: Fn(PaintCallbackInfo, &mut RenderPass<'static>, &CallbackResources)
|
||||
+ std::marker::Sync
|
||||
+ std::marker::Send,
|
||||
{
|
||||
fn paint(
|
||||
&self,
|
||||
info: egui::PaintCallbackInfo,
|
||||
render_pass: &mut wgpu::RenderPass<'static>,
|
||||
callback_resources: &egui_wgpu::CallbackResources,
|
||||
) {
|
||||
(self.paint_fn)(info, render_pass, callback_resources)
|
||||
}
|
||||
}
|
||||
119
src/lib.rs
Normal file
119
src/lib.rs
Normal file
@ -0,0 +1,119 @@
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(generic_const_exprs)]
|
||||
|
||||
pub mod egui_renderer;
|
||||
pub mod state;
|
||||
pub mod voxel;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use winit::application::ApplicationHandler;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::event_loop::{self};
|
||||
use winit::window::Window;
|
||||
|
||||
use crate::state::State;
|
||||
|
||||
pub fn run() -> anyhow::Result<()>
|
||||
{
|
||||
env_logger::init();
|
||||
|
||||
let event_loop = EventLoop::with_user_event().build()?;
|
||||
let mut app = App::default();
|
||||
event_loop.run_app(&mut app)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// App struct
|
||||
#[derive(Default)]
|
||||
pub struct App
|
||||
{
|
||||
state: Option<State>,
|
||||
}
|
||||
|
||||
impl ApplicationHandler for App
|
||||
{
|
||||
fn resumed(&mut self, event_loop: &event_loop::ActiveEventLoop)
|
||||
{
|
||||
// Create window
|
||||
let window = Arc::new(
|
||||
event_loop
|
||||
.create_window(
|
||||
Window::default_attributes()
|
||||
.with_title("Wgpu Template")
|
||||
.with_resizable(true),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let state = pollster::block_on(State::new(window.clone()));
|
||||
self.state = Some(state);
|
||||
|
||||
window.request_redraw();
|
||||
window.set_cursor_visible(false);
|
||||
window
|
||||
.set_cursor_grab(winit::window::CursorGrabMode::Locked)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
&mut self,
|
||||
event_loop: &event_loop::ActiveEventLoop,
|
||||
_window_id: winit::window::WindowId,
|
||||
event: winit::event::WindowEvent,
|
||||
)
|
||||
{
|
||||
let state = self.state.as_mut().unwrap();
|
||||
state.handle_event(&event);
|
||||
|
||||
match event
|
||||
{
|
||||
WindowEvent::CloseRequested =>
|
||||
{
|
||||
event_loop.exit();
|
||||
}
|
||||
WindowEvent::RedrawRequested =>
|
||||
{
|
||||
state.render();
|
||||
state.get_window().request_redraw();
|
||||
}
|
||||
|
||||
WindowEvent::Resized(size) =>
|
||||
{
|
||||
state.resize(size);
|
||||
}
|
||||
|
||||
WindowEvent::MouseWheel { delta, .. } =>
|
||||
{
|
||||
state.mouse_wheel(delta);
|
||||
}
|
||||
|
||||
_ =>
|
||||
{}
|
||||
}
|
||||
}
|
||||
|
||||
fn device_event(
|
||||
&mut self,
|
||||
_event_loop: &event_loop::ActiveEventLoop,
|
||||
_device_id: winit::event::DeviceId,
|
||||
event: winit::event::DeviceEvent,
|
||||
)
|
||||
{
|
||||
let state = self.state.as_mut().unwrap();
|
||||
|
||||
#[allow(clippy::single_match)]
|
||||
match event
|
||||
{
|
||||
winit::event::DeviceEvent::MouseMotion { delta } =>
|
||||
{
|
||||
state.cursor_moved(delta.0 as f32, delta.1 as f32);
|
||||
}
|
||||
|
||||
_ =>
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/main.rs
Normal file
4
src/main.rs
Normal file
@ -0,0 +1,4 @@
|
||||
fn main() -> anyhow::Result<()>
|
||||
{
|
||||
cache::run()
|
||||
}
|
||||
432
src/state.rs
Normal file
432
src/state.rs
Normal file
@ -0,0 +1,432 @@
|
||||
use std::collections::HashSet;
|
||||
use std::f32::consts::PI;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytemuck::bytes_of;
|
||||
use egui_wgpu::ScreenDescriptor;
|
||||
use gpu_shared::ChunkPushConstants;
|
||||
use wgpu::ExperimentalFeatures;
|
||||
use wgpu::Extent3d;
|
||||
use wgpu::Features;
|
||||
use wgpu::FeaturesWGPU;
|
||||
use wgpu::FeaturesWebGPU;
|
||||
use wgpu::RenderPipeline;
|
||||
use wgpu::ShaderStages;
|
||||
use wgpu::TextureDescriptor;
|
||||
use wgpu::TextureFormat;
|
||||
use wgpu::TextureUsages;
|
||||
use wgpu::TextureView;
|
||||
use wgpu::include_spirv;
|
||||
use wgpu::include_spirv_raw;
|
||||
use winit::event::MouseScrollDelta;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::keyboard::KeyCode;
|
||||
use winit::window::Window;
|
||||
|
||||
use crate::egui_renderer::EguiState;
|
||||
|
||||
pub struct State
|
||||
{
|
||||
window: Arc<Window>,
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
size: winit::dpi::PhysicalSize<u32>,
|
||||
surface: wgpu::Surface<'static>,
|
||||
surface_format: wgpu::TextureFormat,
|
||||
egui_state: EguiState,
|
||||
|
||||
// Camera
|
||||
camera: Camera,
|
||||
|
||||
// pressed events
|
||||
pressed_set: HashSet<KeyCode>,
|
||||
pipeline: RenderPipeline,
|
||||
depth_buffer: TextureView,
|
||||
}
|
||||
|
||||
impl State
|
||||
{
|
||||
pub async fn new(window: Arc<Window>) -> State
|
||||
{
|
||||
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions::default())
|
||||
.await
|
||||
.unwrap();
|
||||
let (device, queue) = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor {
|
||||
required_features: Features {
|
||||
features_wgpu: FeaturesWGPU::PUSH_CONSTANTS
|
||||
| FeaturesWGPU::EXPERIMENTAL_PASSTHROUGH_SHADERS,
|
||||
features_webgpu: FeaturesWebGPU::empty(),
|
||||
},
|
||||
experimental_features: unsafe { ExperimentalFeatures::enabled() },
|
||||
required_limits: wgpu::Limits {
|
||||
max_push_constant_size: size_of::<ChunkPushConstants>() as u32,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let size = window.inner_size();
|
||||
|
||||
let surface = instance.create_surface(window.clone()).unwrap();
|
||||
let cap = surface.get_capabilities(&adapter);
|
||||
let surface_format = cap.formats[0];
|
||||
|
||||
// let shaders = include_spirv!(env!("shaders.spv"));
|
||||
// let shader_module = device.create_shader_module(shaders);
|
||||
let shaders = include_spirv_raw!(env!("shaders.spv"));
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("chunk_pipeline"),
|
||||
layout: Some(
|
||||
&device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[],
|
||||
push_constant_ranges: &[wgpu::PushConstantRange {
|
||||
stages: wgpu::ShaderStages::VERTEX,
|
||||
range: 0..(size_of::<ChunkPushConstants>() as u32),
|
||||
}],
|
||||
}),
|
||||
),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader_module,
|
||||
entry_point: Some("main_vs"),
|
||||
buffers: &[],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader_module,
|
||||
entry_point: Some("main_fs"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: surface_format,
|
||||
blend: None,
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: TextureFormat::Depth24PlusStencil8,
|
||||
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: false,
|
||||
},
|
||||
multiview: None,
|
||||
|
||||
cache: None,
|
||||
});
|
||||
|
||||
let state = State {
|
||||
egui_state: EguiState::new(&device, surface_format, &window),
|
||||
|
||||
camera: Camera {
|
||||
eye: glam::Vec3::new(0., 0., 0.),
|
||||
up: glam::Vec3::ZERO.with_y(1.),
|
||||
aspect: size.width as f32 / size.height as f32,
|
||||
fovy: 90.,
|
||||
znear: 0.001,
|
||||
zfar: 1000.,
|
||||
radius: 2.,
|
||||
yaw: 1.,
|
||||
pitch: 0.,
|
||||
rotation_speed: 0.005,
|
||||
speed: 0.005,
|
||||
},
|
||||
|
||||
window,
|
||||
queue,
|
||||
size,
|
||||
surface,
|
||||
surface_format,
|
||||
pipeline,
|
||||
pressed_set: HashSet::new(),
|
||||
depth_buffer: device
|
||||
.create_texture(&TextureDescriptor {
|
||||
label: Some("Depth buffer"),
|
||||
size: Extent3d {
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: TextureFormat::Depth24PlusStencil8,
|
||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
})
|
||||
.create_view(&Default::default()),
|
||||
|
||||
device,
|
||||
};
|
||||
|
||||
// Configure surface for the first time
|
||||
state.configure_surface();
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
pub fn get_window(&self) -> &Window
|
||||
{
|
||||
&self.window
|
||||
}
|
||||
|
||||
pub fn configure_surface(&self)
|
||||
{
|
||||
let surface_config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: self.surface_format,
|
||||
// Request compatibility with the sRGB-format texture view we‘re going to create later.
|
||||
view_formats: vec![self.surface_format.add_srgb_suffix()],
|
||||
alpha_mode: wgpu::CompositeAlphaMode::Auto,
|
||||
width: self.size.width,
|
||||
height: self.size.height,
|
||||
desired_maximum_frame_latency: 2,
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
};
|
||||
self.surface.configure(&self.device, &surface_config);
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>)
|
||||
{
|
||||
self.size = new_size;
|
||||
|
||||
// reconfigure the surface
|
||||
self.configure_surface();
|
||||
|
||||
self.depth_buffer = self
|
||||
.device
|
||||
.create_texture(&TextureDescriptor {
|
||||
label: Some("Depth buffer"),
|
||||
size: Extent3d {
|
||||
width: new_size.width,
|
||||
height: new_size.height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: TextureFormat::Depth24PlusStencil8,
|
||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
})
|
||||
.create_view(&Default::default());
|
||||
}
|
||||
|
||||
pub fn render(&mut self)
|
||||
{
|
||||
self.update_camera();
|
||||
|
||||
// Create texture view
|
||||
let surface_texture = self
|
||||
.surface
|
||||
.get_current_texture()
|
||||
.expect("failed to acquire next swapchain texture");
|
||||
let texture_view = surface_texture
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor {
|
||||
// Without add_srgb_suffix() the image we will be working with
|
||||
// might not be "gamma correct".
|
||||
format: Some(self.surface_format.add_srgb_suffix()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Renders a GREEN screen
|
||||
let mut encoder = self.device.create_command_encoder(&Default::default());
|
||||
{
|
||||
let mut renderpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &texture_view,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color {
|
||||
r: 0.01,
|
||||
g: 0.01,
|
||||
b: 0.01,
|
||||
a: 1.,
|
||||
}),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_buffer,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.),
|
||||
store: wgpu::StoreOp::Discard,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
renderpass.set_pipeline(&self.pipeline);
|
||||
renderpass.set_push_constants(
|
||||
ShaderStages::VERTEX,
|
||||
0,
|
||||
bytes_of(&ChunkPushConstants {
|
||||
view_projection: self.camera.view_proj(),
|
||||
transform: glam::Mat4::IDENTITY,
|
||||
eye_position: self.camera.eye,
|
||||
_zero_pad: 0.,
|
||||
}),
|
||||
);
|
||||
renderpass.draw(0..36, 0..1);
|
||||
|
||||
// End the renderpass.
|
||||
}
|
||||
// Egui
|
||||
{
|
||||
let screen_descriptor = ScreenDescriptor {
|
||||
size_in_pixels: [self.size.width, self.size.height],
|
||||
pixels_per_point: 1.,
|
||||
};
|
||||
self.egui_state.begin_frame(&self.window);
|
||||
|
||||
egui::Window::new("Hello Window").resizable(true).show(
|
||||
self.egui_state.context(),
|
||||
|ui| {
|
||||
ui.label("Hello, world.");
|
||||
},
|
||||
);
|
||||
|
||||
self.egui_state.end_frame_and_draw(
|
||||
&self.device,
|
||||
&self.queue,
|
||||
&mut encoder,
|
||||
&self.window,
|
||||
&texture_view,
|
||||
screen_descriptor,
|
||||
);
|
||||
}
|
||||
|
||||
// Submit the command in the queue to execute
|
||||
self.queue.submit([encoder.finish()]);
|
||||
self.window.pre_present_notify();
|
||||
surface_texture.present();
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// MOVEMENT
|
||||
// ------------------------------
|
||||
pub fn handle_event(&mut self, event: &WindowEvent)
|
||||
{
|
||||
self.egui_state.handle_event(&self.window, event);
|
||||
|
||||
if let WindowEvent::KeyboardInput { event, .. } = event
|
||||
{
|
||||
match (event.state, event.physical_key)
|
||||
{
|
||||
(winit::event::ElementState::Pressed, winit::keyboard::PhysicalKey::Code(c)) =>
|
||||
{
|
||||
self.pressed_set.insert(c);
|
||||
}
|
||||
(winit::event::ElementState::Released, winit::keyboard::PhysicalKey::Code(c)) =>
|
||||
{
|
||||
self.pressed_set.remove(&c);
|
||||
}
|
||||
_ =>
|
||||
{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_camera(&mut self)
|
||||
{
|
||||
let mut movement = glam::Vec3::new(0., 0., 0.);
|
||||
if self.pressed_set.contains(&KeyCode::KeyW)
|
||||
{
|
||||
movement.z += self.camera.speed;
|
||||
}
|
||||
if self.pressed_set.contains(&KeyCode::KeyS)
|
||||
{
|
||||
movement.z -= self.camera.speed;
|
||||
}
|
||||
|
||||
// Left rigth
|
||||
if self.pressed_set.contains(&KeyCode::KeyA)
|
||||
{
|
||||
movement.x += self.camera.speed;
|
||||
}
|
||||
if self.pressed_set.contains(&KeyCode::KeyD)
|
||||
{
|
||||
movement.x -= self.camera.speed;
|
||||
}
|
||||
|
||||
let rot_movement = glam::Mat3::from_rotation_y(-self.camera.yaw)
|
||||
* glam::Mat3::from_rotation_x(-self.camera.pitch)
|
||||
* movement;
|
||||
self.camera.eye -= rot_movement;
|
||||
}
|
||||
|
||||
pub fn cursor_moved(&mut self, x: f32, y: f32)
|
||||
{
|
||||
const SENSIBILITY: f32 = 0.0004;
|
||||
let position = glam::Vec2::new(x, y);
|
||||
let offset = position * SENSIBILITY;
|
||||
|
||||
self.camera.yaw += offset.x;
|
||||
self.camera.pitch += offset.y;
|
||||
}
|
||||
|
||||
pub fn mouse_wheel(&mut self, delta: MouseScrollDelta)
|
||||
{
|
||||
if let MouseScrollDelta::LineDelta(_, y) = delta
|
||||
{
|
||||
self.camera.speed += y * (self.camera.speed * 0.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Camera
|
||||
{
|
||||
pub eye: glam::Vec3,
|
||||
pub up: glam::Vec3,
|
||||
pub aspect: f32,
|
||||
pub fovy: f32,
|
||||
pub znear: f32,
|
||||
pub zfar: f32,
|
||||
|
||||
pub radius: f32,
|
||||
|
||||
pub yaw: f32,
|
||||
pub pitch: f32,
|
||||
pub rotation_speed: f32,
|
||||
pub speed: f32,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const OPENGL_TO_WGPU_MATRIX: glam::Mat4 = glam::Mat4::from_cols(
|
||||
glam::Vec4::new(1.0, 0.0, 0.0, 0.0),
|
||||
glam::Vec4::new(0.0, 1.0, 0.0, 0.0),
|
||||
glam::Vec4::new(0.0, 0.0, 0.5, 0.0),
|
||||
glam::Vec4::new(0.0, 0.0, 0.5, 1.0),
|
||||
);
|
||||
|
||||
impl Camera
|
||||
{
|
||||
pub fn view_proj(&self) -> glam::Mat4
|
||||
{
|
||||
let view = glam::Mat4::from_translation(self.eye)
|
||||
* glam::Mat4::from_rotation_y(-self.yaw)
|
||||
* glam::Mat4::from_rotation_x(-self.pitch);
|
||||
let proj =
|
||||
glam::Mat4::perspective_rh(PI * self.fovy / 180., self.aspect, self.znear, self.zfar);
|
||||
proj * view.inverse()
|
||||
}
|
||||
}
|
||||
837
src/voxel.rs
Normal file
837
src/voxel.rs
Normal file
@ -0,0 +1,837 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::hash::Hash;
|
||||
use std::usize;
|
||||
use std::vec;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub struct Color(pub f32, pub f32, pub f32, pub f32);
|
||||
|
||||
pub struct NTree<const N: usize>
|
||||
{
|
||||
structure: HashMap<NTreeLocator<N>, NTreeNode<N>>,
|
||||
depth: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct NTreeNode<const N: usize>
|
||||
{
|
||||
color: Color,
|
||||
subdivided: bool,
|
||||
}
|
||||
|
||||
impl<const N: usize> NTree<N>
|
||||
where
|
||||
[(); N * N * N]:,
|
||||
{
|
||||
pub fn constant(color: Color, depth: u32) -> Self
|
||||
{
|
||||
let mut structure = HashMap::new();
|
||||
structure.insert(
|
||||
NTreeLocator::root(),
|
||||
NTreeNode {
|
||||
color,
|
||||
subdivided: false,
|
||||
},
|
||||
);
|
||||
Self { structure, depth }
|
||||
}
|
||||
|
||||
pub fn get(&self, x: usize, y: usize, z: usize) -> Color
|
||||
{
|
||||
let mut local_x = x;
|
||||
let mut local_y = y;
|
||||
let mut local_z = z;
|
||||
|
||||
let mut size = N.pow(self.depth);
|
||||
assert!(x < size && y < size && z < size);
|
||||
|
||||
let mut current_loc = NTreeLocator::<N>::root();
|
||||
let mut current_node = *self.structure.get(¤t_loc).unwrap();
|
||||
|
||||
loop
|
||||
{
|
||||
if !current_node.subdivided
|
||||
{
|
||||
return current_node.color;
|
||||
}
|
||||
|
||||
size /= N;
|
||||
|
||||
// Descend
|
||||
let child_x = local_x / size;
|
||||
let child_y = local_y / size;
|
||||
let child_z = local_z / size;
|
||||
|
||||
// Node is subdivided, descend
|
||||
current_loc = current_loc.get_child(child_x, child_y, child_z);
|
||||
current_node = *self.structure.get(¤t_loc).unwrap();
|
||||
|
||||
local_x -= child_x * size;
|
||||
local_y -= child_y * size;
|
||||
local_z -= child_z * size;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_arrays_old(chunk: &[Color], depth: u32) -> Self
|
||||
{
|
||||
let chunk_width = N.pow(depth);
|
||||
let mut structure = HashMap::with_capacity(chunk_width * chunk_width * chunk_width);
|
||||
println!("Inserting voxels");
|
||||
for ((x, y), z) in (0..chunk_width)
|
||||
.cartesian_product(0..chunk_width)
|
||||
.cartesian_product(0..chunk_width)
|
||||
{
|
||||
structure.insert(
|
||||
NTreeLocator::<N>::from_depth_coords(x, y, z, depth as usize),
|
||||
NTreeNode::<N> {
|
||||
color: chunk[x + y * chunk_width + z * chunk_width * chunk_width],
|
||||
subdivided: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
println!("Starting bottom up");
|
||||
let mut current_size = 1;
|
||||
for d in (0..depth).rev()
|
||||
{
|
||||
println!("depth: {d}");
|
||||
current_size *= N;
|
||||
for ((x, y), z) in (0..(chunk_width / current_size))
|
||||
.cartesian_product(0..(chunk_width / current_size))
|
||||
.cartesian_product(0..(chunk_width / current_size))
|
||||
{
|
||||
let loc = NTreeLocator::<N>::from_depth_coords(x, y, z, d as usize);
|
||||
let children = (0..N)
|
||||
.cartesian_product(0..N)
|
||||
.cartesian_product(0..N)
|
||||
.map(|((cx, cy), cz)| structure.get(&loc.get_child(cx, cy, cz)).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (can_merge, _) = children.iter().fold((true, None), |acc, child| match acc
|
||||
{
|
||||
(_, None) => (true, Some(child.color)),
|
||||
(b, Some(color)) =>
|
||||
{
|
||||
(b && !child.subdivided && color == child.color, Some(color))
|
||||
}
|
||||
});
|
||||
|
||||
let node;
|
||||
if can_merge
|
||||
{
|
||||
node = NTreeNode::<N> {
|
||||
color: children[0].color,
|
||||
subdivided: false,
|
||||
};
|
||||
|
||||
//Remove merged childrenn
|
||||
(0..N)
|
||||
.cartesian_product(0..N)
|
||||
.cartesian_product(0..N)
|
||||
.for_each(|((cx, cy), cz)| {
|
||||
structure.remove(&loc.get_child(cx, cy, cz)).unwrap();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
node = NTreeNode::<N> {
|
||||
color: Color::average(
|
||||
children
|
||||
.iter()
|
||||
.map(|x| x.color)
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
),
|
||||
subdivided: true,
|
||||
};
|
||||
}
|
||||
structure.insert(loc, node);
|
||||
}
|
||||
}
|
||||
|
||||
Self { structure, depth }
|
||||
}
|
||||
|
||||
pub fn from_arrays(chunk: &[Color], depth: usize) -> Self
|
||||
{
|
||||
let width = N.pow(depth as u32);
|
||||
let mut structure = HashMap::new();
|
||||
let mut taken = vec![0; chunk.len()]; // Whether or not the current voxel has been added
|
||||
|
||||
// Voxel insertion/combination pass
|
||||
for (y, z) in (0..width).cartesian_product(0..width)
|
||||
{
|
||||
let mut x = 0;
|
||||
while x < width
|
||||
{
|
||||
if taken[x + y * width + z * width * width] != 0
|
||||
{
|
||||
x += taken[x + y * width + z * width * width];
|
||||
continue;
|
||||
}
|
||||
|
||||
let max_combination_depth = trailing_zeroes::<N>(x)
|
||||
.unwrap_or(depth)
|
||||
.min(trailing_zeroes::<N>(y).unwrap_or(depth))
|
||||
.min(trailing_zeroes::<N>(z).unwrap_or(depth));
|
||||
|
||||
let prev_color = chunk[x + y * width + z * width * width];
|
||||
let mut locator = NTreeLocator::<N>::from_depth_coords(x, y, z, depth);
|
||||
//let mut insertion_depth = depth;
|
||||
let mut block_width = 1;
|
||||
'depth_loop: for depth in 1..=max_combination_depth
|
||||
{
|
||||
let combination_width = N.pow(depth as u32);
|
||||
for ((sx, sy), sz) in (0..combination_width)
|
||||
.cartesian_product(0..combination_width)
|
||||
.cartesian_product(0..combination_width)
|
||||
{
|
||||
let voxel_color =
|
||||
chunk[(x + sx) + (y + sy) * width + (z + sz) * width * width];
|
||||
let voxel_taken =
|
||||
taken[(x + sx) + (y + sy) * width + (z + sz) * width * width];
|
||||
if prev_color != voxel_color || voxel_taken != 0
|
||||
{
|
||||
// Cannot merge further
|
||||
break 'depth_loop;
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, voxel in combination_width^3 block can be merged
|
||||
block_width = combination_width;
|
||||
//insertion_depth -= 1;
|
||||
locator = locator.get_parent();
|
||||
}
|
||||
|
||||
structure.insert(
|
||||
locator,
|
||||
NTreeNode {
|
||||
color: prev_color,
|
||||
subdivided: false,
|
||||
},
|
||||
);
|
||||
|
||||
// Mark as taken
|
||||
for ((sx, sy), sz) in (0..block_width)
|
||||
.cartesian_product(0..block_width)
|
||||
.cartesian_product(0..block_width)
|
||||
{
|
||||
taken[(x + sx) + (y + sy) * width + (z + sz) * width * width] = block_width;
|
||||
}
|
||||
|
||||
x += block_width;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase pass
|
||||
for d in (0..=(depth - 1)).rev()
|
||||
{
|
||||
// Iterate on blocks of depth d
|
||||
let block_count = N.pow(d as u32); // Number of such blocks along axis
|
||||
let block_width = width / block_count; // Number of such blocks along axis
|
||||
for ((bx, by), bz) in (0..(block_count))
|
||||
.cartesian_product(0..(block_count))
|
||||
.cartesian_product(0..(block_count))
|
||||
{
|
||||
// Get how was the origin voxel merged
|
||||
let merged_width = taken[(bx * block_width)
|
||||
+ (by * block_width) * width
|
||||
+ (bz * block_width) * width * width];
|
||||
|
||||
if merged_width >= block_width
|
||||
{
|
||||
// Current voxels have been merged in bigger or equal block
|
||||
continue;
|
||||
}
|
||||
|
||||
let locator = NTreeLocator::<N>::from_depth_coords(bx, by, bz, d);
|
||||
|
||||
// Otherwise, merge
|
||||
|
||||
// Children CANNOT be merged, Otherwise they would have been already
|
||||
// Compute average color
|
||||
|
||||
let colors = locator
|
||||
.iter_children()
|
||||
.map(|loc| structure.get(&loc).unwrap().color)
|
||||
.collect::<Vec<_>>();
|
||||
structure.insert(
|
||||
locator,
|
||||
NTreeNode::<N> {
|
||||
color: Color::average(&colors),
|
||||
subdivided: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
structure,
|
||||
depth: depth as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, x: usize, y: usize, z: usize, color: Color)
|
||||
{
|
||||
let mut local_x = x;
|
||||
let mut local_y = y;
|
||||
let mut local_z = z;
|
||||
|
||||
let mut size = N.pow(self.depth);
|
||||
assert!(x < size && y < size && z < size);
|
||||
|
||||
let mut current_loc = NTreeLocator::<N>::root();
|
||||
let mut current_node = *self.structure.get(¤t_loc).unwrap();
|
||||
let mut current_depth = 0;
|
||||
|
||||
loop
|
||||
{
|
||||
// Subnodes alread with correct color
|
||||
if current_node.color == color
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if current_depth == self.depth
|
||||
{
|
||||
current_node.color = color;
|
||||
self.structure.get_mut(¤t_loc).unwrap().color = color;
|
||||
break;
|
||||
}
|
||||
|
||||
if !current_node.subdivided
|
||||
{
|
||||
// Have to subdivide
|
||||
let sub_child = NTreeNode::<N> {
|
||||
color: current_node.color,
|
||||
subdivided: false,
|
||||
};
|
||||
// Set node as subdivided
|
||||
self.structure.get_mut(¤t_loc).unwrap().subdivided = true;
|
||||
current_node.subdivided = true;
|
||||
|
||||
// Insert new children
|
||||
current_loc.iter_children().for_each(|x| {
|
||||
self.structure.insert(x, sub_child);
|
||||
});
|
||||
}
|
||||
|
||||
if current_node.subdivided
|
||||
{
|
||||
size /= N;
|
||||
let child_x = local_x / size;
|
||||
let child_y = local_y / size;
|
||||
let child_z = local_z / size;
|
||||
|
||||
// Node is subdivided, descend
|
||||
current_loc = current_loc.get_child(child_x, child_y, child_z);
|
||||
current_node = *self.structure.get(¤t_loc).unwrap();
|
||||
current_depth += 1;
|
||||
|
||||
local_x -= child_x * size;
|
||||
local_y -= child_y * size;
|
||||
local_z -= child_z * size;
|
||||
}
|
||||
}
|
||||
|
||||
// Insertion has been done, travel back up to optimise
|
||||
loop
|
||||
{
|
||||
// Try to simplify, compute average
|
||||
if current_node.subdivided
|
||||
{
|
||||
let (compressable, color) = current_loc
|
||||
.iter_children()
|
||||
.map(|x| self.structure.get(&x).unwrap())
|
||||
.fold((true, None), |state, child| match state
|
||||
{
|
||||
(_, None) => (!child.subdivided, Some(child.color)),
|
||||
(b, Some(c)) => (b && !child.subdivided && c == child.color, Some(c)),
|
||||
});
|
||||
|
||||
if compressable
|
||||
{
|
||||
// Compress
|
||||
*self.structure.get_mut(¤t_loc).unwrap() = NTreeNode {
|
||||
color: color.unwrap(),
|
||||
subdivided: false,
|
||||
};
|
||||
|
||||
// Remove children
|
||||
current_loc.iter_children().for_each(|x| {
|
||||
self.structure.remove(&x).unwrap();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update average color
|
||||
let colors = current_loc
|
||||
.iter_children()
|
||||
.map(|x| self.structure.get(&x).unwrap().color)
|
||||
.collect::<Vec<_>>();
|
||||
self.structure.get_mut(¤t_loc).unwrap().color =
|
||||
Color::average(colors.as_slice());
|
||||
}
|
||||
}
|
||||
|
||||
// Travel up
|
||||
if current_depth == 0
|
||||
{
|
||||
break; // Finished
|
||||
}
|
||||
|
||||
current_loc = current_loc.get_parent();
|
||||
current_node = *self.structure.get(¤t_loc).unwrap();
|
||||
current_depth -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_gpu_rep(&self) -> (Color, Vec<GPUStructureTile<N>>, Vec<[Color; N * N * N]>)
|
||||
{
|
||||
// No root + group by child group
|
||||
let tile_count = (self.structure.len() - 1) / (N * N * N);
|
||||
if tile_count == 0
|
||||
{
|
||||
return (
|
||||
self.structure
|
||||
.get(&NTreeLocator::<N>::root())
|
||||
.unwrap()
|
||||
.color,
|
||||
vec![],
|
||||
vec![],
|
||||
);
|
||||
}
|
||||
|
||||
let mut structure_tiles = vec![GPUStructureTile::<N>::zero(); tile_count];
|
||||
let mut color_tiles = vec![[Color(0., 0., 0., 0.); N * N * N]; tile_count];
|
||||
|
||||
let mut current_tile = 0usize;
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back((NTreeLocator::<N>::root(), current_tile));
|
||||
current_tile += 1;
|
||||
while !queue.is_empty()
|
||||
{
|
||||
let (loc, dest_tile) = queue.pop_front().unwrap();
|
||||
|
||||
let children = loc
|
||||
.iter_children()
|
||||
.map(|x| (x, self.structure.get(&x).unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut structure_tile = GPUStructureTile::<N>::zero();
|
||||
let mut color_tile = [Color(0., 0., 0., 0.); N * N * N];
|
||||
|
||||
for (i, (child_loc, child)) in children.into_iter().enumerate()
|
||||
{
|
||||
structure_tile.children[i] = GPUStructureTileIndex::new(
|
||||
child.subdivided,
|
||||
child.subdivided,
|
||||
current_tile as u32,
|
||||
);
|
||||
color_tile[i] = child.color;
|
||||
|
||||
if child.subdivided
|
||||
{
|
||||
queue.push_back((child_loc, current_tile));
|
||||
current_tile += 1;
|
||||
}
|
||||
}
|
||||
|
||||
structure_tiles[dest_tile] = structure_tile;
|
||||
color_tiles[dest_tile] = color_tile;
|
||||
}
|
||||
|
||||
println!(
|
||||
"node count: {}, current_tile: {}",
|
||||
self.structure.len(),
|
||||
current_tile
|
||||
);
|
||||
(
|
||||
self.structure
|
||||
.get(&NTreeLocator::<N>::root())
|
||||
.unwrap()
|
||||
.color,
|
||||
structure_tiles,
|
||||
color_tiles,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct GPUStructureTile<const N: usize>
|
||||
where
|
||||
[(); N * N * N]:,
|
||||
{
|
||||
children: [GPUStructureTileIndex; N * N * N],
|
||||
}
|
||||
|
||||
impl<const N: usize> GPUStructureTile<N>
|
||||
where
|
||||
[(); N * N * N]:,
|
||||
{
|
||||
pub fn zero() -> Self
|
||||
{
|
||||
GPUStructureTile {
|
||||
children: [GPUStructureTileIndex::zero(); N * N * N],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct GPUStructureTileIndex(u32);
|
||||
|
||||
impl GPUStructureTileIndex
|
||||
{
|
||||
pub fn zero() -> Self
|
||||
{
|
||||
GPUStructureTileIndex(0)
|
||||
}
|
||||
|
||||
pub fn new(subdivided: bool, leaf: bool, ptr: u32) -> Self
|
||||
{
|
||||
assert_eq!((ptr >> 30), 0);
|
||||
GPUStructureTileIndex(ptr | ((leaf as u32) << 30) | ((subdivided as u32) << 31))
|
||||
}
|
||||
|
||||
pub fn index(&self) -> u32
|
||||
{
|
||||
self.0 & 0x3FFFFFFFu32
|
||||
}
|
||||
|
||||
pub fn subdivided(&self) -> bool
|
||||
{
|
||||
(self.0 >> 31) == 1
|
||||
}
|
||||
|
||||
pub fn leaf(&self) -> bool
|
||||
{
|
||||
((self.0 >> 30) & 1) == 1
|
||||
}
|
||||
}
|
||||
|
||||
impl Color
|
||||
{
|
||||
pub fn average(colors: &[Color]) -> Color
|
||||
{
|
||||
let sum = colors
|
||||
.iter()
|
||||
.copied()
|
||||
.reduce(|Color(r_a, g_a, b_a, a_a), Color(r_b, g_b, b_b, a_b)| {
|
||||
Color(r_a + r_b, g_a + g_b, b_a + b_b, a_a + a_b)
|
||||
})
|
||||
.unwrap_or(Color(0., 0., 0., 0.));
|
||||
//let len = colors.len().max(1) as f32;
|
||||
let len = colors.iter().map(|x| x.3).sum::<f32>();
|
||||
|
||||
Color(sum.0 / len, sum.1 / len, sum.2 / len, sum.3 / len)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub struct NTreeLocator<const N: usize>(usize, usize, usize);
|
||||
|
||||
impl<const N: usize> NTreeLocator<N>
|
||||
{
|
||||
pub fn root() -> Self
|
||||
{
|
||||
Self(1, 1, 1)
|
||||
}
|
||||
|
||||
pub fn get_child(&self, child_x: usize, child_y: usize, child_z: usize) -> Self
|
||||
{
|
||||
assert!(child_x < N && child_y < N && child_z < N);
|
||||
let mut new_loc_x = self.0;
|
||||
let mut new_loc_y = self.1;
|
||||
let mut new_loc_z = self.2;
|
||||
|
||||
// Shift to left three times
|
||||
new_loc_x *= N;
|
||||
new_loc_x += child_x;
|
||||
|
||||
new_loc_y *= N;
|
||||
new_loc_y += child_y;
|
||||
|
||||
new_loc_z *= N;
|
||||
new_loc_z += child_z;
|
||||
|
||||
Self(new_loc_x, new_loc_y, new_loc_z)
|
||||
}
|
||||
|
||||
pub fn from_depth_coords(x: usize, y: usize, z: usize, depth: usize) -> Self
|
||||
{
|
||||
if depth == 0
|
||||
{
|
||||
return Self::root();
|
||||
}
|
||||
let off = N.pow(depth as u32);
|
||||
Self(off + x, off + y, off + z)
|
||||
}
|
||||
|
||||
pub fn iter_children(&self) -> impl Iterator<Item = NTreeLocator<N>>
|
||||
{
|
||||
(0..N)
|
||||
.cartesian_product(0..N)
|
||||
.cartesian_product(0..N)
|
||||
.map(|((x, y), z)| self.get_child(x, y, z))
|
||||
}
|
||||
|
||||
pub fn get_parent(&self) -> Self
|
||||
{
|
||||
if self.0 == 0
|
||||
{
|
||||
*self
|
||||
}
|
||||
else
|
||||
{
|
||||
Self(self.0 / N, self.1 / N, self.2 / N)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_local_location(&self) -> (usize, usize, usize)
|
||||
{
|
||||
let mut loc = self.0;
|
||||
let z = loc % N;
|
||||
loc /= N;
|
||||
|
||||
let y = loc % N;
|
||||
loc /= N;
|
||||
|
||||
let x = loc % N;
|
||||
|
||||
(x, y, z)
|
||||
}
|
||||
|
||||
pub fn is_root(&self) -> bool
|
||||
{
|
||||
self.0 == 1
|
||||
}
|
||||
}
|
||||
|
||||
fn trailing_zeroes<const N: usize>(mut n: usize) -> Option<usize>
|
||||
{
|
||||
if n == 0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let mut i = 0;
|
||||
while n.is_multiple_of(N)
|
||||
// n % N == 0
|
||||
{
|
||||
i += 1;
|
||||
n /= N;
|
||||
}
|
||||
Some(i)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test
|
||||
{
|
||||
|
||||
use crate::voxel::trailing_zeroes;
|
||||
use itertools::Itertools;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::voxel::Color;
|
||||
use crate::voxel::NTree;
|
||||
use crate::voxel::NTreeLocator;
|
||||
|
||||
#[test]
|
||||
pub fn constant()
|
||||
{
|
||||
let color = Color(0.5, 0.3, 0.5, 1.);
|
||||
let depth = 5;
|
||||
const N: usize = 3;
|
||||
let width = N.pow(depth);
|
||||
let ntree = NTree::<N>::constant(color, depth);
|
||||
|
||||
for ((x, y), z) in (0..width)
|
||||
.cartesian_product(0..width)
|
||||
.cartesian_product(0..width)
|
||||
{
|
||||
assert_eq!(ntree.get(x, y, z), color);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn full_insert()
|
||||
{
|
||||
const DEPTH: u32 = 4;
|
||||
const N: usize = 3;
|
||||
const WIDTH: usize = N.pow(DEPTH);
|
||||
let mut ntree = NTree::<N>::constant(Color(1., 0., 0., 0.), DEPTH);
|
||||
|
||||
let mut rng = rand::rng();
|
||||
let mut storage = vec![vec![vec![Color(0., 0., 0., 0.); WIDTH]; WIDTH]; WIDTH];
|
||||
for ((x, y), z) in (0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
{
|
||||
let color = Color(rng.random(), rng.random(), rng.random(), rng.random());
|
||||
storage[x][y][z] = color;
|
||||
ntree.set(x, y, z, color);
|
||||
}
|
||||
|
||||
for ((x, y), z) in (0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
{
|
||||
let color = ntree.get(x, y, z);
|
||||
assert_eq!(storage[x][y][z], color);
|
||||
}
|
||||
println!("Total nodes {}", ntree.structure.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn full_insert_bottom_up()
|
||||
{
|
||||
const DEPTH: u32 = 4;
|
||||
const N: usize = 3;
|
||||
const WIDTH: usize = N.pow(DEPTH);
|
||||
|
||||
let mut rng = rand::rng();
|
||||
let mut storage = vec![Color(0., 0., 0., 0.); WIDTH * WIDTH * WIDTH];
|
||||
for ((x, y), z) in (0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
{
|
||||
let color = Color(rng.random(), rng.random(), rng.random(), rng.random());
|
||||
storage[x + WIDTH * y + WIDTH * WIDTH * z] = color;
|
||||
}
|
||||
let ntree = NTree::<N>::from_arrays(&storage, DEPTH as usize);
|
||||
|
||||
for ((x, y), z) in (0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
{
|
||||
let color = ntree.get(x, y, z);
|
||||
assert_eq!(storage[x + WIDTH * y + WIDTH * WIDTH * z], color);
|
||||
}
|
||||
println!("Total nodes {}", ntree.structure.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn full_insert_const()
|
||||
{
|
||||
const DEPTH: u32 = 4;
|
||||
const N: usize = 3;
|
||||
const WIDTH: usize = N.pow(DEPTH);
|
||||
const NEW_COLOR: Color = Color(0., 1., 0., 1.);
|
||||
let mut ntree = NTree::<N>::constant(Color(1., 0., 1., 0.), DEPTH);
|
||||
|
||||
for ((x, y), z) in (0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
{
|
||||
ntree.set(x, y, z, NEW_COLOR);
|
||||
}
|
||||
|
||||
for ((x, y), z) in (0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
{
|
||||
let color = ntree.get(x, y, z);
|
||||
assert_eq!(NEW_COLOR, color);
|
||||
}
|
||||
|
||||
println!("Total nodes {}", ntree.structure.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn full_insert_quantized()
|
||||
{
|
||||
const DEPTH: u32 = 4;
|
||||
const N: usize = 3;
|
||||
const WIDTH: usize = N.pow(DEPTH);
|
||||
let mut ntree = NTree::<N>::constant(Color(0., 0., 0., 0.), DEPTH);
|
||||
|
||||
let mut rng = rand::rng();
|
||||
let mut storage = vec![vec![vec![Color(0., 0., 0., 0.); WIDTH]; WIDTH]; WIDTH];
|
||||
for ((x, y), z) in (0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
{
|
||||
let nbr = rng.random::<f32>();
|
||||
let mut color = Color(0., 1., 0., 0.);
|
||||
// Only 1 percent of different color
|
||||
if nbr > 0.01
|
||||
{
|
||||
color = Color(1., 0., 0., 0.);
|
||||
}
|
||||
storage[x][y][z] = color;
|
||||
ntree.set(x, y, z, color);
|
||||
}
|
||||
|
||||
for ((x, y), z) in (0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
{
|
||||
let color = ntree.get(x, y, z);
|
||||
assert_eq!(storage[x][y][z], color);
|
||||
}
|
||||
println!("Total nodes {}", ntree.structure.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn gpu_rep_quantized()
|
||||
{
|
||||
const DEPTH: u32 = 4;
|
||||
const N: usize = 3;
|
||||
const WIDTH: usize = N.pow(DEPTH);
|
||||
let mut ntree = NTree::<N>::constant(Color(0., 0., 0., 0.), DEPTH);
|
||||
|
||||
let mut rng = rand::rng();
|
||||
let mut storage = vec![vec![vec![Color(0., 0., 0., 0.); WIDTH]; WIDTH]; WIDTH];
|
||||
for ((x, y), z) in (0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
.cartesian_product(0..WIDTH)
|
||||
{
|
||||
let nbr = rng.random::<f32>();
|
||||
let mut color = Color(0., 1., 0., 0.);
|
||||
// Only 1 percent of different color
|
||||
if nbr > 0.01
|
||||
{
|
||||
color = Color(1., 0., 0., 0.);
|
||||
}
|
||||
storage[x][y][z] = color;
|
||||
ntree.set(x, y, z, color);
|
||||
}
|
||||
|
||||
let (_color, _a, _b) = ntree.to_gpu_rep();
|
||||
|
||||
drop(_a);
|
||||
drop(_b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn tree_locator_from_depth_coords()
|
||||
{
|
||||
const N: usize = 4;
|
||||
assert_eq!(
|
||||
NTreeLocator::<N>::root(),
|
||||
NTreeLocator::<N>::from_depth_coords(1, 2, 3, 0)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
NTreeLocator::<N>::root().get_child(1, 2, 3),
|
||||
NTreeLocator::<N>::from_depth_coords(1, 2, 3, 1)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
NTreeLocator::<N>::root()
|
||||
.get_child(1, 1, 1)
|
||||
.get_child(1, 2, 3),
|
||||
NTreeLocator::<N>::from_depth_coords(4 + 1, 4 + 2, 4 + 3, 2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_zeroes_base10()
|
||||
{
|
||||
let n = 2139800000;
|
||||
assert_eq!(trailing_zeroes::<10>(n), Some(5));
|
||||
assert_eq!(trailing_zeroes::<10>(0), None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user