use std::marker::PhantomData;
use bevy::prelude::*;
use bevy::reflect::TypePath;
use crate::prelude::{Region, VoxelChunk, VoxelWorld};
#[derive(Default)]
pub struct ChunkAnchorPlugin<T>
where
T: Send + Sync + Default + TypePath,
{
_phantom: PhantomData<T>,
}
impl<T> Plugin for ChunkAnchorPlugin<T>
where
T: Send + Sync + Default + TypePath + 'static,
{
fn build(&self, app: &mut App) {
app.register_type::<ChunkAnchor<T>>()
.register_type::<ChunkAnchorRecipient<T>>()
.add_systems(
PostUpdate,
(
(clear_coords_without_transform::<T>, update_coords::<T>)
.in_set(ChunkAnchorSet::UpdateCoords),
update_chunk_priorities::<T>.in_set(ChunkAnchorSet::UpdatePriorities),
attach_chunk_recipient_comp::<T>.in_set(ChunkAnchorSet::AttachChunkComponents),
),
)
.configure_set(
PostUpdate,
ChunkAnchorSet::UpdateCoords.before(ChunkAnchorSet::UpdatePriorities),
);
}
}
#[derive(Debug, SystemSet, PartialEq, Eq, Hash, Clone, Copy)]
pub enum ChunkAnchorSet {
UpdateCoords,
UpdatePriorities,
AttachChunkComponents,
}
#[derive(Debug, Reflect, Component, Clone)]
pub struct ChunkAnchor<T>
where
T: Send + Sync,
{
#[reflect(ignore)]
_phantom: PhantomData<T>,
pub radius: UVec3,
pub weight: f32,
pub dir_bias: Vec3,
pub world_id: Entity,
pub coords: Option<IVec3>,
}
impl<T> ChunkAnchor<T>
where
T: Send + Sync,
{
pub fn new(world_id: Entity, radius: UVec3) -> Self {
Self {
_phantom: PhantomData,
radius,
weight: 1.0,
dir_bias: Vec3::ZERO,
world_id,
coords: None,
}
}
pub fn get_priority(&self, target: IVec3) -> Option<f32> {
let Some(coords) = self.coords else {
return None;
};
let delta = (coords - target).abs().as_uvec3();
let radius = self.radius;
if delta.x > radius.x || delta.y > radius.y || delta.z > radius.z {
return None;
};
let a = coords.as_vec3();
let b = target.as_vec3();
let distance = a.distance(b);
let view_dir = (b - a).normalize_or_zero();
let weight = view_dir.dot(self.dir_bias);
let priority = (-distance + weight) * self.weight;
Some(priority)
}
pub fn get_region(&self) -> Option<Region> {
let Some(coords) = self.coords else {
return None;
};
let radius = self.radius.as_ivec3();
Some(Region::from_points(coords - radius, coords + radius))
}
}
#[derive(Debug, Default, Reflect, Component, Clone)]
pub struct ChunkAnchorRecipient<T>
where
T: Send + Sync,
{
#[reflect(ignore)]
_phantom: PhantomData<T>,
pub priority: Option<f32>,
}
pub(crate) fn clear_coords_without_transform<T>(
mut anchors: Query<&mut ChunkAnchor<T>, Without<GlobalTransform>>,
) where
T: Send + Sync + 'static,
{
for mut anchor in anchors.iter_mut() {
anchor.coords = None;
}
}
pub(crate) fn update_coords<T>(
worlds: Query<&GlobalTransform, With<VoxelWorld>>,
mut anchors: Query<(&mut ChunkAnchor<T>, &GlobalTransform)>,
) where
T: Send + Sync + 'static,
{
anchors
.par_iter_mut()
.for_each_mut(|(mut anchor, anchor_transform)| {
let Ok(world_transform) = worlds.get(anchor.world_id) else {
anchor.coords = None;
return;
};
anchor.coords = Some(
anchor_transform
.reparented_to(world_transform)
.translation
.as_ivec3()
>> 4,
);
});
}
pub(crate) fn update_chunk_priorities<T>(
anchors: Query<&ChunkAnchor<T>>,
mut chunks: Query<(&mut ChunkAnchorRecipient<T>, &VoxelChunk)>,
) where
T: Send + Sync + 'static,
{
chunks
.par_iter_mut()
.for_each_mut(|(mut anchor_recipient, chunk_meta)| {
anchor_recipient.priority = None;
for anchor in anchors.iter() {
if anchor.world_id != chunk_meta.world_id() {
continue;
}
let Some(priority) = anchor.get_priority(chunk_meta.chunk_coords()) else {
continue;
};
anchor_recipient.priority = Some(match anchor_recipient.priority {
Some(old_priority) => f32::max(priority, old_priority),
None => priority,
});
}
});
}
pub(crate) fn attach_chunk_recipient_comp<T>(
new_chunks: Query<Entity, (With<VoxelChunk>, Without<ChunkAnchorRecipient<T>>)>,
mut commands: Commands,
) where
T: Send + Sync + Default + 'static,
{
for chunk_id in new_chunks.iter() {
commands
.entity(chunk_id)
.insert(ChunkAnchorRecipient::<T>::default());
}
}