refactor pass 1: split into two main files, octree and intersectioins
This commit is contained in:
parent
dac1a7a666
commit
e0be55ddcc
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"./Cargo.toml",
|
||||
"./Cargo.toml"
|
||||
],
|
||||
"rust-analyzer.cargo.target": "wasm32-unknown-unknown"
|
||||
|
|
|
@ -2,10 +2,10 @@ use bevy::app::*;
|
|||
use bevy::prelude::*;
|
||||
use bevy::window::WindowResolution;
|
||||
use bevy::DefaultPlugins;
|
||||
pub mod oct_tree;
|
||||
mod orbit_camera;
|
||||
mod vvum;
|
||||
use vvum::mesh_plugin;
|
||||
mod vvlib;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
|
|
1543
src/oct_tree.rs
1543
src/oct_tree.rs
File diff suppressed because it is too large
Load diff
779
src/vvlib/intersections.rs
Normal file
779
src/vvlib/intersections.rs
Normal file
|
@ -0,0 +1,779 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use bevy::{
|
||||
math::{Mat3, Quat, Vec3},
|
||||
transform::{components::Transform, TransformPoint as _},
|
||||
};
|
||||
|
||||
use crate::vvlib::octtree::Path;
|
||||
|
||||
use super::{
|
||||
constants,
|
||||
obb::OBB,
|
||||
octtree::{OctTree, TreeNode},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct VectorPair {
|
||||
global: Vec3,
|
||||
local: Vec3,
|
||||
}
|
||||
impl VectorPair {
|
||||
pub fn new(global: Vec3, local: Vec3) -> VectorPair {
|
||||
VectorPair {
|
||||
global: global,
|
||||
local: local,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct OctTreeCollisionPoint {
|
||||
pos: VectorPair,
|
||||
pub(crate) path: Path,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RayOctTreeIntersection {
|
||||
pub(crate) hitpoint_local: Vec3,
|
||||
pub(crate) hitpoint_global: Vec3,
|
||||
pub(crate) local_normal: Vec3,
|
||||
pub(crate) global_normal: Vec3,
|
||||
}
|
||||
|
||||
pub fn octtree_ray_overlap<T: Clone>(
|
||||
oct: &OctTree<T>,
|
||||
transform: &OBB,
|
||||
ray_origin: Vec3,
|
||||
ray_normal: Vec3,
|
||||
) -> Option<RayOctTreeIntersection> {
|
||||
let x = ray_octtree_visitor(
|
||||
&oct.root, transform, ray_origin, ray_normal, oct.center, oct.size,
|
||||
);
|
||||
if x.is_some() {
|
||||
let x = x.unwrap();
|
||||
let matrix = transform.as_transform().compute_matrix();
|
||||
let local_hitpoint = matrix.transform_point3(x.global);
|
||||
let global_normal = matrix.inverse().transform_vector3(x.local);
|
||||
let result = RayOctTreeIntersection {
|
||||
hitpoint_local: local_hitpoint,
|
||||
hitpoint_global: x.global,
|
||||
local_normal: x.local,
|
||||
global_normal,
|
||||
};
|
||||
return Some(result);
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn ray_octtree_visitor<T: Clone>(
|
||||
node: &TreeNode<T>,
|
||||
transform: &OBB,
|
||||
ray_origin: Vec3,
|
||||
ray_normal: Vec3,
|
||||
position: Vec3,
|
||||
size: usize,
|
||||
) -> Option<VectorPair> {
|
||||
if node.is_branch() {
|
||||
let children = node.branch_contents_reference();
|
||||
if children.is_none() {
|
||||
return None;
|
||||
}
|
||||
let children = children.unwrap();
|
||||
let mut elements: Vec<(Vec3, usize, &TreeNode<T>)> = Vec::new();
|
||||
for (index, element) in children.iter().enumerate() {
|
||||
if !element.is_empty() {
|
||||
let (sub_position, sub_size) = constants::sub_region(position, size, index as i32);
|
||||
elements.push((sub_position, sub_size, element));
|
||||
}
|
||||
}
|
||||
elements.sort_by(|f, s| {
|
||||
let first_transformed = transform
|
||||
.as_transform()
|
||||
.compute_matrix()
|
||||
.transform_point(f.0);
|
||||
let second_transformed = transform
|
||||
.as_transform()
|
||||
.compute_matrix()
|
||||
.transform_point(s.0);
|
||||
let fdist = first_transformed.distance(ray_origin);
|
||||
let sdist = second_transformed.distance(ray_origin);
|
||||
fdist.total_cmp(&sdist)
|
||||
});
|
||||
for element in elements {
|
||||
let x = ray_octtree_visitor(
|
||||
element.2, transform, ray_origin, ray_normal, element.0, element.1,
|
||||
);
|
||||
if x.is_some() {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
} else if node.is_leaf() {
|
||||
let mut node_bounds = transform.clone();
|
||||
let position = transform
|
||||
.as_transform()
|
||||
.compute_matrix()
|
||||
.transform_point(position);
|
||||
node_bounds.position = position;
|
||||
node_bounds.half_size = node_bounds.half_size * size as f32;
|
||||
|
||||
let x = ray_obb_intersection(ray_origin, ray_normal, &node_bounds);
|
||||
if x.is_some() {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
//v nothing could be found, failing out so other searches can continue
|
||||
None
|
||||
}
|
||||
|
||||
pub fn octtree_overlap<T: Clone>(
|
||||
first_oct: &OctTree<T>,
|
||||
first_trans: &OBB,
|
||||
second_oct: &OctTree<T>,
|
||||
second_trans: &OBB,
|
||||
) -> Option<Vec<(OctTreeCollisionPoint, OctTreeCollisionPoint)>> {
|
||||
let first = obb_to_global_sphere(first_oct.center, first_oct.size, first_trans);
|
||||
let second = obb_to_global_sphere(second_oct.center, second_oct.size, second_trans);
|
||||
let early_out = sphere_sphere_overlap(first.0, first.1, second.0, second.1);
|
||||
if early_out.is_none() {
|
||||
return None;
|
||||
}
|
||||
let mut x = Vec::new();
|
||||
visitor_octtree_x_octtree(
|
||||
&first_oct.root,
|
||||
first_oct.center,
|
||||
first_oct.size,
|
||||
first_trans,
|
||||
&second_oct.root,
|
||||
second_oct.center,
|
||||
second_oct.size,
|
||||
second_trans,
|
||||
&mut x,
|
||||
);
|
||||
if x.is_empty() {
|
||||
return None;
|
||||
} else {
|
||||
return Some(x);
|
||||
}
|
||||
}
|
||||
|
||||
fn visitor_octtree_x_octtree<T: Clone>(
|
||||
first_node: &TreeNode<T>,
|
||||
first_pos: Vec3,
|
||||
first_size: usize,
|
||||
first_trans: &OBB,
|
||||
second_node: &TreeNode<T>,
|
||||
second_pos: Vec3,
|
||||
second_size: usize,
|
||||
second_trans: &OBB,
|
||||
results: &mut Vec<(OctTreeCollisionPoint, OctTreeCollisionPoint)>,
|
||||
) {
|
||||
if second_size > first_size {
|
||||
// make second smaller till they match, one recursion at a time
|
||||
let second_el = second_node.branch_contents_reference();
|
||||
if second_el.is_none() {
|
||||
return; // we're done in this subsector of the tree
|
||||
}
|
||||
for (second_count, second_iter) in second_el.unwrap().iter().enumerate() {
|
||||
let (second_center, second_bound) =
|
||||
constants::sub_region(second_pos, second_size, second_count as i32);
|
||||
visitor_octtree_x_octtree(
|
||||
first_node,
|
||||
first_pos,
|
||||
first_size,
|
||||
first_trans,
|
||||
second_iter,
|
||||
second_center,
|
||||
second_bound,
|
||||
second_trans,
|
||||
results,
|
||||
);
|
||||
}
|
||||
} else if first_size > second_size {
|
||||
// make first smaller till they match, one recursion at a time
|
||||
let first_el = first_node.branch_contents_reference();
|
||||
if first_el.is_none() {
|
||||
return; // we're done in this subsector of the tree
|
||||
}
|
||||
for (first_count, first_iter) in first_el.unwrap().iter().enumerate() {
|
||||
let (first_center, first_bound) =
|
||||
constants::sub_region(first_pos, first_size, first_count as i32);
|
||||
visitor_octtree_x_octtree(
|
||||
first_iter,
|
||||
first_center,
|
||||
first_bound,
|
||||
first_trans,
|
||||
second_node,
|
||||
second_pos,
|
||||
second_size,
|
||||
second_trans,
|
||||
results,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// special case for roots being leaves
|
||||
if first_node.is_leaf() && second_node.is_leaf() {
|
||||
// do an intersection on them with OBBxOBB
|
||||
let x = first_trans.as_transform().transform_point(first_pos);
|
||||
let y = first_trans.half_size * first_size as f32;
|
||||
let mut obb1 = first_trans.clone();
|
||||
obb1.half_size = y;
|
||||
obb1.position = x;
|
||||
|
||||
let a = second_trans.as_transform().transform_point(second_pos);
|
||||
let b = second_trans.half_size * second_size as f32;
|
||||
let mut obb2 = second_trans.clone();
|
||||
obb2.half_size = b;
|
||||
obb2.position = a;
|
||||
let res = obb_obb_overlap(&obb1, &obb2);
|
||||
if res {
|
||||
// well,
|
||||
let half1 = OctTreeCollisionPoint {
|
||||
pos: VectorPair::new(obb1.position, first_pos),
|
||||
path: Default::default(),
|
||||
};
|
||||
let half2 = OctTreeCollisionPoint {
|
||||
pos: VectorPair::new(obb2.position, second_pos),
|
||||
path: Default::default(),
|
||||
};
|
||||
results.push((half1, half2));
|
||||
}
|
||||
}
|
||||
// check each element in each side of it and collide, then recurse
|
||||
let first_el = first_node.branch_contents_reference();
|
||||
let second_el = second_node.branch_contents_reference();
|
||||
if first_el.is_some() && second_el.is_some() {
|
||||
for (first_count, first_iter) in first_el.unwrap().iter().enumerate() {
|
||||
for (second_count, second_iter) in second_el.unwrap().iter().enumerate() {
|
||||
let (first_center, first_bound) =
|
||||
constants::sub_region(first_pos, first_size, first_count as i32);
|
||||
let (second_center, second_bound) =
|
||||
constants::sub_region(second_pos, second_size, second_count as i32);
|
||||
if first_iter.is_branch() && second_iter.is_branch() {
|
||||
let (first_sa_center, first_sa_radius) =
|
||||
obb_to_global_sphere(first_center, first_bound, first_trans);
|
||||
let (second_sa_center, second_sa_radius) =
|
||||
obb_to_global_sphere(second_center, second_bound, second_trans);
|
||||
let res = sphere_sphere_overlap(
|
||||
first_sa_center,
|
||||
first_sa_radius,
|
||||
second_sa_center,
|
||||
second_sa_radius,
|
||||
);
|
||||
if res.is_some() {
|
||||
visitor_octtree_x_octtree(
|
||||
first_iter,
|
||||
first_center,
|
||||
first_bound,
|
||||
first_trans,
|
||||
second_iter,
|
||||
second_center,
|
||||
second_bound,
|
||||
second_trans,
|
||||
results,
|
||||
);
|
||||
}
|
||||
} else if first_iter.is_leaf() && second_iter.is_leaf() {
|
||||
// do an intersection on them with OBBxOBB
|
||||
let mut obb1 = first_trans.clone();
|
||||
obb1.position = first_trans.as_transform().transform_point(first_center);
|
||||
|
||||
let mut obb2 = second_trans.clone();
|
||||
obb2.position = second_trans.as_transform().transform_point(second_center);
|
||||
|
||||
let res = obb_obb_overlap(&obb1, &obb2);
|
||||
if res {
|
||||
println!("adding collision");
|
||||
// well,
|
||||
let half1 = OctTreeCollisionPoint {
|
||||
pos: VectorPair::new(obb1.position, first_center),
|
||||
path: Default::default(),
|
||||
};
|
||||
let half2 = OctTreeCollisionPoint {
|
||||
pos: VectorPair::new(obb2.position, second_center),
|
||||
path: Default::default(),
|
||||
};
|
||||
results.push((half1, half2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collision_test<T: Clone>(
|
||||
_structure1: OctTree<T>,
|
||||
_context1: Transform,
|
||||
_structure2: OctTree<T>,
|
||||
_context2: Transform,
|
||||
) -> Option<Vec<Vec3>> {
|
||||
// TODO: actual function
|
||||
None
|
||||
}
|
||||
|
||||
pub fn aa_bounds_contains(center: Vec3, size: usize, subject: Vec3) -> bool {
|
||||
let fsize = size as f32;
|
||||
let min = center - Vec3::splat(fsize as f32 / 2.);
|
||||
let max = center + Vec3::splat(fsize as f32 / 2.);
|
||||
return min.x <= subject.x
|
||||
&& subject.x <= max.x
|
||||
&& min.y <= subject.y
|
||||
&& subject.y <= max.y
|
||||
&& min.z <= subject.z
|
||||
&& subject.z <= max.z;
|
||||
}
|
||||
|
||||
pub fn sphere_sphere_overlap(
|
||||
center1: Vec3,
|
||||
radius1: f32,
|
||||
center2: Vec3,
|
||||
radius2: f32,
|
||||
) -> Option<Vec3> {
|
||||
if center1.distance(center2) < radius1 + radius2 {
|
||||
return Some((center1 + center2) / 2.);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn ray_obb_intersection(
|
||||
ray_origin: Vec3,
|
||||
ray_direction_normalized: Vec3,
|
||||
bounds: &OBB,
|
||||
) -> Option<VectorPair> {
|
||||
let transform: Transform = Transform::from_translation(bounds.position)
|
||||
.with_rotation(bounds.orientation)
|
||||
.with_scale(bounds.half_size * 2.);
|
||||
let matrix = transform.compute_matrix().inverse();
|
||||
let ro_trans: Vec3 = matrix.transform_point3(ray_origin);
|
||||
let rd_trans: Vec3 = matrix.transform_vector3(ray_direction_normalized); // normalizing?
|
||||
let passthru =
|
||||
distance_to_ray_bounds_intersection(ro_trans, rd_trans, Vec3::ZERO, Vec3::splat(0.5));
|
||||
if passthru.is_some() {
|
||||
let hitpoint = ray_origin + (ray_direction_normalized * passthru.unwrap());
|
||||
let mut dist_array: [(Vec3, f32); 6] = Default::default();
|
||||
let mut min_dist: usize = 8;
|
||||
for (index, norm) in crate::vvlib::constants::cardinal_directions::ORDERED
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let pos = norm.clone() * bounds.half_size * 2.;
|
||||
let pos = pos + bounds.position;
|
||||
let pos = matrix.transform_vector3(pos);
|
||||
let dist = pos.distance(hitpoint);
|
||||
dist_array[index] = (norm.clone(), dist);
|
||||
if index == 0 {
|
||||
min_dist = index;
|
||||
} else if dist_array[min_dist].1 > dist {
|
||||
min_dist = index;
|
||||
}
|
||||
}
|
||||
let normal = dist_array[min_dist].0;
|
||||
return Some(VectorPair {
|
||||
global: hitpoint,
|
||||
local: normal,
|
||||
});
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn distance_to_ray_bounds_intersection(
|
||||
ray_origin: Vec3,
|
||||
ray_direction_normalized: Vec3,
|
||||
bound_center: Vec3,
|
||||
bound_half_size: Vec3,
|
||||
) -> Option<f32> {
|
||||
let bound_min: Vec3 = bound_center - bound_half_size;
|
||||
let bound_max: Vec3 = bound_center + bound_half_size;
|
||||
let tmin: Vec3 = (bound_min - ray_origin) / ray_direction_normalized;
|
||||
let tmax: Vec3 = (bound_max - ray_origin) / ray_direction_normalized;
|
||||
let sc = tmin.min(tmax);
|
||||
let sf = tmin.max(tmax);
|
||||
let t0: f32 = sc.x.max(sc.y).max(sc.z);
|
||||
let t1: f32 = sf.x.min(sf.y).min(sf.z);
|
||||
|
||||
if !(t0 <= t1 && t1 > 0.0) {
|
||||
return None;
|
||||
}
|
||||
return Some(t0);
|
||||
}
|
||||
|
||||
pub fn ray_sphere_intersection(
|
||||
ray_origin: Vec3,
|
||||
ray_direction_normalized: Vec3,
|
||||
sphere_center: Vec3,
|
||||
sphere_radius: f32,
|
||||
) -> Option<Vec3> {
|
||||
let e = sphere_center - ray_origin;
|
||||
let rsq = sphere_radius * sphere_radius;
|
||||
let esq = e.length() * e.length();
|
||||
let a = e.dot(ray_direction_normalized);
|
||||
let bsq = esq - (a * a);
|
||||
let f = (((rsq) - bsq).abs()).sqrt();
|
||||
let mut t = a - f;
|
||||
|
||||
if rsq - (esq - a * a) < 0.0 {
|
||||
return None;
|
||||
} else if esq < rsq {
|
||||
t = a + f;
|
||||
}
|
||||
|
||||
return Some(ray_origin + (ray_direction_normalized * t));
|
||||
}
|
||||
|
||||
pub fn ray_aabb_intersection(
|
||||
ray_origin: Vec3,
|
||||
ray_direction_normalized: Vec3,
|
||||
bound_center: Vec3,
|
||||
bound_half_size: Vec3,
|
||||
) -> Option<VectorPair> {
|
||||
let passthru = distance_to_ray_bounds_intersection(
|
||||
ray_origin,
|
||||
ray_direction_normalized,
|
||||
bound_center,
|
||||
bound_half_size,
|
||||
);
|
||||
if passthru.is_some() {
|
||||
let hitpoint = ray_origin + (ray_direction_normalized * passthru.unwrap());
|
||||
let mut dist_array: [(Vec3, f32); 6] = Default::default();
|
||||
let mut min_dist: usize = 8;
|
||||
for (index, norm) in crate::vvlib::constants::cardinal_directions::ORDERED
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
let pos = norm.clone() * bound_half_size * 2.;
|
||||
let pos = pos + bound_center;
|
||||
let dist = pos.distance(hitpoint);
|
||||
dist_array[index] = (norm.clone(), dist);
|
||||
if index == 0 {
|
||||
min_dist = index;
|
||||
} else if dist_array[min_dist].1 > dist {
|
||||
min_dist = index;
|
||||
}
|
||||
}
|
||||
let normal = dist_array[min_dist].0;
|
||||
return Some(VectorPair {
|
||||
global: hitpoint,
|
||||
local: normal,
|
||||
});
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sphere_obb_intersection(
|
||||
sphere_center: Vec3,
|
||||
sphere_radius: f32,
|
||||
bounds: &OBB,
|
||||
) -> Option<Vec3> {
|
||||
let mut closest_point = bounds.position;
|
||||
{
|
||||
// find closest point
|
||||
let matrix = Mat3::from_quat(bounds.orientation);
|
||||
let dir = sphere_center - bounds.position;
|
||||
let cols = matrix.to_cols_array();
|
||||
let obb_size = bounds.half_size.to_array();
|
||||
|
||||
for i in 0..3 {
|
||||
let axis = Vec3::new(cols[i * 3], cols[i * 3 + 1], cols[i * 3 + 2]);
|
||||
let mut distance = dir.dot(axis);
|
||||
|
||||
if distance > obb_size[i] {
|
||||
distance = obb_size[i];
|
||||
}
|
||||
|
||||
if distance < -obb_size[i] {
|
||||
distance = -obb_size[i];
|
||||
}
|
||||
closest_point += axis * distance;
|
||||
}
|
||||
}
|
||||
|
||||
let mut distsq = (sphere_center - closest_point).length();
|
||||
distsq *= distsq;
|
||||
let radiussq = sphere_radius * sphere_radius;
|
||||
if distsq < radiussq {
|
||||
return Some(closest_point);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn obb_to_global_sphere(local_pos: Vec3, size: usize, trans: &OBB) -> (Vec3, f32) {
|
||||
//let local_pos = trans.orientation.mul_vec3(local_pos);
|
||||
let bound_radius = Vec3::splat(size as f32 / 2.).length() * trans.half_size.length();
|
||||
let position = trans
|
||||
.as_transform()
|
||||
.compute_matrix()
|
||||
.transform_point(local_pos);
|
||||
return (position, bound_radius);
|
||||
}
|
||||
#[test]
|
||||
pub fn test_sphere_internal_transform() {
|
||||
let pos = Vec3::new(0., 10., 0.);
|
||||
let size = 10;
|
||||
let obby = OBB::new(
|
||||
Vec3::new(0., 10., 0.),
|
||||
Quat::from_rotation_x(f32::to_radians(90.)),
|
||||
Vec3::new(1., 1., 1.),
|
||||
);
|
||||
let (x, y) = obb_to_global_sphere(pos, size, &obby);
|
||||
println!("{x}, {y:.3}");
|
||||
let z = x.y;
|
||||
println!("{z:.3}");
|
||||
let x = x.round();
|
||||
let y = y.round();
|
||||
assert_eq!(x, Vec3::new(0., 10., 20.));
|
||||
assert_eq!(y, 15.);
|
||||
}
|
||||
|
||||
pub fn obb_obb_overlap(first: &OBB, second: &OBB) -> bool {
|
||||
let r_pos = second.position - first.position;
|
||||
return !(get_seperating_plane_obb(r_pos, first.axis_x(), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_y(), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_z(), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, second.axis_x(), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, second.axis_y(), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, second.axis_z(), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_x().cross(second.axis_x()), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_x().cross(second.axis_y()), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_x().cross(second.axis_z()), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_y().cross(second.axis_x()), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_y().cross(second.axis_y()), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_y().cross(second.axis_z()), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_z().cross(second.axis_x()), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_z().cross(second.axis_y()), first, second)
|
||||
|| get_seperating_plane_obb(r_pos, first.axis_z().cross(second.axis_z()), first, second));
|
||||
}
|
||||
|
||||
fn get_seperating_plane_obb(r_pos: Vec3, plane: Vec3, first: &OBB, second: &OBB) -> bool {
|
||||
return r_pos.dot(plane).abs()
|
||||
> ((first.axis_x() * first.half_size.x).dot(plane).abs()
|
||||
+ (first.axis_y() * first.half_size.y).dot(plane).abs()
|
||||
+ (first.axis_z() * first.half_size.z).dot(plane).abs()
|
||||
+ (second.axis_x() * second.half_size.x).dot(plane).abs()
|
||||
+ (second.axis_y() * second.half_size.y).dot(plane).abs()
|
||||
+ (second.axis_z() * second.half_size.z).dot(plane).abs());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_octtree() {
|
||||
let origin = Vec3::ZERO;
|
||||
let neighbor = Vec3::new(1., 1., 1.);
|
||||
let mut test_oct = OctTree::<i32>::new(origin, 1);
|
||||
test_oct.set_voxel_at_location(neighbor, 2);
|
||||
|
||||
let far_off_bullshit = Vec3::new(26., 26., 26.);
|
||||
test_oct.set_voxel_at_location(far_off_bullshit, 5);
|
||||
dbg!(&test_oct);
|
||||
assert_eq!(test_oct.voxel_at_location(neighbor).unwrap().0, 2);
|
||||
assert_eq!(test_oct.voxel_at_location(origin).unwrap().0, 1);
|
||||
assert_eq!(test_oct.voxel_at_location(far_off_bullshit).unwrap().0, 5);
|
||||
}
|
||||
#[test]
|
||||
pub fn test_path() {
|
||||
let mut path_test: Path = Default::default();
|
||||
path_test.set_index(0, 6);
|
||||
path_test.set_index(7, 4);
|
||||
assert_eq!(path_test.get_index(7), 4);
|
||||
println!("{:#?}", dbg!(path_test.clone()));
|
||||
assert_eq!(path_test.get_index(0), 6);
|
||||
println!("{:#?}", dbg!(path_test.clone()));
|
||||
}
|
||||
#[test]
|
||||
pub fn test_sphere_collisions() {
|
||||
let x = sphere_sphere_overlap(Vec3::new(0., 6., 0.), 4., Vec3::new(0., 12., 0.), 9.5);
|
||||
println!("{}", x.unwrap());
|
||||
assert_eq!(x, Some(Vec3::new(0., 9., 0.)));
|
||||
}
|
||||
#[test]
|
||||
pub fn test_obb_collisions() {
|
||||
{
|
||||
let first = OBB::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
Quat::from_rotation_y(0.),
|
||||
Vec3::splat(5.),
|
||||
);
|
||||
let second = OBB::new(
|
||||
Vec3::new(11., 0., 0.),
|
||||
Quat::from_rotation_y(0.785398),
|
||||
Vec3::splat(5.),
|
||||
);
|
||||
assert!(obb_obb_overlap(&first, &second));
|
||||
}
|
||||
{
|
||||
let first = OBB::new(
|
||||
Vec3::new(0., 0., 0.),
|
||||
Quat::from_rotation_y(0.),
|
||||
Vec3::splat(5.),
|
||||
);
|
||||
let second = OBB::new(
|
||||
Vec3::new(11., 0., 0.),
|
||||
Quat::from_rotation_y(0.),
|
||||
Vec3::splat(5.),
|
||||
);
|
||||
assert!(!obb_obb_overlap(&first, &second));
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
pub fn test_ray_x_bounding_collisions() {
|
||||
let abounds: OBB = OBB::new(
|
||||
Vec3::new(10., 0., 0.),
|
||||
Quat::from_rotation_z(f32::to_radians(90.)),
|
||||
Vec3::splat(5.),
|
||||
);
|
||||
let aabb_test: Option<VectorPair> =
|
||||
ray_aabb_intersection(Vec3::ZERO, Vec3::X, abounds.position, abounds.half_size);
|
||||
let obb_test: Option<VectorPair> = ray_obb_intersection(Vec3::ZERO, Vec3::X, &abounds);
|
||||
|
||||
assert_eq!(aabb_test.clone().unwrap().global, Vec3::new(5., 0., 0.));
|
||||
assert_eq!(aabb_test.unwrap().local, Vec3::new(-1., 0., 0.));
|
||||
|
||||
assert_eq!(obb_test.unwrap().global.round(), Vec3::new(5., 0., 0.));
|
||||
|
||||
let rotated_bounds: OBB = OBB::new(
|
||||
Vec3::new(20., 0., 0.),
|
||||
Quat::from_rotation_z(f32::to_radians(-50.0)),
|
||||
Vec3::splat(5.),
|
||||
);
|
||||
let rotated_obb_test = ray_obb_intersection(Vec3::ZERO, Vec3::X, &rotated_bounds).unwrap();
|
||||
assert_eq!(rotated_obb_test.global.round().x, 13.);
|
||||
assert_eq!(rotated_obb_test.local, Vec3::new(0., -1., 0.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_ray_x_sphere_collisions() {
|
||||
let ro = Vec3::ZERO;
|
||||
let rd = Vec3::X;
|
||||
let sc = Vec3::new(10., 0., 0.);
|
||||
let sr: f32 = 4.;
|
||||
let result = ray_sphere_intersection(ro, rd, sc, sr);
|
||||
assert_eq!(result.unwrap(), Vec3::new(6., 0., 0.));
|
||||
}
|
||||
#[test]
|
||||
pub fn test_sphere_x_obb_collisions() {
|
||||
let obb = OBB::new(
|
||||
Vec3::ZERO,
|
||||
Quat::from_rotation_y(f32::to_radians(45.)),
|
||||
Vec3::splat(4.),
|
||||
);
|
||||
let sc = Vec3::new((std::f32::consts::SQRT_2 * -4.) - 1., 0., 0.);
|
||||
let sr = 1.1;
|
||||
let result = sphere_obb_intersection(sc, sr, &obb);
|
||||
let correct = Vec3::new(-5.656854, 0., 0.);
|
||||
assert!(correct.abs_diff_eq(result.unwrap(), 0.001));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_oct_x_oct_collisions() {
|
||||
let oct1 = OctTree::new(Vec3::splat(10.), 10);
|
||||
let oct2 = OctTree::new(Vec3::splat(10.), 1);
|
||||
let x = octtree_overlap(
|
||||
&oct1,
|
||||
&OBB::new(Vec3::ZERO, Quat::IDENTITY, Vec3::ONE / 2.),
|
||||
&oct2,
|
||||
&OBB::new(Vec3::ZERO, Quat::IDENTITY, Vec3::ONE / 2.),
|
||||
);
|
||||
assert!(x.is_some());
|
||||
|
||||
let mut oct1 = OctTree::new(Vec3::splat(10.), 10);
|
||||
oct1.set_voxel_at_location(Vec3::new(1., 0., 1.), 1);
|
||||
let mut oct2 = OctTree::new(Vec3::splat(0.), 10);
|
||||
oct2.set_voxel_at_location(Vec3::new(0., 10., 0.), 1);
|
||||
|
||||
let x = octtree_overlap(
|
||||
&oct1,
|
||||
&OBB::new(Vec3::ZERO, Quat::IDENTITY, Vec3::ONE / 2.),
|
||||
&oct2,
|
||||
&OBB::new(
|
||||
Vec3::ZERO,
|
||||
Quat::from_rotation_x(f32::to_radians(45.)),
|
||||
Vec3::ONE / 2.,
|
||||
),
|
||||
);
|
||||
assert!(x.is_some());
|
||||
let x = x.unwrap();
|
||||
//dbg!(&x);
|
||||
assert_eq!(
|
||||
x[0],
|
||||
(
|
||||
OctTreeCollisionPoint {
|
||||
pos: VectorPair::new(Vec3::new(1.0, 0.0, 1.0,), Vec3::new(1.0, 0.0, 1.0,)),
|
||||
path: Path {
|
||||
packed: 0,
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
OctTreeCollisionPoint {
|
||||
pos: VectorPair::new(Vec3::new(0.0, 0.0, 0.0,), Vec3::new(0.0, 0.0, 0.0,)),
|
||||
path: Path {
|
||||
packed: 0,
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
{
|
||||
let mut oct1 = OctTree::new(Vec3::splat(10.), 10);
|
||||
let mut oct2 = OctTree::new(Vec3::splat(-10.), 10);
|
||||
oct1.set_voxel_at_location(Vec3::splat(1.), 1);
|
||||
oct2.set_voxel_at_location(Vec3::splat(1.), 1);
|
||||
let orient1 = OBB::new(
|
||||
Vec3::splat(5.),
|
||||
Quat::from_rotation_x(f32::to_radians(10.)),
|
||||
Vec3::splat(1.),
|
||||
);
|
||||
let orient2 = OBB::new(
|
||||
Vec3::splat(5.),
|
||||
Quat::from_rotation_x(f32::to_radians(-10.)),
|
||||
Vec3::splat(1.),
|
||||
);
|
||||
let x = octtree_overlap(&oct1, &orient1, &oct2, &orient2);
|
||||
assert!(x.is_some());
|
||||
assert_eq!(
|
||||
x.unwrap()[0],
|
||||
(
|
||||
OctTreeCollisionPoint {
|
||||
pos: VectorPair::new(
|
||||
Vec3::new(7.0, 6.622319, 7.3169117,),
|
||||
Vec3::new(1.0, 1.0, 1.0,)
|
||||
),
|
||||
path: Path {
|
||||
packed: 0,
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
OctTreeCollisionPoint {
|
||||
pos: VectorPair::new(
|
||||
Vec3::new(7.0, 7.3169117, 6.622319,),
|
||||
Vec3::new(1.0, 1.0, 1.0,),
|
||||
),
|
||||
path: Path {
|
||||
packed: 0,
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
pub fn test_ray_x_octtree() {
|
||||
//A
|
||||
let mut oct = OctTree::new(Vec3::new(0., 0., 0.), 0);
|
||||
oct.set_voxel_at_location(Vec3::new(1., 1., 1.), 1);
|
||||
let obby = OBB::new(
|
||||
Vec3::splat(5.),
|
||||
Quat::from_rotation_x(f32::to_radians(30.)),
|
||||
Vec3::splat(1.),
|
||||
);
|
||||
let ray_origin = Vec3::ZERO;
|
||||
let ray_normal = Vec3::splat(5.).normalize();
|
||||
let collision = octtree_ray_overlap(&oct, &obby, ray_origin, ray_normal);
|
||||
if collision.is_some() {
|
||||
dbg!(collision.unwrap());
|
||||
}
|
||||
panic!();
|
||||
}
|
100
src/vvlib/mod.rs
Normal file
100
src/vvlib/mod.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
#![allow(dead_code)]
|
||||
pub mod intersections;
|
||||
pub mod obb;
|
||||
pub mod octtree;
|
||||
|
||||
pub mod constants {
|
||||
use bevy::math::Vec3;
|
||||
pub const CORNER: f32 = 0.866025;
|
||||
pub fn to_region_id(center: Vec3, subject: Vec3) -> i32 {
|
||||
if subject.x == center.x && subject.y == center.y && subject.z == center.z {
|
||||
return -1;
|
||||
}
|
||||
let mut result: i32 = 0;
|
||||
if subject.x >= center.x {
|
||||
result += 4;
|
||||
}
|
||||
if subject.y >= center.y {
|
||||
result += 2;
|
||||
}
|
||||
if subject.z >= center.z {
|
||||
result += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
pub fn to_index(vec: Vec3) -> i32 {
|
||||
let mut index = 0;
|
||||
for i in quadrant_offsets::ORDERED {
|
||||
if i == vec {
|
||||
return index;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
pub fn from_index(index: i32) -> Vec3 {
|
||||
if index < 0 || index >= 8 {
|
||||
return Vec3::ZERO;
|
||||
}
|
||||
return quadrant_offsets::ORDERED[index as usize];
|
||||
}
|
||||
pub fn sub_region(position: Vec3, size: usize, subject: i32) -> (Vec3, usize) {
|
||||
let size = size as f32;
|
||||
let new_size = size / 2.;
|
||||
let new_half_size = size / 4.;
|
||||
let subvec = from_index(subject);
|
||||
let offset = Vec3 {
|
||||
x: subvec.x * new_half_size,
|
||||
y: subvec.y * new_half_size,
|
||||
z: subvec.z * new_half_size,
|
||||
};
|
||||
let newcenter = position + offset;
|
||||
(newcenter, new_size as usize)
|
||||
}
|
||||
pub fn parent_region(position: Vec3, size: usize, subject: i32) -> (Vec3, usize) {
|
||||
let size = size as f32;
|
||||
let new_size = size * 2.;
|
||||
let half_size = size / 2.;
|
||||
let subvec = from_index(subject);
|
||||
//subvec *= -1.; // former error. tf?
|
||||
let offset = Vec3 {
|
||||
x: subvec.x * half_size,
|
||||
y: subvec.y * half_size,
|
||||
z: subvec.z * half_size,
|
||||
};
|
||||
let newcenter = position + offset;
|
||||
(newcenter, new_size as usize)
|
||||
}
|
||||
mod quadrant_indices {
|
||||
pub const NNN: i32 = 0;
|
||||
pub const NNP: i32 = 1;
|
||||
pub const NPN: i32 = 2;
|
||||
pub const NPP: i32 = 3;
|
||||
pub const PNN: i32 = 4;
|
||||
pub const PNP: i32 = 5;
|
||||
pub const PPN: i32 = 6;
|
||||
pub const PPP: i32 = 7;
|
||||
}
|
||||
mod quadrant_offsets {
|
||||
use bevy::math::Vec3;
|
||||
pub const NNN: Vec3 = Vec3::new(-1., -1., -1.);
|
||||
pub const NNP: Vec3 = Vec3::new(-1., -1., 1.);
|
||||
pub const NPN: Vec3 = Vec3::new(-1., 1., -1.);
|
||||
pub const NPP: Vec3 = Vec3::new(-1., 1., 1.);
|
||||
pub const PNN: Vec3 = Vec3::new(1., -1., -1.);
|
||||
pub const PNP: Vec3 = Vec3::new(1., -1., 1.);
|
||||
pub const PPN: Vec3 = Vec3::new(1., 1., -1.);
|
||||
pub const PPP: Vec3 = Vec3::new(1., 1., 1.);
|
||||
pub const ORDERED: [Vec3; 8] = [NNN, NNP, NPN, NPP, PNN, PNP, PPN, PPP];
|
||||
}
|
||||
pub(crate) mod cardinal_directions {
|
||||
use bevy::math::Vec3;
|
||||
pub const PX: Vec3 = Vec3::X;
|
||||
pub const NX: Vec3 = Vec3::NEG_X;
|
||||
pub const PY: Vec3 = Vec3::Y;
|
||||
pub const NY: Vec3 = Vec3::NEG_Y;
|
||||
pub const PZ: Vec3 = Vec3::Z;
|
||||
pub const NZ: Vec3 = Vec3::NEG_Z;
|
||||
pub const ORDERED: [Vec3; 6] = [PX, NX, PY, NY, PZ, NZ];
|
||||
}
|
||||
}
|
35
src/vvlib/obb.rs
Normal file
35
src/vvlib/obb.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use bevy::math::{Quat, Vec3};
|
||||
use bevy::prelude::Transform;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct OBB {
|
||||
pub(crate) position: Vec3,
|
||||
pub(crate) orientation: Quat,
|
||||
pub(crate) half_size: Vec3,
|
||||
}
|
||||
|
||||
impl OBB {
|
||||
pub fn new(position: Vec3, orientation: Quat, half_size: Vec3) -> OBB {
|
||||
return OBB {
|
||||
position: position,
|
||||
orientation: orientation,
|
||||
half_size: half_size,
|
||||
};
|
||||
}
|
||||
pub fn axis_x(&self) -> Vec3 {
|
||||
return self.orientation.mul_vec3(Vec3::X);
|
||||
}
|
||||
pub fn axis_y(&self) -> Vec3 {
|
||||
return self.orientation.mul_vec3(Vec3::Y);
|
||||
}
|
||||
pub fn axis_z(&self) -> Vec3 {
|
||||
return self.orientation.mul_vec3(Vec3::Z);
|
||||
}
|
||||
pub fn as_transform(&self) -> Transform {
|
||||
Transform::from_translation(self.position)
|
||||
.with_rotation(self.orientation)
|
||||
.with_scale(self.half_size * 2.)
|
||||
}
|
||||
}
|
627
src/vvlib/octtree.rs
Normal file
627
src/vvlib/octtree.rs
Normal file
|
@ -0,0 +1,627 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use std::mem::swap;
|
||||
|
||||
use bevy::math::Vec3;
|
||||
|
||||
use crate::vvlib::intersections::aa_bounds_contains;
|
||||
|
||||
use super::constants::{sub_region, to_region_id};
|
||||
|
||||
use super::constants;
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub enum TreeNode<T> {
|
||||
Branch(Box<[TreeNode<T>; 8]>),
|
||||
Leaf(T),
|
||||
#[default]
|
||||
Empty,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OctTree<T> {
|
||||
pub(crate) size: usize,
|
||||
pub(crate) center: Vec3,
|
||||
pub(crate) root: TreeNode<T>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
pub struct Path {
|
||||
pub(crate) packed: u64,
|
||||
pub(crate) length: usize,
|
||||
}
|
||||
|
||||
impl<T: Clone> TreeNode<T> {
|
||||
pub fn is_branch(&self) -> bool {
|
||||
match self {
|
||||
TreeNode::Branch(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn is_leaf(&self) -> bool {
|
||||
match self {
|
||||
TreeNode::Leaf(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
TreeNode::Empty => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn branch_contents_reference(&self) -> Option<&[TreeNode<T>; 8]> {
|
||||
match self {
|
||||
TreeNode::Branch(a) => Some(a.as_ref()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn branch_contents_mutable(&mut self) -> Option<&mut [TreeNode<T>; 8]> {
|
||||
match self {
|
||||
TreeNode::Branch(a) => Some(a.as_mut()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Path {
|
||||
pub fn pop(&mut self) -> usize {
|
||||
self.length -= 1;
|
||||
return self.get_index(self.length);
|
||||
}
|
||||
pub fn parent(&self) -> Path {
|
||||
let mut duplicate = self.clone();
|
||||
duplicate.set_index(self.length - 1, 0);
|
||||
duplicate.length = duplicate.length - 1;
|
||||
return duplicate;
|
||||
}
|
||||
pub fn get_index(&self, index: usize) -> usize {
|
||||
if self.length < index {
|
||||
panic!("in too deep!");
|
||||
}
|
||||
let shift_amount = index as u64 * 3;
|
||||
let unshifted_result = self.packed >> shift_amount;
|
||||
(unshifted_result & 7) as usize
|
||||
}
|
||||
pub fn set_index(&mut self, index: usize, value: usize) {
|
||||
if value > 7 {
|
||||
panic!("cannot set to more than 7!");
|
||||
}
|
||||
if index + 1 > self.length {
|
||||
self.length = index + 1;
|
||||
}
|
||||
let shift_amount = index as u64 * 3;
|
||||
let shifted_value = (value as u64) << shift_amount;
|
||||
let shift_mask = 7_u64 << shift_amount;
|
||||
let shift_mask = !shift_mask;
|
||||
self.packed = self.packed & shift_mask;
|
||||
self.packed = self.packed | shifted_value;
|
||||
}
|
||||
|
||||
pub fn to_vec3(&self, center: Vec3, size: usize) -> Vec3 {
|
||||
let mut iter = (center, size);
|
||||
for i in 0..self.length {
|
||||
iter = super::constants::sub_region(
|
||||
iter.0,
|
||||
iter.1,
|
||||
self.get_index(i.try_into().unwrap()).try_into().unwrap(),
|
||||
);
|
||||
}
|
||||
return iter.0;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> OctTree<T> {
|
||||
pub fn new(npos: Vec3, value: T) -> Self {
|
||||
Self {
|
||||
size: 1,
|
||||
center: npos,
|
||||
root: TreeNode::Leaf(value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_voxel(&mut self, location: Vec3) -> usize {
|
||||
let result = self.voxel_at_location(location);
|
||||
if result.is_some() {
|
||||
let mut count: usize = 0;
|
||||
Self::prune(&mut self.root, result.unwrap().1, &mut count);
|
||||
self.trim(&mut count);
|
||||
return count;
|
||||
} else {
|
||||
return 0; // no culled leaves
|
||||
}
|
||||
}
|
||||
fn has_only_one_element(subject: &mut [TreeNode<T>; 8]) -> Option<usize> {
|
||||
// returns target location of the element
|
||||
let mut res = Option::<usize>::None;
|
||||
for element in subject.iter().enumerate() {
|
||||
match element.1 {
|
||||
TreeNode::Branch(_) | TreeNode::Leaf(_) => {
|
||||
if res.is_none() {
|
||||
res = Some(element.0);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
fn trim(&mut self, removal_count: &mut usize) {
|
||||
let count;
|
||||
let mut extracted: &mut TreeNode<T> = &mut TreeNode::Empty;
|
||||
match &mut self.root {
|
||||
TreeNode::Branch(a) => {
|
||||
let contents = a.as_mut();
|
||||
count = Self::has_only_one_element(contents);
|
||||
if count.is_some() {
|
||||
extracted = &mut contents[count.unwrap()];
|
||||
}
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
if count.is_some() {
|
||||
self.root = extracted.clone();
|
||||
let next_region =
|
||||
constants::sub_region(self.center, self.size, count.unwrap().try_into().unwrap());
|
||||
self.center = next_region.0;
|
||||
self.size = next_region.1;
|
||||
*removal_count += 1;
|
||||
self.trim(removal_count);
|
||||
}
|
||||
}
|
||||
fn prune(element: &mut TreeNode<T>, mut target: Path, count: &mut usize) {
|
||||
match element {
|
||||
TreeNode::Branch(a) => {
|
||||
let i = target.pop();
|
||||
let ar: &mut [TreeNode<T>; 8] = a.as_mut();
|
||||
Self::prune(&mut ar[i], target, count);
|
||||
|
||||
if ar.iter().all(|x| matches!(x, TreeNode::Empty)) {
|
||||
*element = TreeNode::Empty;
|
||||
*count += 1;
|
||||
}
|
||||
}
|
||||
TreeNode::Leaf(_) => {
|
||||
if target.length == 0 {
|
||||
*element = TreeNode::Empty;
|
||||
*count += 1;
|
||||
}
|
||||
}
|
||||
TreeNode::Empty => return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_voxel_at_path(&mut self, path: &Path, value: T) -> bool {
|
||||
// return value is whether it was successful
|
||||
let mut iter = &mut self.root;
|
||||
if path.length == 0 {
|
||||
match &mut self.root {
|
||||
TreeNode::Leaf(nvalue) => {
|
||||
*nvalue = value;
|
||||
return true;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0..path.length {
|
||||
let region = path.get_index(i) as usize;
|
||||
match iter {
|
||||
TreeNode::Branch(branch) => {
|
||||
let arr = branch.as_mut();
|
||||
iter = &mut arr[region];
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
match &mut iter {
|
||||
TreeNode::Leaf(nvalue) => {
|
||||
*nvalue = value;
|
||||
return true;
|
||||
}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_voxel_at_location(&mut self, location: Vec3, value: T) -> Path {
|
||||
// if the tree is empty, we want to fall out immediately - no looping needed.
|
||||
match self.root {
|
||||
TreeNode::Empty => {
|
||||
self.center = location;
|
||||
self.size = 1;
|
||||
self.root = TreeNode::Leaf(value);
|
||||
return Default::default();
|
||||
}
|
||||
_ => {} // continue to modify the tree structure - no easy answer yet
|
||||
}
|
||||
let mut path: Path = Default::default();
|
||||
// if we don't already have a big enough non-empty tree, we need to branch upward till our tree contains our subject matter
|
||||
if !aa_bounds_contains(self.center, self.size, location) {
|
||||
println!("building up to contain addition {location}");
|
||||
let search_direction = constants::to_region_id(self.center, location);
|
||||
|
||||
while !aa_bounds_contains(self.center, self.size, location) {
|
||||
println!(
|
||||
"{0}, size {1}, region {search_direction}",
|
||||
self.center, self.size
|
||||
);
|
||||
match &self.root {
|
||||
TreeNode::Branch(_) | TreeNode::Leaf(_) => {
|
||||
let mut branch_extension: [TreeNode<T>; 8] = Default::default();
|
||||
let (extension_center, extension_size) =
|
||||
constants::parent_region(self.center, self.size, search_direction);
|
||||
let slot_within_new_branch =
|
||||
constants::to_region_id(extension_center, self.center);
|
||||
let mut old: TreeNode<T> = TreeNode::Empty;
|
||||
|
||||
swap(&mut self.root, &mut old);
|
||||
branch_extension[slot_within_new_branch as usize] = old;
|
||||
self.root = TreeNode::Branch(Box::new(branch_extension));
|
||||
self.center = extension_center;
|
||||
self.size = extension_size;
|
||||
println!("{slot_within_new_branch} housing previous root");
|
||||
match &self.root {
|
||||
TreeNode::Branch(_) => {
|
||||
println!("root is a branch");
|
||||
}
|
||||
TreeNode::Leaf(_) => todo!(),
|
||||
TreeNode::Empty => todo!(),
|
||||
}
|
||||
}
|
||||
TreeNode::Empty => {
|
||||
println!("something went wrong with early fall-out. shouldn't be plausible unless you're using unsafe rust.");
|
||||
return Default::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!(
|
||||
"{0}, size {1}, region {search_direction}",
|
||||
self.center, self.size
|
||||
);
|
||||
}
|
||||
|
||||
// custom loop down: discover next position downward
|
||||
let mut rc_center = self.center;
|
||||
let mut rc_size = self.size;
|
||||
let mut search_direction = constants::to_region_id(rc_center, location);
|
||||
let mut rc_loc = &mut self.root;
|
||||
|
||||
println!("searching down to set {location}");
|
||||
while rc_size > 0 {
|
||||
if search_direction != -1 {
|
||||
let pindex = path.length;
|
||||
path.set_index(pindex, search_direction as usize);
|
||||
}
|
||||
println!("{rc_center}, size {rc_size}, region {search_direction}");
|
||||
match rc_loc {
|
||||
TreeNode::Branch(region) => {
|
||||
let region = region.as_mut();
|
||||
let next_rc_loc = &mut region[search_direction as usize];
|
||||
(rc_center, rc_size) = sub_region(rc_center, rc_size, search_direction);
|
||||
search_direction = constants::to_region_id(rc_center, location);
|
||||
|
||||
if rc_size == 1 {
|
||||
println!(
|
||||
"value set {location} within {rc_center}, {rc_size}, index {search_direction}"
|
||||
);
|
||||
let end = TreeNode::Leaf(value.clone());
|
||||
*next_rc_loc = end;
|
||||
return path;
|
||||
}
|
||||
match next_rc_loc {
|
||||
TreeNode::Branch(_) => {}
|
||||
TreeNode::Leaf(lvalue) => {
|
||||
if rc_size != 1 {
|
||||
println!("tree edited to contain non-unit node size. returning instead of losing data");
|
||||
return Default::default();
|
||||
} else {
|
||||
println!(
|
||||
"value set {location} within {rc_center}, {rc_size}, index {search_direction}"
|
||||
);
|
||||
*lvalue = value.clone();
|
||||
return path;
|
||||
}
|
||||
}
|
||||
TreeNode::Empty => {
|
||||
let branch_extension: [TreeNode<T>; 8] = Default::default();
|
||||
let next_branch = TreeNode::Branch(Box::new(branch_extension));
|
||||
*next_rc_loc = next_branch;
|
||||
}
|
||||
}
|
||||
rc_loc = next_rc_loc;
|
||||
}
|
||||
TreeNode::Leaf(lvalue) => {
|
||||
if rc_size == 1 {
|
||||
println!(
|
||||
"value set {location} within {rc_center}, {rc_size}, index {search_direction}"
|
||||
);
|
||||
*lvalue = value;
|
||||
return path;
|
||||
} else {
|
||||
println!("tree edited to contain non-unit node size. returning instead of losing data");
|
||||
return Default::default();
|
||||
}
|
||||
}
|
||||
TreeNode::Empty => {
|
||||
println!(
|
||||
"should be handled automatically before arrival at target node. returning"
|
||||
);
|
||||
return Default::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Default::default();
|
||||
}
|
||||
|
||||
pub fn collect_voxels(&self) -> Vec<(Path, Vec3, T)> {
|
||||
let mut result = Vec::new();
|
||||
Self::visitor(
|
||||
&self.root,
|
||||
self.center,
|
||||
self.size,
|
||||
&mut result,
|
||||
Default::default(),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn bounding_box(&self) -> Option<(Vec3, Vec3)> {
|
||||
//center, size
|
||||
let contents = self.collect_voxels();
|
||||
let mut min: Option<Vec3> = None;
|
||||
let mut max: Option<Vec3> = None;
|
||||
for each in contents.iter() {
|
||||
if min.is_none() {
|
||||
min = Some(each.1);
|
||||
} else {
|
||||
let mut cont = min.unwrap();
|
||||
if cont.x > each.1.x {
|
||||
cont.x = each.1.x;
|
||||
min = Some(cont);
|
||||
}
|
||||
if cont.y > each.1.y {
|
||||
cont.y = each.1.y;
|
||||
min = Some(cont);
|
||||
}
|
||||
if cont.z > each.1.z {
|
||||
cont.z = each.1.z;
|
||||
min = Some(cont);
|
||||
}
|
||||
}
|
||||
if max.is_none() {
|
||||
max = Some(each.1);
|
||||
} else {
|
||||
let mut cont: Vec3 = max.unwrap();
|
||||
if cont.x < each.1.x {
|
||||
cont.x = each.1.x;
|
||||
max = Some(cont);
|
||||
}
|
||||
if cont.y < each.1.y {
|
||||
cont.y = each.1.y;
|
||||
max = Some(cont);
|
||||
}
|
||||
if cont.z < each.1.z {
|
||||
cont.z = each.1.z;
|
||||
max = Some(cont);
|
||||
}
|
||||
}
|
||||
}
|
||||
if max.is_none() && min.is_none() {
|
||||
return None;
|
||||
} else {
|
||||
let center = min.unwrap() + max.unwrap();
|
||||
let center = center / 2.;
|
||||
let size = max.unwrap() - min.unwrap() + Vec3::ONE; // + 1 accounts for size of voxels themself, one half a voxel on each side
|
||||
return Some((center, size));
|
||||
}
|
||||
}
|
||||
|
||||
fn visitor(
|
||||
subject: &TreeNode<T>,
|
||||
location: Vec3,
|
||||
size: usize,
|
||||
result: &mut Vec<(Path, Vec3, T)>,
|
||||
path: Path,
|
||||
) {
|
||||
match subject {
|
||||
TreeNode::Branch(branch) => {
|
||||
let branch = branch.as_ref();
|
||||
for (index, node) in branch.iter().enumerate() {
|
||||
let (subpos, subsize) = sub_region(location, size, index as i32);
|
||||
let mut path_next = path.clone();
|
||||
path_next.set_index(path.length, index);
|
||||
Self::visitor(node, subpos, subsize, result, path_next);
|
||||
}
|
||||
}
|
||||
TreeNode::Leaf(value) => {
|
||||
result.push((path, location, value.clone()));
|
||||
}
|
||||
TreeNode::Empty => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn voxel_at_path(&self, path: &Path) -> Option<T> {
|
||||
// return value is whether it was successful
|
||||
let mut iter = &self.root;
|
||||
if path.length == 0 {
|
||||
match &self.root {
|
||||
TreeNode::Leaf(nvalue) => {
|
||||
return Some(nvalue.clone());
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
for i in 0..path.length {
|
||||
let region = path.get_index(i) as usize;
|
||||
match iter {
|
||||
TreeNode::Branch(branch) => {
|
||||
let arr = branch.as_ref();
|
||||
iter = &arr[region];
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
match &mut iter {
|
||||
TreeNode::Leaf(nvalue) => {
|
||||
return Some(nvalue.clone());
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn voxel_at_location(&self, location: Vec3) -> Option<(T, Path)> {
|
||||
if aa_bounds_contains(self.center, self.size, location) {
|
||||
let mut rc_center = self.center;
|
||||
let mut rc_size = self.size;
|
||||
let mut path_result: Path = Default::default();
|
||||
|
||||
let mut search_direction = constants::to_region_id(rc_center, location);
|
||||
|
||||
println!("searching for {location}");
|
||||
let mut rc_loc = &self.root;
|
||||
while rc_size > 1 {
|
||||
if search_direction != -1 {
|
||||
let pindex = path_result.length;
|
||||
path_result.set_index(pindex, search_direction as usize);
|
||||
}
|
||||
println!("{rc_center}, size {rc_size}, region {search_direction}");
|
||||
match rc_loc {
|
||||
TreeNode::Branch(branch) => {
|
||||
if search_direction == -1 {
|
||||
panic!("search direction gone wrong");
|
||||
}
|
||||
rc_loc = &branch[search_direction as usize];
|
||||
(rc_center, rc_size) = sub_region(rc_center, rc_size, search_direction);
|
||||
search_direction = to_region_id(rc_center, location);
|
||||
}
|
||||
TreeNode::Leaf(_) => {
|
||||
panic!("tree not constructed correctly - non-unit voxel size");
|
||||
}
|
||||
TreeNode::Empty => {
|
||||
println!("doesn't seem to exist");
|
||||
return None; // does not exist.
|
||||
}
|
||||
}
|
||||
}
|
||||
match rc_loc {
|
||||
TreeNode::Leaf(value) => {
|
||||
println!("found at {rc_center}, {rc_size}");
|
||||
return Some((value.clone(), path_result));
|
||||
}
|
||||
_ => {
|
||||
println!("Couldn't find it..");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_path_return() {
|
||||
let mut octtwee = OctTree::<String>::new(Vec3::ZERO, "a".to_string());
|
||||
octtwee.set_voxel_at_location(Vec3::splat(1.), "test".to_string());
|
||||
octtwee.set_voxel_at_location(Vec3::splat(19.), "test".to_string());
|
||||
let res0 = octtwee.set_voxel_at_location(Vec3::new(190., 15., -100.), "test".to_string());
|
||||
|
||||
println!("{:#?}", dbg!(res0.clone()));
|
||||
println!("{:064b}", res0.packed.clone());
|
||||
for i in 0..res0.length {
|
||||
println!("{}", res0.get_index(i));
|
||||
}
|
||||
let res1 = octtwee.voxel_at_location(Vec3::new(190., 15., -100.));
|
||||
match res1.clone() {
|
||||
Some((_, c)) => {
|
||||
assert_eq!(res0.packed, res1.clone().unwrap().1.packed); //internal path consistency with both functions
|
||||
assert_eq!(
|
||||
Vec3::new(190., 15., -100.),
|
||||
c.to_vec3(octtwee.center, octtwee.size)
|
||||
);
|
||||
assert_eq!(res1.clone().unwrap().0, "test".to_string());
|
||||
}
|
||||
_ => {
|
||||
panic!("should be something there!");
|
||||
}
|
||||
}
|
||||
assert!(octtwee.set_voxel_at_path(&res1.clone().unwrap().1, "test_change".to_string())); //if we cant modify it we get false, a failed test
|
||||
|
||||
assert_eq!(
|
||||
octtwee
|
||||
.voxel_at_location(Vec3::new(190., 15., -100.))
|
||||
.unwrap()
|
||||
.0,
|
||||
octtwee.voxel_at_path(&res1.clone().unwrap().1).unwrap()
|
||||
);
|
||||
let res1 = octtwee.voxel_at_location(Vec3::new(190., 15., -100.));
|
||||
match &res1 {
|
||||
Some((a, _)) => {
|
||||
assert_eq!(*a, "test_change".to_string());
|
||||
}
|
||||
None => {
|
||||
panic!("something should be here");
|
||||
}
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
pub fn test_bounding() {
|
||||
let mut octtree = OctTree::<()>::new(Vec3::ZERO, ());
|
||||
octtree.set_voxel_at_location(Vec3::splat(10.), ());
|
||||
assert_eq!(
|
||||
octtree.bounding_box().unwrap(),
|
||||
(Vec3::splat(5.), Vec3::splat(11.))
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
pub fn test_collect() {
|
||||
let zeropos = Vec3::ZERO;
|
||||
let otherpos = Vec3::new(10., -5., 1.);
|
||||
let mut octtree = OctTree::<usize>::new(zeropos, 0);
|
||||
octtree.set_voxel_at_location(otherpos, 27);
|
||||
let x = octtree.collect_voxels();
|
||||
assert_eq!(
|
||||
x[0],
|
||||
(
|
||||
Path {
|
||||
packed: 1170,
|
||||
length: 4,
|
||||
},
|
||||
Vec3::ZERO,
|
||||
0
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
x[1],
|
||||
(
|
||||
Path {
|
||||
packed: 902,
|
||||
length: 4,
|
||||
},
|
||||
otherpos,
|
||||
27
|
||||
)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
pub fn test_removal() {
|
||||
let mut test_tree = OctTree::new(Vec3::ZERO, 0);
|
||||
test_tree.set_voxel_at_location(Vec3::splat(10.), 5);
|
||||
test_tree.remove_voxel(Vec3::ZERO);
|
||||
assert_eq!(test_tree.size, 1);
|
||||
}
|
Loading…
Reference in a new issue