rg3d/scene/visibility.rs
1//! Visibility cache stores information about objects visibility for a single frame.
2//!
3//! For more info see [`VisibilityCache`]
4
5use crate::{
6 core::{algebra::Vector3, math::frustum::Frustum, pool::Handle},
7 scene::{graph::Graph, node::Node},
8};
9use fxhash::FxHashMap;
10
11/// Visibility cache stores information about objects visibility for a single frame. Allows you to quickly check
12/// if an object is visible or not.
13///
14/// # Notes
15///
16/// Visibility cache stores very coarse information about object visibility, it does not include any kind of occlusion
17/// tests of whatsoever. It just a simple frustum test + level-of-detail (LOD) system.
18///
19/// LODs have priority over other visibility options, if a level is not active, then its every object will be hidden,
20/// not matter if the actual visibility state is `visible`.
21///
22/// # Performance
23///
24/// The cache is based on hash map, so it is very fast and has O(1) complexity for fetching.
25#[derive(Default, Debug)]
26pub struct VisibilityCache {
27 map: FxHashMap<Handle<Node>, bool>,
28}
29
30impl From<FxHashMap<Handle<Node>, bool>> for VisibilityCache {
31 fn from(map: FxHashMap<Handle<Node>, bool>) -> Self {
32 Self { map }
33 }
34}
35
36impl VisibilityCache {
37 /// Replaces internal map with empty and returns previous value. This trick is useful
38 /// to reuse hash map to prevent redundant memory allocations.
39 pub fn invalidate(&mut self) -> FxHashMap<Handle<Node>, bool> {
40 std::mem::take(&mut self.map)
41 }
42
43 /// Updates visibility cache - checks visibility for each node in given graph, also performs
44 /// frustum culling if frustum set is specified.
45 pub fn update(
46 &mut self,
47 graph: &Graph,
48 observer_position: Vector3<f32>,
49 z_near: f32,
50 z_far: f32,
51 frustums: Option<&[&Frustum]>,
52 ) {
53 self.map.clear();
54
55 // Check LODs first, it has priority over other visibility settings.
56 for node in graph.linear_iter() {
57 if let Some(lod_group) = node.lod_group() {
58 for level in lod_group.levels.iter() {
59 for &object in level.objects.iter() {
60 if let Some(object_ref) = graph.try_get(*object) {
61 let distance =
62 observer_position.metric_distance(&object_ref.global_position());
63 let z_range = z_far - z_near;
64 let normalized_distance = (distance - z_near) / z_range;
65 let visible = normalized_distance >= level.begin()
66 && normalized_distance <= level.end();
67 self.map.insert(*object, visible);
68 }
69 }
70 }
71 }
72 }
73
74 // Fill rest of data from global visibility flag of nodes and check frustums (if any).
75 for (handle, node) in graph.pair_iter() {
76 // We need to fill only unfilled entries, none of visibility flags of a node can
77 // make it visible again if lod group hid it.
78 self.map.entry(handle).or_insert_with(|| {
79 let mut visibility = node.global_visibility();
80 if visibility && node.frustum_culling() {
81 // If a node globally visible, check it with each frustum (if any).
82 if let Some(frustums) = frustums {
83 let mut visible_by_any_frustum = false;
84 for frustum in frustums {
85 if frustum.is_intersects_aabb(&node.world_bounding_box()) {
86 visible_by_any_frustum = true;
87 break;
88 }
89 }
90 visibility = visible_by_any_frustum;
91 }
92 }
93 visibility
94 });
95 }
96 }
97
98 /// Checks whether the node is visible or not.
99 ///
100 /// # Complexity
101 ///
102 /// Constant, O(1)
103 pub fn is_visible(&self, node: Handle<Node>) -> bool {
104 self.map.get(&node).cloned().unwrap_or(false)
105 }
106}