Skip to main content

big_space_solari/
lib.rs

1//! Resets [`bevy_solari`] (and optionally DLSS) ray-traced lighting temporal
2//! history when a [`big_space`] floating origin recenters.
3//!
4//! When a `big_space` floating origin crosses into a new cell, the rendering origin
5//! shifts and every entity's global transform translates rigidly by the same delta.
6//! Bevy's motion-vector prepass captures previous view and previous mesh transforms
7//! together, so this rigid shift cancels in screen space and does not corrupt motion
8//! vectors. It does, however, invalidate temporal techniques that key history by
9//! absolute world position: ray-traced GI reservoirs, world radiance caches, and
10//! DLSS history all see every stored position jump by the recenter delta in a single
11//! frame, and reject last frame's history as dissimilar.
12//!
13//! [`BigSpaceSolariPlugin`] detects each recenter and sets
14//! [`SolariLighting::reset`](bevy_solari::prelude::SolariLighting::reset) so the
15//! history restarts cleanly for the one frame it takes to re-converge. Enable the
16//! `dlss` feature to also reset DLSS Ray Reconstruction.
17//!
18//! Enable the `scale-bias` feature for [`SolariRayBiasPlugin`], which additionally
19//! overrides `bevy_solari`'s room-scale ray interval so ray-traced geometry stays
20//! artifact-free at true (large-coordinate) scale.
21
22use std::collections::HashMap;
23
24use bevy_app::prelude::*;
25use bevy_ecs::prelude::*;
26use bevy_solari::prelude::SolariLighting;
27use big_space::prelude::{BigSpace, BigSpaceSystems, CellCoord};
28
29#[cfg(feature = "dlss")]
30use bevy_anti_alias::dlss::{Dlss, DlssRayReconstructionFeature};
31
32#[cfg(feature = "scale-bias")]
33mod ray_bias;
34#[cfg(feature = "scale-bias")]
35pub use ray_bias::{SolariRayBias, SolariRayBiasPlugin};
36
37/// Resets ray-traced lighting temporal history on each floating-origin recenter.
38pub struct BigSpaceSolariPlugin;
39
40impl Plugin for BigSpaceSolariPlugin {
41    fn build(&self, app: &mut App) {
42        app.add_systems(
43            PostUpdate,
44            reset_on_recenter.after(BigSpaceSystems::RecenterLargeTransforms),
45        );
46    }
47}
48
49fn reset_on_recenter(
50    mut last: Local<HashMap<Entity, CellCoord>>,
51    spaces: Query<(Entity, &BigSpace)>,
52    origins: Query<&CellCoord>,
53    mut solari: Query<&mut SolariLighting>,
54    #[cfg(feature = "dlss")] mut dlss: Query<&mut Dlss<DlssRayReconstructionFeature>>,
55) {
56    let mut recentered = false;
57    for (root, space) in &spaces {
58        let Some(origin) = space.floating_origin else {
59            continue;
60        };
61        let Ok(cell) = origins.get(origin) else {
62            continue;
63        };
64        if recentered_since_last(&mut last, root, *cell) {
65            recentered = true;
66        }
67    }
68
69    if !recentered {
70        return;
71    }
72
73    for mut solari in &mut solari {
74        solari.reset = true;
75    }
76    #[cfg(feature = "dlss")]
77    for mut dlss in &mut dlss {
78        dlss.reset = true;
79    }
80}
81
82fn recentered_since_last(
83    last: &mut HashMap<Entity, CellCoord>,
84    root: Entity,
85    cell: CellCoord,
86) -> bool {
87    last.insert(root, cell).is_some_and(|prev| prev != cell)
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn reset_fires_only_on_a_changed_cell() {
96        let mut world = World::new();
97        let a = world.spawn_empty().id();
98        let b = world.spawn_empty().id();
99
100        let mut last = HashMap::new();
101        let origin = CellCoord::new(0, 0, 0);
102        let moved = CellCoord::new(0, 1, 0);
103
104        assert!(!recentered_since_last(&mut last, a, origin));
105        assert!(!recentered_since_last(&mut last, a, origin));
106        assert!(recentered_since_last(&mut last, a, moved));
107        assert!(!recentered_since_last(&mut last, b, origin));
108    }
109}