Skip to content

Commit 1989951

Browse files
authored
Double click on entity in blueprint & timepanel focuses camera on it in 3D space views (#4799)
### What * Fixes #918 * Fixes #917 This PR introduces on-the-fly determined per-entity bounding boxes and unifies a lot of behavior under the hood: A double click of a hovered item (pretty much everywhere) causes now a "focused" event for that item. Focuses are right now set on the viewer context for a single frame, so each part of the viewer can react on it as it pleases. The 3D Space view reacts by focusing the camera on the entity (using the new bounding box) and tracks cameras. Tracking of entities has been unified - originally this PR tracked entities as well, but I found this a bit too confusing for this action, we should instead have something more explicit (this was disabled in 86ddf0d so it's easy to get it back). https://github.com/rerun-io/rerun/assets/1220815/b6077865-d500-4bc9-b112-8ff41b370fe0 Tradeoffs: * can't focus on instances -> before focused on 3D positions (without knowing anything about the object under it) now focusing on entities; different behavior, _sometimes_ worse, sometimes better -> note that tracking an instance rarely what you want here (e.g. point instance ids vary widely) * tracking an entity is not always what you want -> disabled it * it's very hard to tell that we're tracking something, can sometimes be surprising * Alternative design of `focus`: Send a focus event to space views that should be interested in it. This makes would unify the dispatching code for this which is nicer going forward. But for the moment it was just a lot easier to implement it the way I did. I'm fairly sure though about making focus not sticky in the global state since this way we'd end up with two levels of selection. ### Checklist * [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) * [x] I've included a screenshot or gif (if applicable) * [x] I have tested the web demo (if applicable): * Using newly built examples: [app.rerun.io](https://app.rerun.io/pr/4799/index.html) * Using examples from latest `main` build: [app.rerun.io](https://app.rerun.io/pr/4799/index.html?manifest_url=https://app.rerun.io/version/main/examples_manifest.json) * Using full set of examples from `nightly` build: [app.rerun.io](https://app.rerun.io/pr/4799/index.html?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json) * [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG - [PR Build Summary](https://build.rerun.io/pr/4799) - [Docs preview](https://rerun.io/preview/7183423673682ce3d9c0c68fcb335689b8c13693/docs) <!--DOCS-PREVIEW--> - [Examples preview](https://rerun.io/preview/7183423673682ce3d9c0c68fcb335689b8c13693/examples) <!--EXAMPLES-PREVIEW--> - [Recent benchmark results](https://build.rerun.io/graphs/crates.html) - [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)
1 parent 8cd526a commit 1989951

31 files changed

Lines changed: 428 additions & 231 deletions

crates/re_data_ui/src/item_ui.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use re_entity_db::{EntityTree, InstancePath};
66
use re_log_types::{ComponentPath, EntityPath, TimeInt, Timeline};
77
use re_ui::SyntaxHighlighting;
88
use re_viewer_context::{
9-
DataQueryId, HoverHighlight, Item, Selection, SpaceViewId, UiVerbosity, ViewerContext,
9+
DataQueryId, HoverHighlight, Item, Selection, SpaceViewId, SystemCommandSender, UiVerbosity,
10+
ViewerContext,
1011
};
1112

1213
use super::DataUi;
@@ -339,6 +340,13 @@ pub fn select_hovered_on_click(
339340
selection_state.set_hovered(selection.clone());
340341
}
341342

343+
if response.double_clicked() {
344+
if let Some((item, _)) = selection.first() {
345+
ctx.command_sender
346+
.send_system(re_viewer_context::SystemCommand::SetFocus(item.clone()));
347+
}
348+
}
349+
342350
if response.clicked() {
343351
if response.ctx.input(|i| i.modifiers.command) {
344352
selection_state.toggle_selection(selection);

crates/re_space_view/src/controls.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub const ROLL_MOUSE_MODIFIER: egui::Modifiers = egui::Modifiers::ALT;
3838
pub const SPEED_UP_3D_MODIFIER: egui::Modifiers = egui::Modifiers::SHIFT;
3939

4040
/// Key to restore the camera.
41-
pub const TRACKED_CAMERA_RESTORE_KEY: egui::Key = egui::Key::Escape;
41+
pub const TRACKED_OBJECT_RESTORE_KEY: egui::Key = egui::Key::Escape;
4242

4343
/// Description text for which action resets a space view.
4444
pub const RESET_VIEW_BUTTON_TEXT: &str = "double click";

crates/re_space_view_spatial/src/eye.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ impl OrbitEye {
204204
// The hard part is finding a good center. Let's try to keep the same, and see how that goes:
205205
let distance = eye
206206
.forward_in_world()
207-
.dot(self.orbit_center - eye.pos_in_world());
207+
.dot(self.orbit_center - eye.pos_in_world())
208+
.abs();
208209
self.orbit_radius = distance.at_least(self.orbit_radius / 5.0);
209210
self.orbit_center = eye.pos_in_world() + self.orbit_radius * eye.forward_in_world();
210211
self.world_from_view_rot = eye.world_from_rub_view.rotation();

crates/re_space_view_spatial/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod instance_hash_conversions;
99
mod mesh_cache;
1010
mod mesh_loader;
1111
mod picking;
12+
mod scene_bounding_boxes;
1213
mod space_camera_3d;
1314
mod space_view_2d;
1415
mod space_view_3d;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use nohash_hasher::IntMap;
2+
use re_log_types::EntityPathHash;
3+
use re_viewer_context::VisualizerCollection;
4+
5+
use crate::visualizers::SpatialViewVisualizerData;
6+
7+
#[derive(Clone)]
8+
pub struct SceneBoundingBoxes {
9+
/// Accumulated bounding box over several frames.
10+
pub accumulated: macaw::BoundingBox,
11+
12+
/// Overall bounding box of the scene for the current query.
13+
pub current: macaw::BoundingBox,
14+
15+
/// Per-entity bounding boxes for the current query.
16+
pub per_entity: IntMap<EntityPathHash, macaw::BoundingBox>,
17+
}
18+
19+
impl Default for SceneBoundingBoxes {
20+
fn default() -> Self {
21+
Self {
22+
accumulated: macaw::BoundingBox::nothing(),
23+
current: macaw::BoundingBox::nothing(),
24+
per_entity: IntMap::default(),
25+
}
26+
}
27+
}
28+
29+
impl SceneBoundingBoxes {
30+
pub fn update(&mut self, visualizers: &VisualizerCollection) {
31+
re_tracing::profile_function!();
32+
33+
self.current = macaw::BoundingBox::nothing();
34+
self.per_entity.clear();
35+
36+
for visualizer in visualizers.iter() {
37+
if let Some(data) = visualizer
38+
.data()
39+
.and_then(|d| d.downcast_ref::<SpatialViewVisualizerData>())
40+
{
41+
for (entity, bbox) in &data.bounding_boxes {
42+
self.per_entity
43+
.entry(*entity)
44+
.and_modify(|bbox_entry| *bbox_entry = bbox_entry.union(*bbox))
45+
.or_insert(*bbox);
46+
}
47+
}
48+
}
49+
50+
for bbox in self.per_entity.values() {
51+
self.current = self.current.union(*bbox);
52+
}
53+
54+
if self.accumulated.is_nothing() || !self.accumulated.size().is_finite() {
55+
self.accumulated = self.current;
56+
} else {
57+
self.accumulated = self.accumulated.union(self.current);
58+
}
59+
}
60+
}

crates/re_space_view_spatial/src/space_view_2d.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ use crate::{
1212
heuristics::{auto_spawn_heuristic, update_object_property_heuristics},
1313
ui::SpatialSpaceViewState,
1414
view_kind::SpatialSpaceViewKind,
15-
visualizers::{
16-
calculate_bounding_box, register_2d_spatial_visualizers, SpatialViewVisualizerData,
17-
},
15+
visualizers::{register_2d_spatial_visualizers, SpatialViewVisualizerData},
1816
};
1917

2018
// TODO(#4388): 2D/3D relationships should be solved via a "SpatialTopology" construct.
@@ -60,7 +58,7 @@ impl SpaceViewClass for SpatialSpaceView2D {
6058
}
6159

6260
fn preferred_tile_aspect_ratio(&self, state: &Self::State) -> Option<f32> {
63-
let size = state.scene_bbox_accum.size();
61+
let size = state.bounding_boxes.accumulated.size();
6462
Some(size.x / size.y)
6563
}
6664

@@ -123,7 +121,7 @@ impl SpaceViewClass for SpatialSpaceView2D {
123121
ctx,
124122
ent_paths,
125123
entity_properties,
126-
&state.scene_bbox_accum,
124+
&state.bounding_boxes.accumulated,
127125
SpatialSpaceViewKind::TwoD,
128126
);
129127
}
@@ -205,8 +203,9 @@ impl SpaceViewClass for SpatialSpaceView2D {
205203
query: &ViewQuery<'_>,
206204
system_output: re_viewer_context::SystemExecutionOutput,
207205
) -> Result<(), SpaceViewSystemExecutionError> {
208-
state.scene_bbox =
209-
calculate_bounding_box(&system_output.view_systems, &mut state.scene_bbox_accum);
206+
re_tracing::profile_function!();
207+
208+
state.bounding_boxes.update(&system_output.view_systems);
210209
state.scene_num_primitives = system_output
211210
.context_systems
212211
.get::<PrimitiveCounter>()?

crates/re_space_view_spatial/src/space_view_3d.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
heuristics::{auto_spawn_heuristic, update_object_property_heuristics},
1414
ui::SpatialSpaceViewState,
1515
view_kind::SpatialSpaceViewKind,
16-
visualizers::{calculate_bounding_box, register_3d_spatial_visualizers, CamerasVisualizer},
16+
visualizers::{register_3d_spatial_visualizers, CamerasVisualizer},
1717
};
1818

1919
// TODO(andreas): This context is used to determine whether a 2D entity has a valid transform
@@ -157,7 +157,7 @@ impl SpaceViewClass for SpatialSpaceView3D {
157157
ctx,
158158
ent_paths,
159159
entity_properties,
160-
&state.scene_bbox_accum,
160+
&state.bounding_boxes.accumulated,
161161
SpatialSpaceViewKind::ThreeD,
162162
);
163163
}
@@ -185,8 +185,7 @@ impl SpaceViewClass for SpatialSpaceView3D {
185185
) -> Result<(), SpaceViewSystemExecutionError> {
186186
re_tracing::profile_function!();
187187

188-
state.scene_bbox =
189-
calculate_bounding_box(&system_output.view_systems, &mut state.scene_bbox_accum);
188+
state.bounding_boxes.update(&system_output.view_systems);
190189
state.scene_num_primitives = system_output
191190
.context_systems
192191
.get::<PrimitiveCounter>()?

crates/re_space_view_spatial/src/ui.rs

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use re_viewer_context::{
1717

1818
use super::{eye::Eye, ui_2d::View2DState, ui_3d::View3DState};
1919
use crate::heuristics::auto_size_world_heuristic;
20+
use crate::scene_bounding_boxes::SceneBoundingBoxes;
2021
use crate::{
2122
contexts::{AnnotationSceneContext, NonInteractiveEntities},
2223
picking::{PickableUiRect, PickingContext, PickingHitType, PickingResult},
@@ -48,15 +49,9 @@ impl From<AutoSizeUnit> for WidgetText {
4849
}
4950

5051
/// TODO(andreas): Should turn this "inside out" - [`SpatialSpaceViewState`] should be used by [`View2DState`]/[`View3DState`], not the other way round.
51-
#[derive(Clone)]
52+
#[derive(Clone, Default)]
5253
pub struct SpatialSpaceViewState {
53-
/// Estimated bounding box of all data. Accumulated over every time data is displayed.
54-
///
55-
/// Specify default explicitly, otherwise it will be a box at 0.0 after deserialization.
56-
pub scene_bbox_accum: BoundingBox,
57-
58-
/// Estimated bounding box of all data for the last scene query.
59-
pub scene_bbox: BoundingBox,
54+
pub bounding_boxes: SceneBoundingBoxes,
6055

6156
/// Estimated number of primitives last frame. Used to inform some heuristics.
6257
pub scene_num_primitives: usize,
@@ -71,23 +66,6 @@ pub struct SpatialSpaceViewState {
7166
auto_size_config: re_renderer::AutoSizeConfig,
7267
}
7368

74-
impl Default for SpatialSpaceViewState {
75-
fn default() -> Self {
76-
Self {
77-
scene_bbox_accum: BoundingBox::nothing(),
78-
scene_bbox: BoundingBox::nothing(),
79-
scene_num_primitives: 0,
80-
state_2d: Default::default(),
81-
state_3d: Default::default(),
82-
auto_size_config: re_renderer::AutoSizeConfig {
83-
point_radius: re_renderer::Size::AUTO, // let re_renderer decide
84-
line_radius: re_renderer::Size::AUTO, // let re_renderer decide
85-
},
86-
previous_picking_result: None,
87-
}
88-
}
89-
}
90-
9169
impl SpaceViewState for SpatialSpaceViewState {
9270
fn as_any(&self) -> &dyn std::any::Any {
9371
self
@@ -127,7 +105,7 @@ impl SpatialSpaceViewState {
127105

128106
ctx.re_ui.selection_grid(ui, "spatial_settings_ui")
129107
.show(ui, |ui| {
130-
let auto_size_world = auto_size_world_heuristic(&self.scene_bbox_accum, self.scene_num_primitives);
108+
let auto_size_world = auto_size_world_heuristic(&self.bounding_boxes.accumulated, self.scene_num_primitives);
131109

132110
ctx.re_ui.grid_left_hand_label(ui, "Default size");
133111
ui.vertical(|ui| {
@@ -166,8 +144,8 @@ impl SpatialSpaceViewState {
166144
"Resets camera position & orientation.\nYou can also double-click the 3D view.")
167145
.clicked()
168146
{
169-
self.scene_bbox_accum = self.scene_bbox;
170-
self.state_3d.reset_camera(&self.scene_bbox_accum, &view_coordinates);
147+
self.bounding_boxes.accumulated = self.bounding_boxes.current;
148+
self.state_3d.reset_camera(&self.bounding_boxes.accumulated, &view_coordinates);
171149
}
172150
let mut spin = self.state_3d.spin();
173151
if re_ui.checkbox(ui, &mut spin, "Spin")
@@ -206,7 +184,7 @@ impl SpatialSpaceViewState {
206184
.on_hover_text("The bounding box encompassing all Entities in the view right now");
207185
ui.vertical(|ui| {
208186
ui.style_mut().wrap = Some(false);
209-
let BoundingBox { min, max } = self.scene_bbox;
187+
let BoundingBox { min, max } = self.bounding_boxes.current;
210188
ui.label(format!(
211189
"x [{} - {}]",
212190
format_f32(min.x),
@@ -655,7 +633,7 @@ pub fn picking(
655633
SelectedSpaceContext::ThreeD {
656634
space_3d: query.space_origin.clone(),
657635
pos: hovered_point,
658-
tracked_space_camera: state.state_3d.tracked_camera.clone(),
636+
tracked_entity: state.state_3d.tracked_entity.clone(),
659637
point_in_space_cameras: visualizers
660638
.get::<CamerasVisualizer>()?
661639
.space_cameras

crates/re_space_view_spatial/src/ui_2d.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,10 @@ pub fn view_2d(
243243
let available_size = ui.available_size();
244244
let store = ctx.entity_db.store();
245245

246+
let scene_rect_accum = state.bounding_boxes.accumulated;
246247
let scene_rect_accum = egui::Rect::from_min_max(
247-
state.scene_bbox_accum.min.truncate().to_array().into(),
248-
state.scene_bbox_accum.max.truncate().to_array().into(),
248+
scene_rect_accum.min.truncate().to_array().into(),
249+
scene_rect_accum.max.truncate().to_array().into(),
249250
);
250251

251252
// Determine the canvas which determines the extent of the explorable scene coordinates,

0 commit comments

Comments
 (0)