use crate::{
core::{
algebra::{Matrix4, Vector2, Vector3},
color::Color,
log::Log,
math::{aabb::AxisAlignedBoundingBox, frustum::Frustum},
},
scene::debug::SceneDrawingContext,
};
#[derive(Default, Debug, PartialEq)]
pub struct QuadTree {
root: QuadTreeNode,
pub max_level: u32,
height_mod_count: u64,
}
#[derive(Debug, PartialEq)]
pub struct QuadTreeNode {
pub size: Vector2<u32>,
pub position: Vector2<u32>,
pub kind: QuadTreeNodeKind,
pub level: u32,
pub min_height: f32,
pub max_height: f32,
}
impl Default for QuadTreeNode {
fn default() -> Self {
Self {
size: Default::default(),
position: Default::default(),
kind: QuadTreeNodeKind::Leaf,
level: 0,
min_height: 0.0,
max_height: 0.0,
}
}
}
#[derive(Debug)]
pub struct SelectedNode {
pub position: Vector2<u32>,
pub size: Vector2<u32>,
pub active_quadrants: [bool; 4],
}
impl SelectedNode {
pub fn is_draw_full(&self) -> bool {
self.active_quadrants.iter().all(|s| *s)
}
}
impl QuadTreeNode {
pub fn new(
height_map: &[f32],
height_map_size: Vector2<u32>,
position: Vector2<u32>,
node_size: Vector2<u32>,
max_size: Vector2<u32>,
level: u32,
) -> Self {
let mut min_height = f32::MAX;
let mut max_height = f32::MIN;
let x_max = position.x + node_size.x;
let y_max = position.y + node_size.y;
assert!(
x_max <= height_map_size.x,
"position.x({}) + node_size.x({}) = {} > {} (height_map_size.x)",
position.x,
node_size.x,
x_max,
height_map_size.x
);
assert!(
y_max <= height_map_size.y,
"position.y({}) + node_size.y({}) = {} > {} (height_map_size.y)",
position.y,
node_size.y,
y_max,
height_map_size.y
);
for y in position.y..y_max {
for x in position.x..x_max {
let height = height_map[(y * height_map_size.x + x) as usize];
if height < min_height {
min_height = height;
}
if height > max_height {
max_height = height;
}
}
}
let kind = if node_size.x < 3
|| node_size.y < 3
|| node_size.x <= max_size.x && node_size.y <= max_size.y
{
QuadTreeNodeKind::Leaf
} else {
let real_size = Vector2::new(node_size.x - 1, node_size.y - 1);
let new_size = Vector2::new(real_size.x / 2 + 1, real_size.y / 2 + 1);
let remain = (node_size - new_size).map(|x| x + 1);
let center_pos = Vector2::new(new_size.x - 1, new_size.y - 1);
let next_level = level + 1;
QuadTreeNodeKind::Branch {
leafs: [
Box::new(QuadTreeNode::new(
height_map,
height_map_size,
position,
new_size,
max_size,
next_level,
)),
Box::new(QuadTreeNode::new(
height_map,
height_map_size,
position + Vector2::new(center_pos.x, 0),
Vector2::new(remain.x, new_size.y),
max_size,
next_level,
)),
Box::new(QuadTreeNode::new(
height_map,
height_map_size,
position + center_pos,
remain,
max_size,
next_level,
)),
Box::new(QuadTreeNode::new(
height_map,
height_map_size,
position + Vector2::new(0, center_pos.y),
Vector2::new(new_size.x, remain.y),
max_size,
next_level,
)),
],
}
};
Self {
position,
size: node_size,
kind,
level,
min_height,
max_height,
}
}
pub fn aabb(
&self,
transform: &Matrix4<f32>,
height_map_size: Vector2<u32>,
physical_size: Vector2<f32>,
) -> AxisAlignedBoundingBox {
if self.size.x < 1 || self.size.y < 1 {
Log::err("Invalid Terrain quad tree node size");
return Default::default();
}
if height_map_size.x < 3 || height_map_size.y < 3 {
Log::err("Invalid Terrain height texture size");
return Default::default();
}
if self.position.x < 1 || self.position.y < 1 {
Log::err("Invalid Terrain quad tree node position");
return Default::default();
}
let real_map_size = height_map_size.map(|x| x - 3);
let real_node_size = self.size.map(|x| x - 1);
let pos = self.position.map(|x| x - 1);
let min_x = (pos.x as f32 / real_map_size.x as f32) * physical_size.x;
let min_y = (pos.y as f32 / real_map_size.y as f32) * physical_size.y;
let max_x = ((pos.x + real_node_size.x) as f32 / real_map_size.x as f32) * physical_size.x;
let max_y = ((pos.y + real_node_size.y) as f32 / real_map_size.y as f32) * physical_size.y;
let min = Vector3::new(min_x, self.min_height, min_y);
let max = Vector3::new(max_x, self.max_height, max_y);
AxisAlignedBoundingBox::from_min_max(min, max).transform(transform)
}
pub fn debug_draw(
&self,
transform: &Matrix4<f32>,
height_map_size: Vector2<u32>,
physical_size: Vector2<f32>,
drawing_context: &mut SceneDrawingContext,
) {
drawing_context.draw_aabb(
&self.aabb(transform, height_map_size, physical_size),
Color::RED,
);
if let QuadTreeNodeKind::Branch { ref leafs } = self.kind {
for leaf in leafs {
leaf.debug_draw(transform, height_map_size, physical_size, drawing_context);
}
}
}
pub fn select(
&self,
transform: &Matrix4<f32>,
height_map_size: Vector2<u32>,
physical_size: Vector2<f32>,
frustum: Option<&Frustum>,
camera_position: Vector3<f32>,
level_ranges: &[f32],
selection: &mut Vec<SelectedNode>,
) -> bool {
let current_level = self.level as usize;
let Some(radius) = level_ranges.get(current_level).copied() else {
return false;
};
let aabb = self.aabb(transform, height_map_size, physical_size);
if !frustum.is_none_or(|f| f.is_intersects_aabb(&aabb))
|| !aabb.is_intersects_sphere(camera_position, radius)
{
return false;
}
if level_ranges
.get(current_level + 1)
.is_some_and(|next_range| aabb.is_intersects_sphere(camera_position, *next_range))
{
match self.kind {
QuadTreeNodeKind::Branch { ref leafs } => {
let mut active_quadrants = [false; 4];
for (leaf, is_active) in leafs.iter().zip(active_quadrants.iter_mut()) {
*is_active = !leaf.select(
transform,
height_map_size,
physical_size,
frustum,
camera_position,
level_ranges,
selection,
);
}
selection.push(SelectedNode {
position: self.position,
size: self.size,
active_quadrants,
});
}
QuadTreeNodeKind::Leaf => {
selection.push(SelectedNode {
position: self.position,
size: self.size,
active_quadrants: [true; 4],
});
}
}
} else {
selection.push(SelectedNode {
position: self.position,
size: self.size,
active_quadrants: [true; 4],
});
}
true
}
fn max_level(&self, max_level: &mut u32) {
if self.level > *max_level {
*max_level = self.level
}
if let QuadTreeNodeKind::Branch { ref leafs } = self.kind {
for leaf in leafs {
leaf.max_level(max_level);
}
}
}
}
#[derive(Debug, PartialEq)]
pub enum QuadTreeNodeKind {
Leaf,
Branch { leafs: [Box<QuadTreeNode>; 4] },
}
impl QuadTree {
pub fn new(
height_map: &[f32],
height_map_size: Vector2<u32>,
block_size: Vector2<u32>,
height_mod_count: u64,
) -> Self {
let root = QuadTreeNode::new(
height_map,
height_map_size,
Vector2::new(1, 1),
height_map_size.map(|x| x - 2),
block_size,
0,
);
let mut max_level = 0;
root.max_level(&mut max_level);
Self {
max_level,
root,
height_mod_count,
}
}
pub fn height_mod_count(&self) -> u64 {
self.height_mod_count
}
pub fn select(
&self,
transform: &Matrix4<f32>,
height_map_size: Vector2<u32>,
physical_size: Vector2<f32>,
frustum: Option<&Frustum>,
camera_position: Vector3<f32>,
level_ranges: &[f32],
selection: &mut Vec<SelectedNode>,
) {
self.root.select(
transform,
height_map_size,
physical_size,
frustum,
camera_position,
level_ranges,
selection,
);
}
pub fn debug_draw(
&self,
transform: &Matrix4<f32>,
height_map_size: Vector2<u32>,
physical_size: Vector2<f32>,
drawing_context: &mut SceneDrawingContext,
) {
self.root
.debug_draw(transform, height_map_size, physical_size, drawing_context);
}
}
#[cfg(test)]
mod test {
use crate::{
core::{
algebra::{Matrix4, Point3, Vector2, Vector3},
math::frustum::Frustum,
},
scene::terrain::quadtree::QuadTree,
};
#[test]
fn test_terrain_quad_tree_selection() {
let block_size = Vector2::new(16, 16);
let height_map_size = Vector2::<u32>::new(64, 64);
let physical_size = Vector2::new(100.0, 100.0);
let heightmap = vec![0.0; (height_map_size.x * height_map_size.y) as usize];
let z_far = 100.0;
let quadtree = QuadTree::new(&heightmap, height_map_size, block_size, 0);
let levels = (0..quadtree.max_level)
.map(|n| z_far * (n as f32 / quadtree.max_level as f32))
.collect::<Vec<_>>();
for (iteration, (position, target)) in [
(
Point3::new(physical_size.x * 0.5, 0.0, 0.0),
Point3::new(0.0, 0.0, 1.0),
),
(
Point3::new(physical_size.x * 0.5, 0.0, -f32::EPSILON),
Point3::new(0.0, 0.0, -1.0),
),
]
.into_iter()
.enumerate()
{
let view_matrix = Matrix4::look_at_rh(&position, &target, &Vector3::y());
let projection = Matrix4::new_perspective(1.0, 90.0f32.to_radians(), 0.025, z_far);
let frustum = Frustum::from_view_projection_matrix(projection * view_matrix).unwrap();
let mut selection = Vec::new();
quadtree.select(
&Matrix4::identity(),
height_map_size,
physical_size,
Some(&frustum),
position.coords,
&levels,
&mut selection,
);
dbg!(iteration, &selection);
if iteration == 1 {
assert!(selection.is_empty());
}
}
}
}