Skip to content

Commit ba4722a

Browse files
authored
Allow root space views transforms to other roots (#1075)
* Allow adding entities from arbitrary space paths Fix bug in space info reachability check (which drives the entity picker) * improve group naming for now unrestricted object addings
1 parent eff9ddb commit ba4722a

File tree

7 files changed

+92
-98
lines changed

7 files changed

+92
-98
lines changed

crates/re_log_types/src/component_types/transform.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ pub struct Rigid3 {
5151

5252
#[cfg(feature = "glam")]
5353
impl Rigid3 {
54+
pub const IDENTITY: Rigid3 = Rigid3 {
55+
rotation: Quaternion {
56+
x: 1.0,
57+
y: 0.0,
58+
z: 0.0,
59+
w: 0.0,
60+
},
61+
translation: Vec3D([0.0, 0.0, 0.0]),
62+
};
63+
5464
#[inline]
5565
pub fn new_parent_from_child(parent_from_child: macaw::IsoTransform) -> Self {
5666
Self {

crates/re_viewer/src/misc/space_info.rs

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ impl SpaceInfo {
9898
/// Information about all spaces.
9999
///
100100
/// This is gathered by analyzing the transform hierarchy of the entities:
101-
/// For every child of the root there is a space info.
101+
/// For every child of the root there is a space info, as well as the root itself.
102102
/// Each of these we walk down recursively, every time a transform is encountered, we create another space info.
103103
///
104104
/// Expected to be recreated every frame (or whenever new data is available).
@@ -163,20 +163,39 @@ impl SpaceInfoCollection {
163163

164164
let mut spaces_info = Self::default();
165165

166+
// The root itself.
167+
// To make our heuristics work we pretend direct child of the root has a transform,
168+
// breaking the pattern applied for everything else where we create a SpaceInfo once we hit a transform.
169+
//
170+
// TODO(andreas): Our dependency on SpaceInfo in this way is quite telling - we should be able to create a SpaceView without having a corresponding SpaceInfo
171+
// Currently too many things depend on every SpaceView being backed up by a concrete SpaceInfo on its space path.
172+
if query_transform(entity_db, &EntityPath::root(), &query).is_some() {
173+
re_log::warn_once!("The root entity has a 'transform' component! This will have no effect. Did you mean to apply the transform elsewhere?");
174+
}
175+
let mut root_space_info = SpaceInfo::new(EntityPath::root());
176+
root_space_info
177+
.descendants_without_transform
178+
.insert(EntityPath::root());
179+
166180
for tree in entity_db.tree.children.values() {
167-
// Each root entity is its own space (or should be)
181+
let mut space_info = SpaceInfo::new(tree.path.clone());
182+
let transform = query_transform(entity_db, &EntityPath::root(), &query)
183+
.unwrap_or(Transform::Rigid3(re_log_types::Rigid3::IDENTITY));
184+
space_info.parent = Some((EntityPath::root(), transform.clone()));
185+
space_info
186+
.descendants_without_transform
187+
.insert(tree.path.clone());
168188

169-
if query_transform(entity_db, &tree.path, &query).is_some() {
170-
re_log::warn_once!(
171-
"Root entity '{}' has a _transform - this is not allowed!",
172-
tree.path
173-
);
174-
}
189+
root_space_info
190+
.child_spaces
191+
.insert(tree.path.clone(), transform);
175192

176-
let mut space_info = SpaceInfo::new(tree.path.clone());
177193
add_children(entity_db, &mut spaces_info, &mut space_info, tree, &query);
178194
spaces_info.spaces.insert(tree.path.clone(), space_info);
179195
}
196+
spaces_info
197+
.spaces
198+
.insert(EntityPath::root(), root_space_info);
180199

181200
for (entity_path, space_info) in &mut spaces_info.spaces {
182201
space_info.coordinates = query_view_coordinates(entity_db, entity_path, &query);
@@ -209,40 +228,36 @@ impl SpaceInfoCollection {
209228
///
210229
/// For how, you need to check [`crate::misc::TransformCache`]!
211230
/// Note that in any individual frame, entities may or may not be reachable.
212-
///
213-
/// If `from` and `to_reference` are not under the same root branch, they are regarded as [`UnreachableTransform::Unconnected`]
214231
pub fn is_reachable_by_transform(
215232
&self,
216233
from: &EntityPath,
217234
to_reference: &EntityPath,
218235
) -> Result<(), UnreachableTransform> {
219236
crate::profile_function!();
220237

221-
// By convention we regard the global hierarchy as a forest - don't allow breaking out of the current tree.
222-
if from.iter().next() != to_reference.iter().next() {
223-
return Err(UnreachableTransform::Unconnected);
224-
}
225-
226238
// Get closest space infos for the given entity paths.
227239
let Some(mut from_space) = self.get_first_parent_with_info(from) else {
228240
re_log::warn_once!("{} not part of space infos", from);
229-
return Err(UnreachableTransform::Unconnected);
241+
return Err(UnreachableTransform::UnknownSpaceInfo);
230242
};
231243
let Some(mut to_reference_space) = self.get_first_parent_with_info(to_reference) else {
232244
re_log::warn_once!("{} not part of space infos", to_reference);
233-
return Err(UnreachableTransform::Unconnected);
245+
return Err(UnreachableTransform::UnknownSpaceInfo);
234246
};
235247

236-
// If this is not true, the path we're querying, `from`, is outside of the tree the reference node.
237-
// Note that this means that all transforms on the way are inversed!
238-
let from_is_child_of_reference = from.is_descendant_of(to_reference);
239-
240248
// Reachability is (mostly) commutative!
241-
// This means we can simply walk from the lower node to the parent until we're on the same node
249+
// This means we can simply walk from both nodes up until we find a common ancestor!
242250
// If we haven't encountered any obstacles, we're fine!
243251
let mut encountered_pinhole = false;
244252
while from_space.path != to_reference_space.path {
245-
let parent = if from_is_child_of_reference {
253+
// Decide if we should walk up "from" or "to_reference"
254+
// If "from" is a descendant of "to_reference", we walk up "from"
255+
// Otherwise we walk up on "to_reference".
256+
//
257+
// If neither is a descendant of the other it doesn't matter which one we walk up, since we eventually going to hit common ancestor!
258+
let walk_up_from = from_space.path.is_descendant_of(&to_reference_space.path);
259+
260+
let parent = if walk_up_from {
246261
&from_space.parent
247262
} else {
248263
&to_reference_space.parent
@@ -258,7 +273,7 @@ impl SpaceInfoCollection {
258273
Err(UnreachableTransform::NestedPinholeCameras)
259274
} else {
260275
encountered_pinhole = true;
261-
if pinhole.resolution.is_none() && !from_is_child_of_reference {
276+
if pinhole.resolution.is_none() && !walk_up_from {
262277
Err(UnreachableTransform::InversePinholeCameraWithoutResolution)
263278
} else {
264279
Ok(())
@@ -269,10 +284,10 @@ impl SpaceInfoCollection {
269284

270285
let Some(parent_space) = self.get(parent_path) else {
271286
re_log::warn_once!("{} not part of space infos", parent_path);
272-
return Err(UnreachableTransform::Unconnected);
287+
return Err(UnreachableTransform::UnknownSpaceInfo);
273288
};
274289

275-
if from_is_child_of_reference {
290+
if walk_up_from {
276291
from_space = parent_space;
277292
} else {
278293
to_reference_space = parent_space;
@@ -283,7 +298,7 @@ impl SpaceInfoCollection {
283298
from,
284299
to_reference
285300
);
286-
return Err(UnreachableTransform::Unconnected);
301+
return Err(UnreachableTransform::UnknownSpaceInfo);
287302
}
288303
}
289304

crates/re_viewer/src/misc/transform_cache.rs

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,15 @@ pub struct TransformCache {
2424
unreachable_descendants: Vec<(EntityPath, UnreachableTransform)>,
2525

2626
/// The first parent of reference_path that is no longer reachable.
27-
first_unreachable_parent: (EntityPath, UnreachableTransform),
27+
first_unreachable_parent: Option<(EntityPath, UnreachableTransform)>,
2828
}
2929

3030
#[derive(Clone, Copy)]
3131
pub enum UnreachableTransform {
32-
/// Not part of the hierarchy at all.
33-
/// TODO(andreas): This is only needed for the "breaking out of the root" case that we want to scrap.
34-
/// After that it's impossible to be unreachable since there can always be an identity matrix.
35-
/// We already allow walking over unlogged paths (because why not)
36-
Unconnected,
32+
/// [`super::space_info::SpaceInfoCollection`] is outdated and can't find a corresponding space info for the given path.
33+
///
34+
/// If at all, this should only happen for a single frame until space infos are rebuilt.
35+
UnknownSpaceInfo,
3736
/// More than one pinhole camera between this and the reference space.
3837
NestedPinholeCameras,
3938
/// Exiting out of a space with a pinhole camera that doesn't have a resolution is not supported.
@@ -45,8 +44,8 @@ pub enum UnreachableTransform {
4544
impl std::fmt::Display for UnreachableTransform {
4645
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4746
f.write_str(match self {
48-
Self::Unconnected =>
49-
"No entity path connection from this space view.",
47+
Self::UnknownSpaceInfo =>
48+
"Can't determine transform because internal data structures are not in a valid state. Please file an issue on https://github.com/rerun-io/rerun/",
5049
Self::NestedPinholeCameras =>
5150
"Can't display entities under nested pinhole cameras.",
5251
Self::UnknownTransform =>
@@ -75,7 +74,7 @@ impl TransformCache {
7574
reference_path: root_path.clone(),
7675
reference_from_entity_per_entity: Default::default(),
7776
unreachable_descendants: Default::default(),
78-
first_unreachable_parent: (EntityPath::root(), UnreachableTransform::Unconnected),
77+
first_unreachable_parent: None,
7978
};
8079

8180
// Find the entity path tree for the root.
@@ -113,13 +112,6 @@ impl TransformCache {
113112
let mut encountered_pinhole = false;
114113
let mut reference_from_ancestor = glam::Mat4::IDENTITY;
115114
while let Some(parent_tree) = parent_tree_stack.pop() {
116-
// By convention we regard the global hierarchy as a forest - don't allow breaking out of the current tree.
117-
if parent_tree.path.is_root() {
118-
transforms.first_unreachable_parent =
119-
(parent_tree.path.clone(), UnreachableTransform::Unconnected);
120-
break;
121-
}
122-
123115
// Note that the transform at the reference is the first that needs to be inverted to "break out" of its hierarchy.
124116
// Generally, the transform _at_ a node isn't relevant to it's children, but only to get to its parent in turn!
125117
match inverse_transform_at(
@@ -130,7 +122,7 @@ impl TransformCache {
130122
) {
131123
Err(unreachable_reason) => {
132124
transforms.first_unreachable_parent =
133-
(parent_tree.path.clone(), unreachable_reason);
125+
Some((parent_tree.path.clone(), unreachable_reason));
134126
break;
135127
}
136128
Ok(None) => {}

crates/re_viewer/src/ui/data_blueprint.rs

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ impl DataBlueprintTree {
221221
} else {
222222
// Otherwise, create a new group which only contains this entity and add the group to the hierarchy.
223223
let new_group = self.groups.insert(DataBlueprintGroup {
224-
display_name: path_to_group_name(path, base_path),
224+
display_name: path_to_group_name(path),
225225
..Default::default()
226226
});
227227
self.add_group_to_hierarchy_recursively(new_group, path, base_path);
@@ -271,8 +271,7 @@ impl DataBlueprintTree {
271271

272272
// Short circuit to the root group at base_path.
273273
// If the entity is outside of the base path we would walk up all the way to the root
274-
// That's ok but we want to stop one element short (since a space view can only show elements under a shared path)
275-
if &parent_path == base_path || parent_path.iter().count() == 1 {
274+
if &parent_path == base_path {
276275
parent_path = EntityPath::root();
277276
}
278277

@@ -289,7 +288,7 @@ impl DataBlueprintTree {
289288

290289
std::collections::hash_map::Entry::Vacant(vacant_mapping) => {
291290
let parent_group = self.groups.insert(DataBlueprintGroup {
292-
display_name: path_to_group_name(&parent_path, base_path),
291+
display_name: path_to_group_name(&parent_path),
293292
children: smallvec![new_group],
294293
..Default::default()
295294
});
@@ -398,18 +397,6 @@ impl DataBlueprintTree {
398397
}
399398
}
400399

401-
fn path_to_group_name(path: &EntityPath, base_path: &EntityPath) -> String {
402-
// Root should never be pasesd in here, but handle it gracefully regardless.
403-
let name = path.iter().last().map_or("/".to_owned(), |c| c.to_string());
404-
405-
// How many steps until a common ancestor?
406-
let mut num_steps_until_common_ancestor = 0;
407-
let mut common_ancestor = base_path.clone();
408-
while !path.is_descendant_of(&common_ancestor) {
409-
// Can never go beyond root because everything is a descendent of root, therefore this is safe.
410-
common_ancestor = common_ancestor.parent().unwrap();
411-
num_steps_until_common_ancestor += 1;
412-
}
413-
414-
format!("{}{}", "../".repeat(num_steps_until_common_ancestor), name)
400+
fn path_to_group_name(path: &EntityPath) -> String {
401+
path.iter().last().map_or("/".to_owned(), |c| c.to_string())
415402
}

crates/re_viewer/src/ui/space_view.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,6 @@ pub struct SpaceView {
4141
pub id: SpaceViewId,
4242
pub name: String,
4343

44-
/// Everything under this root *can* be shown in the space view.
45-
///
46-
/// TODO(andreas): We decided to remove this concept.
47-
pub root_path: EntityPath,
48-
4944
/// The "anchor point" of this space view.
5045
/// It refers to a [`SpaceInfo`] which forms our reference point for all scene->world transforms in this space view.
5146
/// I.e. the position of this entity path in space forms the origin of the coordinate system in this space view.
@@ -71,11 +66,6 @@ impl SpaceView {
7166
space_info: &SpaceInfo,
7267
queries_entities: &[EntityPath],
7368
) -> Self {
74-
let root_path = space_info.path.iter().next().map_or_else(
75-
|| space_info.path.clone(),
76-
|c| EntityPath::from(vec![c.clone()]),
77-
);
78-
7969
let name = if queries_entities.len() == 1 {
8070
// a single entity in this space-view - name the space after it
8171
queries_entities[0].to_string()
@@ -90,7 +80,6 @@ impl SpaceView {
9080
Self {
9181
name,
9282
id: SpaceViewId::random(),
93-
root_path,
9483
space_path: space_info.path.clone(),
9584
data_blueprint: data_blueprint_tree,
9685
view_state: ViewState::default(),

crates/re_viewer/src/ui/space_view_entity_picker.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,18 @@ impl SpaceViewEntityPicker {
7575

7676
fn add_entities_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui, space_view: &mut SpaceView) {
7777
let spaces_info = SpaceInfoCollection::new(&ctx.log_db.entity_db);
78-
// TODO(andreas): remove use space_view.root_path, just show everything
79-
if let Some(tree) = ctx.log_db.entity_db.tree.subtree(&space_view.root_path) {
80-
let entities_add_info = create_entity_add_info(ctx, tree, space_view, &spaces_info);
78+
let tree = &ctx.log_db.entity_db.tree;
79+
let entities_add_info = create_entity_add_info(ctx, tree, space_view, &spaces_info);
8180

82-
add_entities_tree_ui(
83-
ctx,
84-
ui,
85-
&spaces_info,
86-
&tree.path.to_string(),
87-
tree,
88-
space_view,
89-
&entities_add_info,
90-
);
91-
}
81+
add_entities_tree_ui(
82+
ctx,
83+
ui,
84+
&spaces_info,
85+
&tree.path.to_string(),
86+
tree,
87+
space_view,
88+
&entities_add_info,
89+
);
9290
}
9391

9492
fn add_entities_tree_ui(

crates/re_viewer/src/ui/viewport.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -521,24 +521,27 @@ impl Viewport {
521521
let mut space_views = Vec::new();
522522

523523
for space_view_candidate in Self::all_possible_space_views(ctx, spaces_info) {
524+
// Skip root space for now, messes things up.
525+
if space_view_candidate.space_path.is_root() {
526+
continue;
527+
}
528+
524529
let Some(space_info) = spaces_info.get(&space_view_candidate.space_path) else {
525530
// Should never happen.
526531
continue;
527532
};
528533

529-
// If it doesn't contain anything but the transform itself, skip,
530-
if space_info.descendants_without_transform.is_empty() {
531-
continue;
532-
}
533-
534534
if space_view_candidate.category == ViewCategory::Spatial {
535-
// Skip if connection to parent is via rigid (too trivial for a new space view!)
536-
if let Some(parent_transform) = space_info.parent_transform() {
537-
match parent_transform {
538-
re_log_types::Transform::Rigid3(_) => {
539-
continue;
535+
// For every item that isn't a direct descendant of the root, skip if connection to parent is via rigid (too trivial for a new space view!)
536+
if space_info.path.parent() != Some(EntityPath::root()) {
537+
if let Some(parent_transform) = space_info.parent_transform() {
538+
match parent_transform {
539+
re_log_types::Transform::Rigid3(_) => {
540+
continue;
541+
}
542+
re_log_types::Transform::Pinhole(_)
543+
| re_log_types::Transform::Unknown => {}
540544
}
541-
re_log_types::Transform::Pinhole(_) | re_log_types::Transform::Unknown => {}
542545
}
543546
}
544547

0 commit comments

Comments
 (0)