1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use egui::NumExt as _;
use nohash_hasher::IntMap;
use re_log_types::EntityPathHash;
use re_viewer_context::VisualizerCollection;

use crate::{view_kind::SpatialSpaceViewKind, visualizers::SpatialViewVisualizerData};

#[derive(Clone)]
pub struct SceneBoundingBoxes {
    /// Overall bounding box of the scene for the current query.
    pub current: re_math::BoundingBox,

    /// A bounding box that smoothly transitions to the current bounding box.
    ///
    /// If discontinuities are detected, this bounding box will be reset immediately to the current bounding box.
    pub smoothed: re_math::BoundingBox,

    /// Per-entity bounding boxes for the current query.
    pub per_entity: IntMap<EntityPathHash, re_math::BoundingBox>,
}

impl Default for SceneBoundingBoxes {
    fn default() -> Self {
        Self {
            current: re_math::BoundingBox::NOTHING,
            smoothed: re_math::BoundingBox::NOTHING,
            per_entity: IntMap::default(),
        }
    }
}

impl SceneBoundingBoxes {
    pub fn update(
        &mut self,
        ui: &egui::Ui,
        visualizers: &VisualizerCollection,
        space_kind: SpatialSpaceViewKind,
    ) {
        re_tracing::profile_function!();

        let previous = self.current;
        self.current = re_math::BoundingBox::NOTHING;
        self.per_entity.clear();

        for data in visualizers.iter_visualizer_data::<SpatialViewVisualizerData>() {
            // If we're in a 3D space, but the visualizer is distintivly 2D, don't count it towards the bounding box.
            // These visualizers show up when we're on a pinhole camera plane which itself is heuristically fed by the
            // bounding box, creating a feedback loop if we were to add it here.
            let data_is_only_2d = data
                .preferred_view_kind
                .map_or(false, |kind| kind == SpatialSpaceViewKind::TwoD);
            if space_kind == SpatialSpaceViewKind::ThreeD && data_is_only_2d {
                continue;
            }

            for (entity, bbox) in &data.bounding_boxes {
                self.per_entity
                    .entry(*entity)
                    .and_modify(|bbox_entry| *bbox_entry = bbox_entry.union(*bbox))
                    .or_insert(*bbox);
            }
        }

        for bbox in self.per_entity.values() {
            self.current = self.current.union(*bbox);
        }

        // Update smoothed bounding box.
        let discontinuity = detect_boundingbox_discontinuity(self.current, previous);
        if !self.smoothed.is_finite() || self.smoothed.is_nothing() || discontinuity {
            // Reset the smoothed bounding box if it's not valid or we detect a discontinuity.
            self.smoothed = self.current;
        } else {
            let dt = ui.input(|input| input.stable_dt.at_most(0.1));

            // Smooth the bounding box by moving center & size towards the current bounding box.
            let reach_this_factor = 0.9;
            let in_this_many_seconds = 0.2;
            let smoothing_factor =
                egui::emath::exponential_smooth_factor(reach_this_factor, in_this_many_seconds, dt);

            let current_center = self.current.center();
            let current_size = self.current.size();

            let new_smoothed_center = self
                .smoothed
                .center()
                .lerp(current_center, smoothing_factor);
            let new_smoothed_size = self.smoothed.size().lerp(current_size, smoothing_factor);

            self.smoothed =
                re_math::BoundingBox::from_center_size(new_smoothed_center, new_smoothed_size);

            let current_diagonal_length = current_size.length();
            let sameness_threshold = current_diagonal_length * (0.1 / 100.0); // 0.1% of the diagonal.
            if new_smoothed_center.distance(current_center) > sameness_threshold
                || (new_smoothed_size.length() - current_diagonal_length) / current_diagonal_length
                    > sameness_threshold
            {
                ui.ctx().request_repaint();
            }
        }
    }
}

fn detect_boundingbox_discontinuity(
    current: re_math::BoundingBox,
    previous: re_math::BoundingBox,
) -> bool {
    if !previous.is_finite() {
        // Previous bounding box is not finite, so we can't compare.
        return true;
    }

    // Is the size jumping a lot?
    let current_size = current.size().length();
    let previous_size = previous.size().length();
    let size_change = (current_size - previous_size).abs();
    let size_change_ratio = size_change / previous_size;
    if size_change_ratio > 0.5 {
        // Box size change by more than 50% since the previous frame.
        return true;
    }

    // Did the center jump?
    let current_center = current.center();
    let previous_center = previous.center();
    let center_change = current_center.distance(previous_center);
    let center_change_ratio = center_change / previous_size;
    if center_change_ratio > 0.5 {
        // Center change by more than 50% of the previous size since the previous frame.
        return true;
    }

    false
}