use crate::{
core::{algebra::Vector3, pool::Handle},
graph::SceneGraph,
graphics::{
error::FrameworkError,
query::{GpuQuery, QueryKind, QueryResult},
server::GraphicsServer,
},
scene::{graph::Graph, node::Node},
};
use fxhash::FxHashMap;
use std::fmt::{Debug, Formatter};
struct PendingQuery {
query: GpuQuery,
observer_position: Vector3<f32>,
node: Handle<Node>,
}
impl Debug for PendingQuery {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "pos: {}, node: {}", self.observer_position, self.node)
}
}
#[derive(Debug)]
enum Visibility {
Undefined,
Invisible,
Visible,
}
type NodeVisibilityMap = FxHashMap<Handle<Node>, Visibility>;
#[derive(Debug)]
pub struct ObserverVisibilityCache {
cells: FxHashMap<Vector3<i32>, NodeVisibilityMap>,
pending_queries: Vec<PendingQuery>,
granularity: Vector3<u32>,
distance_discard_threshold: f32,
}
fn world_to_grid(world_position: Vector3<f32>, granularity: Vector3<u32>) -> Vector3<i32> {
Vector3::new(
(world_position.x * (granularity.x as f32)).round() as i32,
(world_position.y * (granularity.y as f32)).round() as i32,
(world_position.z * (granularity.z as f32)).round() as i32,
)
}
fn grid_to_world(grid_position: Vector3<i32>, granularity: Vector3<u32>) -> Vector3<f32> {
Vector3::new(
grid_position.x as f32 / (granularity.x as f32),
grid_position.y as f32 / (granularity.y as f32),
grid_position.z as f32 / (granularity.z as f32),
)
}
impl ObserverVisibilityCache {
pub fn new(granularity: Vector3<u32>, distance_discard_threshold: f32) -> Self {
Self {
cells: Default::default(),
pending_queries: Default::default(),
granularity,
distance_discard_threshold,
}
}
pub fn world_to_grid(&self, world_position: Vector3<f32>) -> Vector3<i32> {
world_to_grid(world_position, self.granularity)
}
pub fn grid_to_world(&self, grid_position: Vector3<i32>) -> Vector3<f32> {
grid_to_world(grid_position, self.granularity)
}
fn visibility_info(
&self,
observer_position: Vector3<f32>,
node: Handle<Node>,
) -> Option<&Visibility> {
let grid_position = self.world_to_grid(observer_position);
self.cells
.get(&grid_position)
.and_then(|cell| cell.get(&node))
}
pub fn needs_occlusion_query(
&self,
observer_position: Vector3<f32>,
node: Handle<Node>,
) -> bool {
let Some(visibility) = self.visibility_info(observer_position, node) else {
return true;
};
match visibility {
Visibility::Undefined => {
false
}
Visibility::Invisible => {
true
}
Visibility::Visible => {
false
}
}
}
pub fn is_visible(&self, observer_position: Vector3<f32>, node: Handle<Node>) -> bool {
let Some(visibility_info) = self.visibility_info(observer_position, node) else {
return false;
};
match *visibility_info {
Visibility::Visible
| Visibility::Undefined => true,
Visibility::Invisible => false,
}
}
pub fn begin_query(
&mut self,
server: &dyn GraphicsServer,
observer_position: Vector3<f32>,
node: Handle<Node>,
) -> Result<(), FrameworkError> {
let query = server.create_query()?;
query.begin(QueryKind::AnySamplesPassed);
self.pending_queries.push(PendingQuery {
query,
observer_position,
node,
});
let grid_position = self.world_to_grid(observer_position);
self.cells
.entry(grid_position)
.or_default()
.entry(node)
.or_insert(Visibility::Undefined);
Ok(())
}
pub fn end_query(&mut self) {
let last_pending_query = self
.pending_queries
.last()
.expect("begin_query/end_query calls mismatch!");
last_pending_query.query.end();
}
pub fn update(&mut self, observer_position: Vector3<f32>) {
self.pending_queries.retain_mut(|pending_query| {
if let Some(QueryResult::AnySamplesPassed(query_result)) =
pending_query.query.try_get_result()
{
let grid_position =
world_to_grid(pending_query.observer_position, self.granularity);
let Some(cell) = self.cells.get_mut(&grid_position) else {
return false;
};
let Some(visibility) = cell.get_mut(&pending_query.node) else {
return false;
};
match visibility {
Visibility::Undefined => match query_result {
true => {
*visibility = Visibility::Visible;
}
false => {
*visibility = Visibility::Invisible;
}
},
Visibility::Invisible => {
if query_result {
*visibility = Visibility::Visible;
}
}
Visibility::Visible => {
}
}
false
} else {
true
}
});
self.cells.retain(|grid_position, _| {
let world_position = grid_to_world(*grid_position, self.granularity);
world_position.metric_distance(&observer_position) < self.distance_discard_threshold
});
}
}
#[derive(Debug)]
struct ObserverData {
position: Vector3<f32>,
visibility_cache: ObserverVisibilityCache,
}
#[derive(Default, Debug)]
pub struct VisibilityCache {
observers: FxHashMap<Handle<Node>, ObserverData>,
}
impl VisibilityCache {
pub fn get_or_register(
&mut self,
graph: &Graph,
observer: Handle<Node>,
) -> &mut ObserverVisibilityCache {
&mut self
.observers
.entry(observer)
.or_insert_with(|| ObserverData {
position: graph[observer].global_position(),
visibility_cache: ObserverVisibilityCache::new(Vector3::repeat(2), 100.0),
})
.visibility_cache
}
pub fn update(&mut self, graph: &Graph) {
self.observers.retain(|observer, data| {
let Ok(observer_ref) = graph.try_get_node(*observer) else {
return false;
};
data.position = observer_ref.global_position();
data.visibility_cache.update(data.position);
true
});
}
}