Skip to main content

fyrox_impl/renderer/
visibility.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Volumetric visibility cache based on occlusion query.
22
23use crate::{
24    core::{algebra::Vector3, pool::Handle},
25    graph::SceneGraph,
26    graphics::{
27        error::FrameworkError,
28        query::{GpuQuery, QueryKind, QueryResult},
29        server::GraphicsServer,
30    },
31    scene::{graph::Graph, node::Node},
32};
33use fxhash::FxHashMap;
34use std::fmt::{Debug, Formatter};
35
36struct PendingQuery {
37    query: GpuQuery,
38    observer_position: Vector3<f32>,
39    node: Handle<Node>,
40}
41
42impl Debug for PendingQuery {
43    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44        write!(f, "pos: {}, node: {}", self.observer_position, self.node)
45    }
46}
47
48#[derive(Debug)]
49enum Visibility {
50    Undefined,
51    Invisible,
52    Visible,
53}
54
55type NodeVisibilityMap = FxHashMap<Handle<Node>, Visibility>;
56
57/// Volumetric visibility cache based on occlusion query.
58#[derive(Debug)]
59pub struct ObserverVisibilityCache {
60    cells: FxHashMap<Vector3<i32>, NodeVisibilityMap>,
61    pending_queries: Vec<PendingQuery>,
62    granularity: Vector3<u32>,
63    distance_discard_threshold: f32,
64}
65
66fn world_to_grid(world_position: Vector3<f32>, granularity: Vector3<u32>) -> Vector3<i32> {
67    Vector3::new(
68        (world_position.x * (granularity.x as f32)).round() as i32,
69        (world_position.y * (granularity.y as f32)).round() as i32,
70        (world_position.z * (granularity.z as f32)).round() as i32,
71    )
72}
73
74fn grid_to_world(grid_position: Vector3<i32>, granularity: Vector3<u32>) -> Vector3<f32> {
75    Vector3::new(
76        grid_position.x as f32 / (granularity.x as f32),
77        grid_position.y as f32 / (granularity.y as f32),
78        grid_position.z as f32 / (granularity.z as f32),
79    )
80}
81
82impl ObserverVisibilityCache {
83    /// Creates new visibility cache with the given granularity and distance discard threshold.
84    /// Granularity in means how much the cache should subdivide the world. For example 2 means that
85    /// 1 meter cell will be split into 8 blocks by 0.5 meters. Distance discard threshold means how
86    /// far an observer can without discarding visibility info about distant objects.
87    pub fn new(granularity: Vector3<u32>, distance_discard_threshold: f32) -> Self {
88        Self {
89            cells: Default::default(),
90            pending_queries: Default::default(),
91            granularity,
92            distance_discard_threshold,
93        }
94    }
95
96    /// Transforms the given world-space position into internal grid-space position.
97    pub fn world_to_grid(&self, world_position: Vector3<f32>) -> Vector3<i32> {
98        world_to_grid(world_position, self.granularity)
99    }
100
101    /// Transforms the given grid-space position into the world-space position.
102    pub fn grid_to_world(&self, grid_position: Vector3<i32>) -> Vector3<f32> {
103        grid_to_world(grid_position, self.granularity)
104    }
105
106    fn visibility_info(
107        &self,
108        observer_position: Vector3<f32>,
109        node: Handle<Node>,
110    ) -> Option<&Visibility> {
111        let grid_position = self.world_to_grid(observer_position);
112
113        self.cells
114            .get(&grid_position)
115            .and_then(|cell| cell.get(&node))
116    }
117
118    /// Checks whether the given object needs an occlusion query for the given observer position.
119    pub fn needs_occlusion_query(
120        &self,
121        observer_position: Vector3<f32>,
122        node: Handle<Node>,
123    ) -> bool {
124        let Some(visibility) = self.visibility_info(observer_position, node) else {
125            // There's no data about the visibility, so the occlusion query is needed.
126            return true;
127        };
128
129        match visibility {
130            Visibility::Undefined => {
131                // There's already an occlusion query on GPU.
132                false
133            }
134            Visibility::Invisible => {
135                // The object could be invisible from one angle at the observer position, but visible
136                // from another. Since we're using only position of the observer, we cannot be 100%
137                // sure, that the object is invisible even if a previous query told us so.
138                true
139            }
140            Visibility::Visible => {
141                // Some pixels of the object is visible from the given observer position, so we don't
142                // need a new occlusion query.
143                false
144            }
145        }
146    }
147
148    /// Checks whether the object at the given handle is visible from the given observer position.
149    /// This method returns `true` for non-completed occlusion queries, because occlusion query is
150    /// async operation.
151    pub fn is_visible(&self, observer_position: Vector3<f32>, node: Handle<Node>) -> bool {
152        let Some(visibility_info) = self.visibility_info(observer_position, node) else {
153            return false;
154        };
155
156        match *visibility_info {
157            Visibility::Visible
158            // Undefined visibility is treated like the object is visible, this is needed because
159            // GPU queries are async, and we must still render the object to prevent popping light.
160            | Visibility::Undefined => true,
161            Visibility::Invisible => false,
162        }
163    }
164
165    /// Begins a new visibility query (using occlusion query) for the object at the given handle from
166    /// the given observer position.
167    pub fn begin_query(
168        &mut self,
169        server: &dyn GraphicsServer,
170        observer_position: Vector3<f32>,
171        node: Handle<Node>,
172    ) -> Result<(), FrameworkError> {
173        let query = server.create_query()?;
174        query.begin(QueryKind::AnySamplesPassed);
175        self.pending_queries.push(PendingQuery {
176            query,
177            observer_position,
178            node,
179        });
180
181        let grid_position = self.world_to_grid(observer_position);
182        self.cells
183            .entry(grid_position)
184            .or_default()
185            .entry(node)
186            .or_insert(Visibility::Undefined);
187
188        Ok(())
189    }
190
191    /// Ends the last visibility query.
192    pub fn end_query(&mut self) {
193        let last_pending_query = self
194            .pending_queries
195            .last()
196            .expect("begin_query/end_query calls mismatch!");
197        last_pending_query.query.end();
198    }
199
200    /// This method removes info about too distant objects and processes the pending visibility queries.
201    pub fn update(&mut self, observer_position: Vector3<f32>) {
202        self.pending_queries.retain_mut(|pending_query| {
203            if let Some(QueryResult::AnySamplesPassed(query_result)) =
204                pending_query.query.try_get_result()
205            {
206                let grid_position =
207                    world_to_grid(pending_query.observer_position, self.granularity);
208
209                let Some(cell) = self.cells.get_mut(&grid_position) else {
210                    return false;
211                };
212
213                let Some(visibility) = cell.get_mut(&pending_query.node) else {
214                    return false;
215                };
216
217                match visibility {
218                    Visibility::Undefined => match query_result {
219                        true => {
220                            *visibility = Visibility::Visible;
221                        }
222                        false => {
223                            *visibility = Visibility::Invisible;
224                        }
225                    },
226                    Visibility::Invisible => {
227                        if query_result {
228                            // Override "invisibility" - if any fragment of an object is visible, then
229                            // it will remain visible forever. This is ok for non-moving objects only.
230                            *visibility = Visibility::Visible;
231                        }
232                    }
233                    Visibility::Visible => {
234                        // Ignore the query result and keep the visibility.
235                    }
236                }
237
238                false
239            } else {
240                true
241            }
242        });
243
244        // Remove visibility info from the cache for distant cells.
245        self.cells.retain(|grid_position, _| {
246            let world_position = grid_to_world(*grid_position, self.granularity);
247
248            world_position.metric_distance(&observer_position) < self.distance_discard_threshold
249        });
250    }
251}
252
253#[derive(Debug)]
254struct ObserverData {
255    position: Vector3<f32>,
256    visibility_cache: ObserverVisibilityCache,
257}
258
259/// Visibility cache that caches visibility info for multiple cameras.
260#[derive(Default, Debug)]
261pub struct VisibilityCache {
262    observers: FxHashMap<Handle<Node>, ObserverData>,
263}
264
265impl VisibilityCache {
266    /// Gets or adds new storage for the given observer.
267    pub fn get_or_register(
268        &mut self,
269        graph: &Graph,
270        observer: Handle<Node>,
271    ) -> &mut ObserverVisibilityCache {
272        &mut self
273            .observers
274            .entry(observer)
275            .or_insert_with(|| ObserverData {
276                position: graph[observer].global_position(),
277                visibility_cache: ObserverVisibilityCache::new(Vector3::repeat(2), 100.0),
278            })
279            .visibility_cache
280    }
281
282    /// Updates the cache by removing unused data.
283    pub fn update(&mut self, graph: &Graph) {
284        self.observers.retain(|observer, data| {
285            let Ok(observer_ref) = graph.try_get_node(*observer) else {
286                return false;
287            };
288
289            data.position = observer_ref.global_position();
290
291            data.visibility_cache.update(data.position);
292
293            true
294        });
295    }
296}