diff --git a/Cargo.toml b/Cargo.toml index 7b56c81..7143be7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,19 @@ authors = ["Dávid Tóth "] license = "MIT OR Apache-2.0" [features] -default = [] +default = ["dot_vox_support"] raytracing = ["dep:image", "dep:show-image"] serialization = ["dep:serde"] +dot_vox_support = ["dep:dot_vox", "dep:nalgebra"] bevy_wgpu = ["raytracing", "dep:bevy", "dep:iyes_perf_ui"] [dependencies] +num-traits = "0.2.19" serde = { version = "1.0.183", features = ["derive"], optional = true } bendy = { git = "https://github.com/davids91/bendy.git" , features = ["std", "serde"]} +dot_vox = { version = "5.1.1", optional = true } +nalgebra = { version = "0.33.0", optional = true } + # for example cpu_render image = { version = "0.25.1", optional = true } show-image = { version = "0.14.0", optional = true } diff --git a/README.md b/README.md index c31895b..5857c2d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ # Shocovox - Shady Octree Of Voxels with Ray Marching Shocovox is a Sparse Voxel Octree implementation in GPU Shader Language(s) ( hence: shady ). -A basic implementation for raycasting/ray-marching is available, GPU support through various platforms is planned. +A basic implementation for raytracing is available with GPU support! +The library uses Left handed Y up coordinate system. + +Features: +- + +Special thanks to contributors and supporters! +@nerdachse For the Albedo type and amazing support! +@DouglasDwyer My nemesis; Check out [his project](https://github.com/DouglasDwyer/octo-release) it's amazing! ( I hate him ) \ No newline at end of file diff --git a/assets/models/minecraft.vox b/assets/models/minecraft.vox new file mode 100644 index 0000000..b9d7df0 Binary files /dev/null and b/assets/models/minecraft.vox differ diff --git a/assets/models/navigate.vox b/assets/models/navigate.vox new file mode 100644 index 0000000..8c5ff8c Binary files /dev/null and b/assets/models/navigate.vox differ diff --git a/assets/models/navigate_x.vox b/assets/models/navigate_x.vox new file mode 100644 index 0000000..98ef4f5 Binary files /dev/null and b/assets/models/navigate_x.vox differ diff --git a/assets/models/navigate_y.vox b/assets/models/navigate_y.vox new file mode 100644 index 0000000..1dbab3f Binary files /dev/null and b/assets/models/navigate_y.vox differ diff --git a/assets/models/navigate_z.vox b/assets/models/navigate_z.vox new file mode 100644 index 0000000..c1451d3 Binary files /dev/null and b/assets/models/navigate_z.vox differ diff --git a/assets/shaders/viewport_render.wgsl b/assets/shaders/viewport_render.wgsl index 930c504..99eabe1 100644 --- a/assets/shaders/viewport_render.wgsl +++ b/assets/shaders/viewport_render.wgsl @@ -468,10 +468,7 @@ fn get_by_ray(ray_: Line) -> OctreeRayIntersection{ ); node_stack_i = 1; } - - var i = 0.; while(0 < node_stack_i && node_stack_i < max_depth) { - i += 1.; var current_bounds = node_stack[node_stack_i - 1].bounds; var current_node = nodes[node_stack[node_stack_i - 1].node]; //!NOTE: should be const, but then it can not be indexed dynamically var target_octant = node_stack[node_stack_i - 1].target_octant; @@ -688,6 +685,30 @@ fn update( rgb_result = result_with_lights.rgb; } + /*// +++ DEBUG +++ + // Display the xyz axes + let root_hit = cube_intersect_ray( + Cube(vec3(0.,0.,0.), f32(octreeMetaData.octree_size)), ray + ); + if root_hit.hit == true { + if root_hit. impact_hit == true { + let axes_length = f32(octreeMetaData.octree_size) / 2.; + let axes_width = f32(octreeMetaData.octree_size) / 50.; + let entry_point = point_in_ray_at_distance(ray, root_hit.impact_distance); + if entry_point.x < axes_length && entry_point.y < axes_width && entry_point.z < axes_width { + rgb_result.r = 1.; + } + if entry_point.x < axes_width && entry_point.y < axes_length && entry_point.z < axes_width { + rgb_result.g = 1.; + } + if entry_point.x < axes_width && entry_point.y < axes_width && entry_point.z < axes_length { + rgb_result.b = 1.; + } + } + + } + */// --- DEBUG --- + textureStore(output_texture, pixel_location, vec4f(rgb_result, 1.)); } diff --git a/examples/minecraft.rs b/examples/minecraft.rs new file mode 100644 index 0000000..2e93085 --- /dev/null +++ b/examples/minecraft.rs @@ -0,0 +1,159 @@ +#[cfg(feature = "bevy_wgpu")] +use shocovox_rs::octree::Octree; + +#[cfg(feature = "bevy_wgpu")] +use bevy::{prelude::*, window::WindowPlugin}; + +#[cfg(feature = "bevy_wgpu")] +use shocovox_rs::octree::{ + raytracing::{ + bevy::create_viewing_glass, ShocoVoxRenderPlugin, ShocoVoxViewingGlass, Viewport, + }, + Albedo, V3c, +}; + +#[cfg(feature = "bevy_wgpu")] +const DISPLAY_RESOLUTION: [u32; 2] = [1024, 768]; + +#[cfg(feature = "bevy_wgpu")] +fn main() { + App::new() + .insert_resource(ClearColor(Color::BLACK)) + .add_plugins(( + DefaultPlugins.set(WindowPlugin::default()), + ShocoVoxRenderPlugin { + resolution: DISPLAY_RESOLUTION, + }, + )) + .add_systems(Startup, setup) + .add_systems(Update, rotate_camera) + .add_systems(Update, handle_zoom) + .run(); +} + +#[cfg(feature = "bevy_wgpu")] +fn setup(mut commands: Commands, images: ResMut>) { + // fill octree with data + let tree; + let tree_path = "example_junk_minecraft"; + if std::path::Path::new(tree_path).exists() { + tree = Octree::::load(&tree_path).ok().unwrap(); + } else { + tree = match shocovox_rs::octree::Octree::::load_vox_file( + "assets/models/minecraft.vox", + ) { + Ok(tree_) => tree_, + Err(message) => panic!("Parsing model file failed with message: {message}"), + }; + tree.save(&tree_path).ok().unwrap(); + } + + let origin = V3c::new( + tree.get_size() as f32 * 2., + tree.get_size() as f32 / 2., + tree.get_size() as f32 * -2., + ); + commands.spawn(DomePosition { + yaw: 0., + roll: 0., + radius: tree.get_size() as f32 * 2.2, + }); + + let render_data = tree.create_bevy_view(); + let viewing_glass = create_viewing_glass( + &Viewport { + origin: V3c { + x: 0., + y: 0., + z: 0., + }, + direction: V3c { + x: 0., + y: 0., + z: -1., + }, + w_h_fov: V3c::new(10., 10., 3.), + }, + DISPLAY_RESOLUTION, + images, + ); + commands.spawn(SpriteBundle { + sprite: Sprite { + custom_size: Some(Vec2::new(1024., 768.)), + ..default() + }, + texture: viewing_glass.output_texture.clone(), + ..default() + }); + commands.spawn(Camera2dBundle::default()); + commands.insert_resource(render_data); + commands.insert_resource(viewing_glass); +} + +#[cfg(feature = "bevy_wgpu")] +#[derive(Component)] +struct DomePosition { + radius: f32, + yaw: f32, + roll: f32, +} + +#[cfg(feature = "bevy_wgpu")] +fn rotate_camera( + angles_query: Query<&mut DomePosition>, + mut viewing_glass: ResMut, +) { + let (yaw, roll) = (angles_query.single().yaw, angles_query.single().roll); + + let radius = angles_query.single().radius; + viewing_glass.viewport.origin = V3c::new( + radius / 2. + yaw.sin() * radius, + radius + roll.sin() * radius * 2., + radius / 2. + yaw.cos() * radius, + ); + viewing_glass.viewport.direction = + (V3c::unit(radius / 2.) - viewing_glass.viewport.origin).normalized(); +} + +#[cfg(feature = "bevy_wgpu")] +fn handle_zoom( + keys: Res>, + mut viewing_glass: ResMut, + mut angles_query: Query<&mut DomePosition>, +) { + const ADDITION: f32 = 0.05; + let angle_update_fn = |angle, delta| -> f32 { + let new_angle = angle + delta; + if new_angle < 360. { + new_angle + } else { + 0. + } + }; + if keys.pressed(KeyCode::ArrowUp) { + angles_query.single_mut().roll = angle_update_fn(angles_query.single().roll, ADDITION); + } + if keys.pressed(KeyCode::ArrowDown) { + angles_query.single_mut().roll = angle_update_fn(angles_query.single().roll, -ADDITION); + } + if keys.pressed(KeyCode::ArrowLeft) { + angles_query.single_mut().yaw = angle_update_fn(angles_query.single().yaw, ADDITION); + // println!("viewport: {:?}", viewing_glass.viewport); + } + if keys.pressed(KeyCode::ArrowRight) { + angles_query.single_mut().yaw = angle_update_fn(angles_query.single().yaw, -ADDITION); + // println!("viewport: {:?}", viewing_glass.viewport); + } + if keys.pressed(KeyCode::PageUp) { + angles_query.single_mut().radius *= 0.9; + } + if keys.pressed(KeyCode::PageDown) { + angles_query.single_mut().radius *= 1.1; + } +} + +#[cfg(not(feature = "bevy_wgpu"))] +fn main() { + println!("You probably forgot to enable the bevy_wgpu feature!"); + //nothing to do when the feature is not enabled +} diff --git a/src/octree/bytecode.rs b/src/octree/convert/bytecode.rs similarity index 98% rename from src/octree/bytecode.rs rename to src/octree/convert/bytecode.rs index d5aab63..afdb432 100644 --- a/src/octree/bytecode.rs +++ b/src/octree/convert/bytecode.rs @@ -1,7 +1,10 @@ use crate::object_pool::ObjectPool; -use crate::octree::types::{NodeChildren, NodeChildrenArray, NodeContent, Octree, VoxelData}; +use crate::octree::{ + types::{NodeChildren, NodeChildrenArray, NodeContent}, + Albedo, Octree, VoxelData, +}; use bendy::{ - decoding::ListDecoder, + decoding::{FromBencode, ListDecoder, Object}, encoding::{Encoder, Error as BencodeError, SingleItemEncoder, ToBencode}, }; @@ -84,9 +87,6 @@ where } } -use bendy::decoding::{FromBencode, Object}; - -use super::types::Albedo; impl FromBencode for NodeContent where T: Eq + Default + Clone + Copy + VoxelData, diff --git a/src/octree/convert/magicavoxel.rs b/src/octree/convert/magicavoxel.rs new file mode 100644 index 0000000..e54e469 --- /dev/null +++ b/src/octree/convert/magicavoxel.rs @@ -0,0 +1,339 @@ +use crate::{ + octree::{Albedo, Octree, V3c, VoxelData}, + spatial::math::{convert_coordinate, CoordinateSystemType}, +}; +use dot_vox::{Color, DotVoxData, Model, SceneNode, Size, Voxel}; +use nalgebra::Matrix3; + +impl From for Color { + fn from(color: Albedo) -> Self { + Self { + r: color.r, + g: color.g, + b: color.b, + a: color.a, + } + } +} + +impl From for Albedo { + fn from(color: Color) -> Self { + Self { + r: color.r, + g: color.g, + b: color.b, + a: color.a, + } + } +} + +impl From for V3c { + fn from(other: Voxel) -> Self { + Self { + x: other.x as i32, + y: other.y as i32, + z: other.z as i32, + } + } +} + +impl From for V3c { + fn from(other: Size) -> Self { + Self { + x: other.x as i32, + y: other.y as i32, + z: other.z as i32, + } + } +} + +impl VoxelData for Color { + fn new(albedo: Albedo, _: u32) -> Self { + albedo.into() + } + fn albedo(&self) -> Albedo { + (*self).into() + } + fn user_data(&self) -> u32 { + 0 + } + fn clear(&mut self) { + self.r = 0; + self.g = 0; + self.b = 0; + self.a = 0; + } +} + +/// Converts the given byte value to a rotation matrix +/// Rotation matrix in voxel context enables 90 degr rotations only, so the contents of the matrix is restricted to 0,1,-1 +/// Takes into consideration, that the stored matrix is row-major, while Matrix3 storage is column major +fn parse_rotation_matrix(b: u8) -> Matrix3 { + let mut result = Matrix3::::new(0, 0, 0, 0, 0, 0, 0, 0, 0); + + // decide absolute values of each row + let index_in_first_row = b & 0x3; + let index_in_second_row = ((b & (0x3 << 2)) >> 2) & 0x3; + let index_in_third_row = !(index_in_first_row ^ index_in_second_row) & 0x3; + debug_assert!(index_in_first_row < 3); + debug_assert!(index_in_second_row < 3); + debug_assert!(index_in_third_row < 3); + debug_assert!(index_in_first_row != index_in_second_row); + debug_assert!(index_in_first_row != index_in_third_row); + debug_assert!(index_in_second_row != index_in_third_row); + + // decide the sign of the values + let sign_first_row = if 0 == (b & 0x10) { 1 } else { -1 }; + let sign_second_row = if 0 == (b & 0x20) { 1 } else { -1 }; + let sign_third_row = if 0 == (b & 0x40) { 1 } else { -1 }; + + // set the values in the matrix + result.data.0[index_in_first_row as usize][0] = sign_first_row; + result.data.0[index_in_second_row as usize][1] = sign_second_row; + result.data.0[index_in_third_row as usize][2] = sign_third_row; + + result +} + +impl V3c +where + T: num_traits::Num + Clone + Copy + std::convert::From, +{ + fn clone_transformed(&self, matrix: &Matrix3) -> V3c { + V3c::new( + self.x * matrix.m11.into() + self.y * matrix.m12.into() + self.z * matrix.m13.into(), + self.x * matrix.m21.into() + self.y * matrix.m22.into() + self.z * matrix.m23.into(), + self.x * matrix.m31.into() + self.y * matrix.m32.into() + self.z * matrix.m33.into(), + ) + } +} + +fn iterate_vox_tree, &Matrix3) -> ()>( + vox_tree: &DotVoxData, + mut fun: F, +) { + let mut node_stack: Vec<(u32, V3c, Matrix3, u32)> = Vec::new(); + + match &vox_tree.scenes[0] { + SceneNode::Transform { + attributes: _, + frames: _, + child, + layer_id: _, + } => { + node_stack.push((*child, V3c::unit(0), Matrix3::identity(), 0)); + } + _ => { + panic!("The root node for a magicka voxel DAG should be a translation") + } + } + + while 0 < node_stack.len() { + let (current_node, translation, rotation, index) = *node_stack.last().unwrap(); + match &vox_tree.scenes[current_node as usize] { + SceneNode::Transform { + attributes: _, + frames, + child, + layer_id: _, + } => { + let translation = if let Some(t) = frames[0].attributes.get("_t") { + translation + + t.split(" ") + .map(|x| x.parse().expect("Not an integer!")) + .collect::>() + .into() + } else { + translation + }; + let orientation = if let Some(r) = frames[0].attributes.get("_r") { + rotation + * parse_rotation_matrix( + r.parse() + .expect("Expected valid u8 byte to parse rotation matrix"), + ) + } else { + rotation + }; + // 0 == index ==> iterate into the child of the translation + if 0 == index { + node_stack.push((*child, translation, orientation, 0)); + } else { + // 0 != index ==> remove translation and iterate into parent + node_stack.pop(); + } + } + SceneNode::Group { + attributes: _, + children, + } => { + if (index as usize) < children.len() { + node_stack.last_mut().unwrap().3 += 1; + node_stack.push((children[index as usize], translation, rotation, 0)); + } else { + node_stack.pop(); + if let Some(parent) = node_stack.last_mut() { + parent.3 += 1; + } + } + } + SceneNode::Shape { + attributes: _, + models, + } => { + for model in models { + fun( + &vox_tree.models[model.model_id as usize], + &translation, + &rotation, + ); + } + node_stack.pop(); + if let Some(parent) = node_stack.last_mut() { + parent.3 += 1; + } + } + } + } +} + +impl Octree +where + T: Default + Eq + Clone + Copy + VoxelData, +{ + pub fn load_vox_file(filename: &str) -> Result { + let vox_tree = dot_vox::load(filename)?; + + let mut min_position_lyup = V3c::::new(0, 0, 0); + let mut max_position_lyup = V3c::::new(0, 0, 0); + iterate_vox_tree(&vox_tree, |model, position, orientation| { + let model_size_half_lyup = convert_coordinate( + V3c::from(model.size).clone_transformed(orientation), + CoordinateSystemType::RZUP, + CoordinateSystemType::LYUP, + ) / 2; + + // If the index is negative, then it is calculated + // as model[size - i - 1][..][..], instead of model[i][..][..] + // So one needs to be added in every dimension where the index is below 0 + let position = convert_coordinate( + *position, + CoordinateSystemType::RZUP, + CoordinateSystemType::LYUP, + ) + V3c::new( + if model_size_half_lyup.x < 0 { -1 } else { 0 }, + if model_size_half_lyup.y < 0 { -1 } else { 0 }, + if model_size_half_lyup.z < 0 { -1 } else { 0 }, + ); + + min_position_lyup.x = min_position_lyup + .x + .min(position.x - model_size_half_lyup.x) + .min(position.x + model_size_half_lyup.x); + min_position_lyup.y = min_position_lyup + .y + .min(position.y - model_size_half_lyup.y) + .min(position.y + model_size_half_lyup.y); + min_position_lyup.z = min_position_lyup + .z + .min(position.z - model_size_half_lyup.z) + .min(position.z + model_size_half_lyup.z); + + max_position_lyup.x = max_position_lyup + .x + .max(position.x + model_size_half_lyup.x) + .max(position.x - model_size_half_lyup.x); + max_position_lyup.y = max_position_lyup + .y + .max(position.y + model_size_half_lyup.y) + .max(position.y - model_size_half_lyup.y); + max_position_lyup.z = max_position_lyup + .z + .max(position.z + model_size_half_lyup.z) + .max(position.z - model_size_half_lyup.z); + }); + max_position_lyup = max_position_lyup - min_position_lyup; + let max_dimension = max_position_lyup + .x + .max(max_position_lyup.y) + .max(max_position_lyup.z); + let max_dimension = (max_dimension as f32).log2().ceil() as u32; + let max_dimension = 2_u32.pow(max_dimension); + let mut shocovox_octree = Octree::::new(max_dimension).ok().unwrap(); + iterate_vox_tree(&vox_tree, |model, position, orientation| { + let model_size_lyup = convert_coordinate( + V3c::from(model.size).clone_transformed(orientation), + CoordinateSystemType::RZUP, + CoordinateSystemType::LYUP, + ); + let position = V3c::from(*position); + let position_lyup = convert_coordinate( + position, + CoordinateSystemType::RZUP, + CoordinateSystemType::LYUP, + ); + + let current_position = position_lyup - min_position_lyup - (model_size_lyup / 2) + + V3c::new( + if model_size_lyup.x < 0 { -1 } else { 0 }, + if model_size_lyup.y < 0 { -1 } else { 0 }, + if model_size_lyup.z < 0 { -1 } else { 0 }, + ); + + let mut vmin = V3c::unit(max_dimension as u32); + let mut vmax = V3c::unit(0u32); + for voxel in &model.voxels { + let voxel_position = convert_coordinate( + V3c::from(*voxel).clone_transformed(orientation), + CoordinateSystemType::RZUP, + CoordinateSystemType::LYUP, + ); + let cpos = current_position + voxel_position; + if cpos.length() < vmin.length() { + vmin = cpos.into(); + } + if cpos.length() > vmax.length() { + vmax = cpos.into(); + } + + shocovox_octree + .insert( + &V3c::::from(current_position + voxel_position.into()), + T::new(vox_tree.palette[voxel.i as usize].into(), 0), + ) + .ok() + .unwrap(); + } + }); + Ok(shocovox_octree) + } +} + +#[cfg(test)] +mod octree_tests { + use super::parse_rotation_matrix; + use nalgebra::Matrix3; + + #[test] + fn test_matrix_parse() { + let test_matrix = Matrix3::::new(1, 0, 0, 0, 1, 0, 0, 0, 1); + assert!(test_matrix.m11 == 1); + assert!(test_matrix.m22 == 1); + assert!(test_matrix.m33 == 1); + + let parsed_matrix = parse_rotation_matrix(4); + assert!(parse_rotation_matrix(4) == test_matrix); + assert!(parsed_matrix.m11 == 1); + assert!(parsed_matrix.m22 == 1); + assert!(parsed_matrix.m33 == 1); + + // https://github.com/ephtracy/voxel-model/blob/master/MagicaVoxel-file-format-vox-extension.txt + let example = Matrix3::::new(0, 1, 0, 0, 0, -1, -1, 0, 0); + let parsed_example = + parse_rotation_matrix((1 << 0) | (2 << 2) | (0 << 4) | (1 << 5) | (1 << 6)); + assert!(parsed_example == example); + assert!(parsed_example.m12 == 1); + assert!(parsed_example.m23 == -1); + assert!(parsed_example.m31 == -1); + } +} diff --git a/src/octree/convert/mod.rs b/src/octree/convert/mod.rs new file mode 100644 index 0000000..3b68ae2 --- /dev/null +++ b/src/octree/convert/mod.rs @@ -0,0 +1,4 @@ +mod bytecode; + +#[cfg(feature = "dot_vox_support")] +mod magicavoxel; diff --git a/src/octree/detail.rs b/src/octree/detail.rs index df06921..e154595 100644 --- a/src/octree/detail.rs +++ b/src/octree/detail.rs @@ -1,6 +1,8 @@ use crate::object_pool::empty_marker; -use crate::octree::types::{NodeChildren, NodeChildrenArray, NodeContent, Octree, VoxelData}; -use crate::octree::{hash_region, Cube, V3c}; +use crate::octree::{ + types::{Albedo, NodeChildren, NodeChildrenArray, NodeContent, Octree, VoxelData}, + {hash_region, Cube, V3c}, +}; use crate::spatial::math::{octant_bitmask, set_occupancy_in_bitmap_64bits}; ///#################################################################################### @@ -23,6 +25,45 @@ pub(in crate::octree) fn child_octant_for(bounds: &Cube, position: &V3c) -> hash_region(&(*position - bounds.min_position), bounds.size) } +///#################################################################################### +/// Type implements +///#################################################################################### +impl VoxelData for Albedo { + fn new(color: Albedo, _user_data: u32) -> Self { + color + } + + fn albedo(&self) -> Albedo { + *self + } + + fn user_data(&self) -> u32 { + 0u32 + } + + fn clear(&mut self) { + self.r = 0; + self.g = 0; + self.b = 0; + self.a = 0; + } +} + +impl From for Albedo { + fn from(value: u32) -> Self { + let a = (value & 0x000000FF) as u8; + let b = ((value & 0x0000FF00) >> 8) as u8; + let g = ((value & 0x00FF0000) >> 16) as u8; + let r = ((value & 0xFF000000) >> 24) as u8; + + Albedo::default() + .with_red(r) + .with_green(g) + .with_blue(b) + .with_alpha(a) + } +} + ///#################################################################################### /// NodeChildren ///#################################################################################### @@ -236,7 +277,10 @@ where pub(crate) const ROOT_NODE_KEY: u32 = 0; } -impl Octree { +impl Octree +where + T: Default + Clone + VoxelData, +{ pub(in crate::octree) fn mat_index(bounds: &Cube, position: &V3c) -> V3c { // --> In case the smallest possible node the contained matrix of voxels // starts at bounds min_position and ends in min_position + (DIM,DIM,DIM) @@ -275,6 +319,7 @@ impl Octree { &mut self, content: Box<[[[T; DIM]; DIM]; DIM]>, ) -> [u32; 8] { + println!("Making uniform children!"); // Create new children leaf nodes based on the provided content let occupancy_bitmap = Self::bruteforce_occupancy_bitmask(&content); let children = [ diff --git a/src/octree/mod.rs b/src/octree/mod.rs index f316f76..d71e8d6 100644 --- a/src/octree/mod.rs +++ b/src/octree/mod.rs @@ -1,9 +1,10 @@ -pub mod bytecode; -pub mod detail; -pub mod tests; pub mod types; pub mod update; +mod convert; +mod detail; +mod tests; + #[cfg(feature = "raytracing")] pub mod raytracing; @@ -33,7 +34,7 @@ where } /// saves the data structure to the given file path - pub fn save(&mut self, path: &str) -> Result<(), std::io::Error> { + pub fn save(&self, path: &str) -> Result<(), std::io::Error> { use std::fs::File; use std::io::Write; let mut file = File::create(path)?; diff --git a/src/octree/types.rs b/src/octree/types.rs index a59091d..cfcfd6b 100644 --- a/src/octree/types.rs +++ b/src/octree/types.rs @@ -55,48 +55,15 @@ pub trait VoxelData { fn clear(&mut self); } -impl VoxelData for Albedo { - fn new(color: Albedo, _user_data: u32) -> Self { - color - } - - fn albedo(&self) -> Albedo { - *self - } - - fn user_data(&self) -> u32 { - 0u32 - } - - fn clear(&mut self) { - self.r = 0; - self.r = 0; - self.b = 0; - self.a = 0; - } -} - -impl From for Albedo { - fn from(value: u32) -> Self { - let a = (value & 0x000000FF) as u8; - let b = ((value & 0x0000FF00) >> 8) as u8; - let g = ((value & 0x00FF0000) >> 16) as u8; - let r = ((value & 0xFF000000) >> 24) as u8; - - Albedo::default() - .with_red(r) - .with_green(g) - .with_blue(b) - .with_alpha(a) - } -} - /// Sparse Octree of Nodes, where each node contains a brick of voxels. /// A Brick is a 3 dimensional matrix, each element of it containing a voxel. /// A Brick can be indexed directly, as opposed to the octree which is essentially a /// tree-graph where each node has 8 children. #[cfg_attr(feature = "serialization", derive(Serialize))] -pub struct Octree { +pub struct Octree +where + T: Default + Clone + VoxelData, +{ pub auto_simplify: bool, pub(in crate::octree) octree_size: u32, pub(in crate::octree) nodes: ObjectPool>, diff --git a/src/octree/update.rs b/src/octree/update.rs index b938066..5e76ebe 100644 --- a/src/octree/update.rs +++ b/src/octree/update.rs @@ -13,7 +13,7 @@ use crate::spatial::{ impl Octree where - T: Default + Eq + Clone + Copy + VoxelData, + T: Default + PartialEq + Clone + Copy + VoxelData, { /// Inserts the given data into the octree into the intended voxel position pub fn insert(&mut self, position: &V3c, data: T) -> Result<(), OctreeError> { diff --git a/src/spatial/math/mod.rs b/src/spatial/math/mod.rs index b4219da..5a8e3af 100644 --- a/src/spatial/math/mod.rs +++ b/src/spatial/math/mod.rs @@ -2,6 +2,7 @@ mod tests; pub mod vector; use crate::spatial::math::vector::V3c; +use std::ops::Neg; ///#################################################################################### /// Octant @@ -100,3 +101,41 @@ pub(crate) fn set_occupancy_in_bitmap_64bits( pub(crate) fn octant_bitmask(octant: u8) -> u8 { 0x01 << octant } + +#[cfg(feature = "dot_vox_support")] +pub(crate) enum CoordinateSystemType { + LZUP, // Left handed Z Up + LYUP, // Left handed Y Up + RZUP, // Right handed Z Up + RYUP, // Right handed Y Up +} + +#[cfg(feature = "dot_vox_support")] +pub(crate) fn convert_coordinate>( + c: V3c, + src_type: CoordinateSystemType, + dst_type: CoordinateSystemType, +) -> V3c { + match (src_type, dst_type) { + (CoordinateSystemType::LZUP, CoordinateSystemType::LZUP) => c, + (CoordinateSystemType::LYUP, CoordinateSystemType::LYUP) => c, + (CoordinateSystemType::RZUP, CoordinateSystemType::RZUP) => c, + (CoordinateSystemType::RYUP, CoordinateSystemType::RYUP) => c, + + (CoordinateSystemType::LYUP, CoordinateSystemType::RYUP) + | (CoordinateSystemType::RYUP, CoordinateSystemType::LYUP) => V3c::new(c.x, c.y, -c.z), + + (CoordinateSystemType::LZUP, CoordinateSystemType::RZUP) + | (CoordinateSystemType::RZUP, CoordinateSystemType::LZUP) => V3c::new(c.x, -c.y, c.z), + + (CoordinateSystemType::LYUP, CoordinateSystemType::LZUP) + | (CoordinateSystemType::RYUP, CoordinateSystemType::RZUP) => V3c::new(c.x, -c.z, c.y), + (CoordinateSystemType::LZUP, CoordinateSystemType::LYUP) + | (CoordinateSystemType::RZUP, CoordinateSystemType::RYUP) => V3c::new(c.x, c.z, -c.y), + + (CoordinateSystemType::LYUP, CoordinateSystemType::RZUP) + | (CoordinateSystemType::RZUP, CoordinateSystemType::LYUP) + | (CoordinateSystemType::RYUP, CoordinateSystemType::LZUP) + | (CoordinateSystemType::LZUP, CoordinateSystemType::RYUP) => V3c::new(c.x, c.z, c.y), + } +} diff --git a/src/spatial/math/tests.rs b/src/spatial/math/tests.rs index 620be90..be734f0 100644 --- a/src/spatial/math/tests.rs +++ b/src/spatial/math/tests.rs @@ -84,3 +84,51 @@ mod wgpu_tests { assert_eq!(value, original_value); } } + +#[cfg(test)] +#[cfg(feature = "dot_vox_support")] +mod dot_vox_tests { + + use crate::octree::V3c; + use crate::spatial::math::convert_coordinate; + use crate::spatial::math::CoordinateSystemType; + + #[test] + fn test_coordinate_conversion() { + assert_eq!( + V3c::new(1., 2., 3.), + convert_coordinate( + V3c::new(1., 2., 3.), + CoordinateSystemType::RZUP, + CoordinateSystemType::RZUP, + ), + ); + + assert_eq!( + V3c::new(1., 3., 2.), + convert_coordinate( + V3c::new(1., 2., 3.), + CoordinateSystemType::LZUP, + CoordinateSystemType::RYUP, + ), + ); + + assert_eq!( + V3c::new(1., 3., -2.), + convert_coordinate( + V3c::new(1., 2., 3.), + CoordinateSystemType::RZUP, + CoordinateSystemType::RYUP, + ), + ); + + assert_eq!( + V3c::new(1., 2., -3.), + convert_coordinate( + V3c::new(1., 2., 3.), + CoordinateSystemType::LYUP, + CoordinateSystemType::RYUP, + ), + ); + } +} diff --git a/src/spatial/math/vector.rs b/src/spatial/math/vector.rs index 66bc952..f9e087b 100644 --- a/src/spatial/math/vector.rs +++ b/src/spatial/math/vector.rs @@ -25,6 +25,18 @@ impl V3c { } } +impl V3c +where + T: num_traits::Signed + Clone, +{ + pub fn abs(&mut self) -> V3c { + self.x = self.x.abs(); + self.y = self.y.abs(); + self.z = self.z.abs(); + self.clone() + } +} + impl V3c { pub fn length(&self) -> f32 { ((self.x * self.x) + (self.y * self.y) + (self.z * self.z)).sqrt() @@ -41,6 +53,12 @@ impl V3c { } } +impl V3c { + pub fn length(&self) -> f32 { + (((self.x * self.x) + (self.y * self.y) + (self.z * self.z)) as f32).sqrt() + } +} + impl V3c { pub fn length(&self) -> f32 { (((self.x * self.x) + (self.y * self.y) + (self.z * self.z)) as f32).sqrt() @@ -236,6 +254,22 @@ impl From> for V3c { } } +impl From> for V3c { + fn from(vec: V3c) -> V3c { + { + V3c::new(vec.x as u32, vec.y as u32, vec.z as u32) + } + } +} + +impl From> for V3c { + fn from(vec: V3c) -> V3c { + { + V3c::new(vec.x as u32, vec.y as u32, vec.z as u32) + } + } +} + impl From> for V3c { fn from(vec: V3c) -> V3c { { @@ -248,18 +282,18 @@ impl From> for V3c { } } -impl From> for V3c { - fn from(vec: V3c) -> V3c { +impl From> for V3c { + fn from(vec: Vec) -> V3c { { - V3c::new(vec.x as i32, vec.y as i32, vec.z as i32) + V3c::new(vec[0], vec[1], vec[2]) } } } -impl From> for V3c { - fn from(vec: V3c) -> V3c { +impl From> for V3c { + fn from(vec: V3c) -> V3c { { - V3c::new(vec.x as u32, vec.y as u32, vec.z as u32) + V3c::new(vec.x as i32, vec.y as i32, vec.z as i32) } } }