use crate::core::components::LevelOfDetail;
use bevy_camera::prelude::Visibility;
use bevy_camera::visibility::VisibilityRange;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::prelude::*;
use bevy_math::{IVec2, Vec3};
use bevy_reflect::Reflect;
use bevy_transform::prelude::Transform;
use bevy_utils::default;
use derive_more::From;
#[cfg(feature = "trace")]
use tracing::warn;
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
pub struct LodConfig {
pub distance: Vec<LodDistance>,
pub density: Vec<LodDensity>,
}
impl Default for LodConfig {
fn default() -> Self {
Self {
distance:
vec![
30.0.into(),
90.0.into(),
270.into(),
default(), ],
density: vec![
1.0.into(),
0.2.into(),
0.1.into(),
default() ]
}
}
}
impl LodConfig {
pub fn none() -> Self {
Self {
distance: vec![default()],
density: vec![1.0.into()],
}
}
}
impl From<Vec<LodDistance>> for LodConfig {
fn from(value: Vec<LodDistance>) -> Self {
Self {
distance: value,
..default()
}
}
}
impl LodConfiguration for LodConfig {
fn get(&self) -> &Vec<LodDistance> {
&self.distance
}
}
pub trait LodConfiguration {
fn get(&self) -> &Vec<LodDistance>;
fn get_max_lod(&self) -> u32 {
(self.get().len() - 1) as u32
}
fn get_lod_config(&self, level: u32) -> LodDistance {
self.get().get(level as usize).cloned().unwrap_or_default()
}
fn get_visibility_range(&self, lod: LevelOfDetail) -> VisibilityRange {
let current_lod_dist = *self.get_lod_config(*lod);
let fade_band_multiplier = 0.05;
let start_margin = if *lod == 0 {
0.0..0.0
} else {
let prev_lod_dist = self
.get()
.get(*lod as usize - 1)
.map(|d| **d)
.unwrap_or(*LodDistance::default());
let fade_band = prev_lod_dist * fade_band_multiplier;
prev_lod_dist..(prev_lod_dist + fade_band)
};
let end_margin = if *lod == self.get_max_lod() {
f32::MAX..f32::MAX
} else {
let fade_band = current_lod_dist * fade_band_multiplier;
current_lod_dist..(current_lod_dist + fade_band)
};
VisibilityRange {
start_margin,
end_margin,
use_aabb: false,
}
}
}
#[derive(Component, Reflect, Deref, DerefMut, Debug)]
#[reflect(Component)]
pub struct ChunkRootSizeDim(pub u32);
impl Default for ChunkRootSizeDim {
fn default() -> Self {
Self(2)
}
}
#[derive(Component, Reflect, Debug, Default)]
#[reflect(Component)]
pub struct ChunkRootDisabled;
#[derive(Component, Reflect, Deref, DerefMut, Debug, Hash)]
#[reflect(Component)]
pub struct ChunkCoord(pub IVec2);
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
pub struct ChunkInitialize;
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
pub struct CanSplit;
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
pub struct CanMerge;
#[derive(Component, Reflect, Deref, DerefMut, Default, Debug, Clone, Copy)]
#[reflect(Component, Clone, Debug)]
pub struct ChunkLevel(pub u32);
#[derive(Component, Reflect, Deref, DerefMut, Debug, Clone, Copy)]
#[reflect(Component, Debug, Clone)]
pub struct ChunkSize(pub u32);
#[derive(Component, Reflect, Deref, DerefMut, Debug, Clone)]
#[reflect(Component, Debug, Clone)]
pub struct BaseChunkSize(pub Vec3);
impl BaseChunkSize {
#[inline]
fn get_chunk_radius(&self, scalar: u32) -> f32 {
let scalar = scalar as f32;
let scaled_size = **self * scalar;
let diagonal_sq = scaled_size.x.powi(2) + scaled_size.y.powi(2) + scaled_size.z.powi(2);
let diagonal = diagonal_sq.sqrt();
diagonal / 2.0
}
}
#[derive(Component, Reflect, Deref, DerefMut, Debug, From)]
#[require(CanMerge)]
#[reflect(Component)]
pub struct MergeDistance(pub f32);
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
pub struct Merging;
#[derive(Component, Reflect, Deref, DerefMut, Debug)]
#[require(CanSplit)]
#[reflect(Component)]
pub struct SplitDistance(pub f32);
#[derive(Component, Debug, Clone, Reflect)]
#[require(Transform, Visibility, ChunkInitialize)]
#[reflect(Component)]
#[derive(Default)]
pub struct Chunk;
#[derive(Component, Debug, Clone, Reflect, Deref)]
#[reflect(Component)]
#[relationship(relationship_target = ChunkRoot)]
pub struct ChunkOf(pub Entity);
#[derive(Component, Debug, Clone, Reflect, Deref, Default)]
#[reflect(Component)]
#[require(Transform, Visibility, ChunkSizeScalarConfig, ChunkRootSizeDim)]
#[relationship_target(relationship = ChunkOf)]
pub struct ChunkRoot(Vec<Entity>);
#[derive(Reflect, Debug, Deref, DerefMut, Clone, Copy, PartialEq, From)]
pub struct LodDistance(pub f32);
impl Default for LodDistance {
fn default() -> Self {
f32::MAX.into()
}
}
impl From<i32> for LodDistance {
fn from(val: i32) -> Self {
LodDistance(val as f32)
}
}
#[derive(Reflect, Debug, Deref, DerefMut, Clone, Default, From)]
pub struct LodDensity(pub f32);
impl From<i32> for LodDensity {
fn from(val: i32) -> Self {
Self(val as f32)
}
}
impl From<usize> for LodDensity {
fn from(val: usize) -> Self {
Self(val as f32)
}
}
#[derive(Component, Reflect, Deref, Debug)]
#[reflect(Component)]
pub struct ChunkSizeScalarConfig(pub Vec<ChunkSizeScalar>);
#[derive(Reflect, Debug, Deref, From)]
pub struct ChunkSizeScalar(pub u32);
impl Default for ChunkSizeScalarConfig {
fn default() -> Self {
Self(
vec![
1.into(),
2.into(),
4.into(),
8.into(),
],
)
}
}
impl ChunkSizeScalarConfig {
pub fn get_size_scalar(&self, level: u32) -> Option<u32> {
self.0.get(level as usize).map(|s| **s)
}
pub fn get_max_lod(&self) -> u32 {
(self.0.len() - 1) as u32
}
pub fn get_scalar_config(&self, level: u32) -> &ChunkSizeScalar {
&self.0[level as usize]
}
}
#[derive(Component, Clone, Reflect, Deref, DerefMut, Debug, PartialEq)]
#[reflect(Component)]
pub struct ChunkLodConfig(pub Vec<LodDistance>);
impl Default for ChunkLodConfig {
fn default() -> Self {
Self(
vec![
60.0.into(),
90.0.into(),
120.0.into(),
default(), ],
)
}
}
impl ChunkLodConfig {
pub fn from_sources(
lod_config: &LodConfig,
size_scalars: &ChunkSizeScalarConfig,
base_size: &BaseChunkSize,
buffer: f32,
) -> Self {
let radii = size_scalars
.iter()
.map(|scalar| base_size.get_chunk_radius(**scalar))
.collect::<Vec<_>>();
lod_config
.distance
.iter()
.enumerate()
.map(|(i, base_dist)| {
if **base_dist == f32::MAX {
return *base_dist;
}
radii
.get(i + 1)
.map(|r_parent|LodDistance(**base_dist + r_parent + buffer))
.unwrap_or_else(|| {
#[cfg(feature = "trace")]
warn!("Failed to calculate LOD distance for chunk at level {}. Using base distance.", i);
LodDistance(**base_dist + buffer)
})
})
.collect::<Vec<_>>().into()
}
}
impl From<Vec<LodDistance>> for ChunkLodConfig {
fn from(value: Vec<LodDistance>) -> Self {
Self(value)
}
}
impl LodConfiguration for ChunkLodConfig {
fn get(&self) -> &Vec<LodDistance> {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
const EPSILON: f32 = 0.0001;
#[test]
fn test_get_chunk_radius_1d_should_get_correct_radius() {
let base_size_1d = BaseChunkSize(Vec3::new(20.0, 0.0, 0.0));
assert!((base_size_1d.get_chunk_radius(1) - 10.0).abs() < EPSILON);
}
#[test]
fn test_get_chunk_radius_3d_should_get_correct_radius() {
let base_size_3d = BaseChunkSize(Vec3::new(10.0, 10.0, 10.0));
let r1 = base_size_3d.get_chunk_radius(1);
assert!((r1 - 8.66025).abs() < EPSILON);
}
#[test]
fn test_get_chunk_radius_should_scale() {
let base_size_3d = BaseChunkSize(Vec3::new(10.0, 10.0, 10.0));
let r1 = base_size_3d.get_chunk_radius(1);
let r2 = base_size_3d.get_chunk_radius(2);
assert!((r2 - 17.3205).abs() < EPSILON);
assert!((r2 - (r1 * 2.0)).abs() < EPSILON);
}
#[test]
fn test_from_sources_standard_should_calc_correctly() {
let base_size = BaseChunkSize(Vec3::new(20.0, 0.0, 0.0));
let size_scalars = ChunkSizeScalarConfig(vec![
1.into(), 2.into(), 4.into(), 8.into(), ]);
let lod_config = LodConfig {
distance: vec![
30.0.into(), 60.0.into(), 120.0.into(), default(), ],
..default()
};
let buffer = 5.0;
let expected_config = ChunkLodConfig(vec![
55.0.into(),
105.0.into(),
205.0.into(),
LodDistance::default(),
]);
let calculated_config =
ChunkLodConfig::from_sources(&lod_config, &size_scalars, &base_size, buffer);
assert_eq!(calculated_config, expected_config);
}
#[test]
fn test_from_sources_should_fallback_when_scalars_too_short() {
let base_size = BaseChunkSize(Vec3::new(20.0, 0.0, 0.0));
let size_scalars = ChunkSizeScalarConfig(vec![
1.into(), 2.into(), ]);
let lod_config = LodConfig {
distance: vec![
30.0.into(), 60.0.into(), 120.0.into(), default(), ],
..default()
};
let buffer = 10.0;
let expected_config = ChunkLodConfig(vec![
60.0.into(),
70.0.into(),
130.0.into(),
LodDistance::default(),
]);
let calculated_config =
ChunkLodConfig::from_sources(&lod_config, &size_scalars, &base_size, buffer);
assert_eq!(calculated_config, expected_config);
}
}