Skip to content

Commit 4dbd4bb

Browse files
authored
Introduce (2D) Draw order component (#2056)
* create depth offset from draw order and patch it through * transparent layers now know about depth offset - only images on the same layer will be made transparent * add some points to layering demo * comments and renames on draw order constants * doc & sample details * images with a shared root & an explicit draw order all go to the same space view now * formatting
1 parent 32cc9f6 commit 4dbd4bb

File tree

18 files changed

+342
-46
lines changed

18 files changed

+342
-46
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize};
2+
3+
use crate::Component;
4+
5+
/// Draw order used for the display order of 2D elements.
6+
///
7+
/// Higher values are drawn on top of lower values.
8+
/// An entity can have only a single draw order component.
9+
/// Within an entity draw order is governed by the order of the components.
10+
///
11+
/// Draw order for entities with the same draw order is generally undefined.
12+
///
13+
/// ```
14+
/// use re_log_types::component_types::DrawOrder;
15+
/// use arrow2_convert::field::ArrowField;
16+
/// use arrow2::datatypes::{DataType, Field};
17+
///
18+
/// assert_eq!(DrawOrder::data_type(), DataType::Float32);
19+
/// ```
20+
#[derive(Debug, Clone, ArrowField, ArrowSerialize, ArrowDeserialize)]
21+
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
22+
#[arrow_field(transparent)]
23+
pub struct DrawOrder(pub f32);
24+
25+
impl DrawOrder {
26+
/// Draw order used for images if no draw order was specified.
27+
pub const DEFAULT_IMAGE: DrawOrder = DrawOrder(-10.0);
28+
29+
/// Draw order used for 2D boxes if no draw order was specified.
30+
pub const DEFAULT_BOX2D: DrawOrder = DrawOrder(10.0);
31+
32+
/// Draw order used for 2D lines if no draw order was specified.
33+
pub const DEFAULT_LINES2D: DrawOrder = DrawOrder(20.0);
34+
35+
/// Draw order used for 2D points if no draw order was specified.
36+
pub const DEFAULT_POINTS2D: DrawOrder = DrawOrder(30.0);
37+
}
38+
39+
impl Component for DrawOrder {
40+
#[inline]
41+
fn name() -> crate::ComponentName {
42+
"rerun.draw_order".into()
43+
}
44+
}
45+
46+
impl std::cmp::PartialEq for DrawOrder {
47+
#[inline]
48+
fn eq(&self, other: &Self) -> bool {
49+
self.0.is_nan() && other.0.is_nan() || self.0 == other.0
50+
}
51+
}
52+
53+
impl std::cmp::Eq for DrawOrder {}
54+
55+
impl std::cmp::PartialOrd for DrawOrder {
56+
#[inline]
57+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
58+
if other == self {
59+
Some(std::cmp::Ordering::Equal)
60+
} else if other.0.is_nan() || self.0 < other.0 {
61+
Some(std::cmp::Ordering::Less)
62+
} else {
63+
Some(std::cmp::Ordering::Greater)
64+
}
65+
}
66+
}
67+
68+
impl std::cmp::Ord for DrawOrder {
69+
#[inline]
70+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
71+
self.partial_cmp(other).unwrap()
72+
}
73+
}
74+
75+
impl From<f32> for DrawOrder {
76+
#[inline]
77+
fn from(value: f32) -> Self {
78+
Self(value)
79+
}
80+
}
81+
82+
impl From<DrawOrder> for f32 {
83+
#[inline]
84+
fn from(value: DrawOrder) -> Self {
85+
value.0
86+
}
87+
}

crates/re_log_types/src/component_types/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod class_id;
2323
mod color;
2424
pub mod context;
2525
pub mod coordinates;
26+
mod draw_order;
2627
mod instance_key;
2728
mod keypoint_id;
2829
mod label;
@@ -46,6 +47,7 @@ pub use class_id::ClassId;
4647
pub use color::ColorRGBA;
4748
pub use context::{AnnotationContext, AnnotationInfo, ClassDescription};
4849
pub use coordinates::ViewCoordinates;
50+
pub use draw_order::DrawOrder;
4951
pub use instance_key::InstanceKey;
5052
pub use keypoint_id::KeypointId;
5153
pub use label::Label;

crates/re_log_types/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub use self::component_types::coordinates;
3636
pub use self::component_types::AnnotationContext;
3737
pub use self::component_types::Arrow3D;
3838
pub use self::component_types::DecodedTensor;
39+
pub use self::component_types::DrawOrder;
3940
pub use self::component_types::{EncodedMesh3D, Mesh3D, MeshFormat, MeshId, RawMesh3D};
4041
pub use self::component_types::{Tensor, ViewCoordinates};
4142
pub use self::data::*;

crates/re_sdk/src/lib.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ pub mod time {
6969
pub mod components {
7070
pub use re_log_types::component_types::{
7171
AnnotationContext, AnnotationInfo, Arrow3D, Box3D, ClassDescription, ClassId, ColorRGBA,
72-
EncodedMesh3D, InstanceKey, KeypointId, Label, LineStrip2D, LineStrip3D, Mat3x3, Mesh3D,
73-
MeshFormat, MeshId, Pinhole, Point2D, Point3D, Quaternion, Radius, RawMesh3D, Rect2D,
74-
Rigid3, Scalar, ScalarPlotProps, Size3D, Tensor, TensorData, TensorDataMeaning,
72+
DrawOrder, EncodedMesh3D, InstanceKey, KeypointId, Label, LineStrip2D, LineStrip3D, Mat3x3,
73+
Mesh3D, MeshFormat, MeshId, Pinhole, Point2D, Point3D, Quaternion, Radius, RawMesh3D,
74+
Rect2D, Rigid3, Scalar, ScalarPlotProps, Size3D, Tensor, TensorData, TensorDataMeaning,
7575
TensorDimension, TensorId, TextEntry, Transform, Vec2D, Vec3D, Vec4D, ViewCoordinates,
7676
};
7777
}

crates/re_viewer/src/ui/space_view_heuristics.rs

+36-13
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,13 @@ fn default_created_space_views_from_candidates(
171171

172172
// Spatial views with images get extra treatment as well.
173173
if candidate.category == ViewCategory::Spatial {
174-
let mut images_by_size: HashMap<(u64, u64), Vec<EntityPath>> = HashMap::default();
174+
#[derive(Hash, PartialEq, Eq)]
175+
enum ImageBucketing {
176+
BySize((u64, u64)),
177+
ExplicitDrawOrder,
178+
}
179+
180+
let mut images_by_bucket: HashMap<ImageBucketing, Vec<EntityPath>> = HashMap::default();
175181

176182
// For this we're only interested in the direct children.
177183
for entity_path in &candidate.data_blueprint.root_group().entities {
@@ -184,24 +190,41 @@ fn default_created_space_views_from_candidates(
184190
for tensor in entity_view.iter_primary_flattened() {
185191
if tensor.is_shaped_like_an_image() {
186192
debug_assert!(matches!(tensor.shape.len(), 2 | 3));
187-
let dim = (tensor.shape[0].size, tensor.shape[1].size);
188-
images_by_size
189-
.entry(dim)
190-
.or_default()
191-
.push(entity_path.clone());
193+
194+
if query_latest_single::<re_log_types::DrawOrder>(
195+
entity_db,
196+
entity_path,
197+
&query,
198+
)
199+
.is_some()
200+
{
201+
// Put everything in the same bucket if it has a draw order.
202+
images_by_bucket
203+
.entry(ImageBucketing::ExplicitDrawOrder)
204+
.or_default()
205+
.push(entity_path.clone());
206+
} else {
207+
// Otherwise, distinguish buckets by image size.
208+
let dim = (tensor.shape[0].size, tensor.shape[1].size);
209+
images_by_bucket
210+
.entry(ImageBucketing::BySize(dim))
211+
.or_default()
212+
.push(entity_path.clone());
213+
}
192214
}
193215
}
194216
}
195217
}
196218

197-
// If all images are the same size, proceed with the candidate as is. Otherwise...
198-
if images_by_size.len() > 1 {
199-
// ...stack images of the same size, but no others.
200-
for dim in images_by_size.keys() {
201-
// Ignore every image that has a different size.
202-
let images_of_different_size = images_by_size
219+
if images_by_bucket.len() > 1 {
220+
// If all images end up in the same bucket, proceed as normal. Otherwise stack images as instructed.
221+
for bucket in images_by_bucket.keys() {
222+
// Ignore every image from antoher bucket. Keep all other entities.
223+
let images_of_different_size = images_by_bucket
203224
.iter()
204-
.filter_map(|(other_dim, images)| (dim != other_dim).then_some(images))
225+
.filter_map(|(other_bucket, images)| {
226+
(bucket != other_bucket).then_some(images)
227+
})
205228
.flatten()
206229
.cloned()
207230
.collect::<IntSet<_>>();

crates/re_viewer/src/ui/view_spatial/scene/mod.rs

+106-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
use std::sync::Arc;
1+
use std::{collections::BTreeMap, sync::Arc};
22

33
use ahash::HashMap;
44

5-
use re_data_store::{EntityPath, InstancePathHash};
5+
use nohash_hasher::IntMap;
6+
use re_data_store::{query_latest_single, EntityPath, InstancePathHash};
67
use re_log_types::{
78
component_types::{ClassId, InstanceKey, KeypointId},
8-
DecodedTensor,
9+
DecodedTensor, DrawOrder, EntityPathHash,
910
};
1011
use re_renderer::{renderer::TexturedRect, Color32, OutlineMaskPreference, Size};
1112
use re_viewer_context::{auto_color, AnnotationMap, Annotations, SceneQuery, ViewerContext};
13+
use smallvec::SmallVec;
1214

1315
use crate::misc::{mesh_loader::LoadedMesh, SpaceViewHighlights, TransformCache};
1416

@@ -91,6 +93,21 @@ pub struct SceneSpatial {
9193

9294
pub type Keypoints = HashMap<(ClassId, i64), HashMap<KeypointId, glam::Vec3>>;
9395

96+
#[derive(Default)]
97+
pub struct EntityDepthOffsets {
98+
pub per_entity: IntMap<EntityPathHash, re_renderer::DepthOffset>,
99+
pub box2d: re_renderer::DepthOffset,
100+
pub lines2d: re_renderer::DepthOffset,
101+
pub image: re_renderer::DepthOffset,
102+
pub points: re_renderer::DepthOffset,
103+
}
104+
105+
impl EntityDepthOffsets {
106+
pub fn get(&self, ent_path: &EntityPath) -> Option<re_renderer::DepthOffset> {
107+
self.per_entity.get(&ent_path.hash()).cloned()
108+
}
109+
}
110+
94111
impl SceneSpatial {
95112
pub fn new(re_ctx: &mut re_renderer::RenderContext) -> Self {
96113
Self {
@@ -103,6 +120,89 @@ impl SceneSpatial {
103120
}
104121
}
105122

123+
fn determine_depth_offsets(
124+
ctx: &mut ViewerContext<'_>,
125+
query: &SceneQuery<'_>,
126+
) -> EntityDepthOffsets {
127+
crate::profile_function!();
128+
129+
enum DrawOrderTarget {
130+
Entity(EntityPathHash),
131+
DefaultBox2D,
132+
DefaultLines2D,
133+
DefaultImage,
134+
DefaultPoints,
135+
}
136+
137+
let mut entities_per_draw_order = BTreeMap::<DrawOrder, SmallVec<[_; 4]>>::new();
138+
for (ent_path, _) in query.iter_entities() {
139+
if let Some(draw_order) = query_latest_single::<DrawOrder>(
140+
&ctx.log_db.entity_db,
141+
ent_path,
142+
&ctx.rec_cfg.time_ctrl.current_query(),
143+
) {
144+
entities_per_draw_order
145+
.entry(draw_order)
146+
.or_default()
147+
.push(DrawOrderTarget::Entity(ent_path.hash()));
148+
}
149+
}
150+
151+
// Push in default draw orders. All of them using the none hash.
152+
entities_per_draw_order
153+
.entry(DrawOrder::DEFAULT_BOX2D)
154+
.or_default()
155+
.push(DrawOrderTarget::DefaultBox2D);
156+
entities_per_draw_order
157+
.entry(DrawOrder::DEFAULT_IMAGE)
158+
.or_default()
159+
.push(DrawOrderTarget::DefaultImage);
160+
entities_per_draw_order
161+
.entry(DrawOrder::DEFAULT_LINES2D)
162+
.or_default()
163+
.push(DrawOrderTarget::DefaultLines2D);
164+
entities_per_draw_order
165+
.entry(DrawOrder::DEFAULT_POINTS2D)
166+
.or_default()
167+
.push(DrawOrderTarget::DefaultPoints);
168+
169+
// Determine re_renderer draw order from this.
170+
// We want to be as tightly around 0 as possible.
171+
let mut offsets = EntityDepthOffsets::default();
172+
let mut draw_order = -((entities_per_draw_order.len() / 2) as re_renderer::DepthOffset);
173+
offsets.per_entity = entities_per_draw_order
174+
.into_values()
175+
.flat_map(move |targets| {
176+
let pairs = targets
177+
.into_iter()
178+
.filter_map(|target| match target {
179+
DrawOrderTarget::Entity(entity) => Some((entity, draw_order)),
180+
DrawOrderTarget::DefaultBox2D => {
181+
offsets.box2d = draw_order;
182+
None
183+
}
184+
DrawOrderTarget::DefaultLines2D => {
185+
offsets.lines2d = draw_order;
186+
None
187+
}
188+
DrawOrderTarget::DefaultImage => {
189+
offsets.image = draw_order;
190+
None
191+
}
192+
DrawOrderTarget::DefaultPoints => {
193+
offsets.points = draw_order;
194+
None
195+
}
196+
})
197+
.collect::<SmallVec<[_; 4]>>();
198+
draw_order += 1;
199+
pairs
200+
})
201+
.collect();
202+
203+
offsets
204+
}
205+
106206
/// Loads all 3D objects into the scene according to the given query.
107207
pub(crate) fn load(
108208
&mut self,
@@ -133,8 +233,10 @@ impl SceneSpatial {
133233
&scene_part::CamerasPart,
134234
];
135235

236+
let depth_offsets = Self::determine_depth_offsets(ctx, query);
237+
136238
for part in parts {
137-
part.load(self, ctx, query, transforms, highlights);
239+
part.load(self, ctx, query, transforms, highlights, &depth_offsets);
138240
}
139241

140242
self.primitives.any_outlines = highlights.any_outlines();

crates/re_viewer/src/ui/view_spatial/scene/scene_part/arrows3d.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use re_viewer_context::{DefaultColor, SceneQuery, ViewerContext};
99

1010
use crate::{
1111
misc::{SpaceViewHighlights, TransformCache},
12-
ui::view_spatial::SceneSpatial,
12+
ui::view_spatial::{scene::EntityDepthOffsets, SceneSpatial},
1313
};
1414

1515
use super::{instance_key_to_picking_id, ScenePart};
@@ -94,6 +94,7 @@ impl ScenePart for Arrows3DPart {
9494
query: &SceneQuery<'_>,
9595
transforms: &TransformCache,
9696
highlights: &SpaceViewHighlights,
97+
_depth_offsets: &EntityDepthOffsets,
9798
) {
9899
crate::profile_scope!("Points2DPart");
99100

crates/re_viewer/src/ui/view_spatial/scene/scene_part/boxes2d.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use re_viewer_context::{DefaultColor, SceneQuery, ViewerContext};
1010
use crate::{
1111
misc::{SpaceViewHighlights, TransformCache},
1212
ui::view_spatial::{
13-
scene::scene_part::instance_path_hash_for_picking, SceneSpatial, UiLabel, UiLabelTarget,
13+
scene::{scene_part::instance_path_hash_for_picking, EntityDepthOffsets},
14+
SceneSpatial, UiLabel, UiLabelTarget,
1415
},
1516
};
1617

@@ -25,6 +26,7 @@ impl Boxes2DPart {
2526
ent_path: &EntityPath,
2627
world_from_obj: glam::Affine3A,
2728
highlights: &SpaceViewHighlights,
29+
depth_offset: re_renderer::DepthOffset,
2830
) -> Result<(), QueryError> {
2931
scene.num_logged_2d_objects += 1;
3032

@@ -37,6 +39,7 @@ impl Boxes2DPart {
3739
.primitives
3840
.line_strips
3941
.batch("2d boxes")
42+
.depth_offset(depth_offset)
4043
.world_from_obj(world_from_obj)
4144
.outline_mask_ids(entity_highlight.overall)
4245
.picking_object_id(re_renderer::PickingLayerObjectId(ent_path.hash64()));
@@ -105,6 +108,7 @@ impl ScenePart for Boxes2DPart {
105108
query: &SceneQuery<'_>,
106109
transforms: &TransformCache,
107110
highlights: &SpaceViewHighlights,
111+
depth_offsets: &EntityDepthOffsets,
108112
) {
109113
crate::profile_scope!("Boxes2DPart");
110114

@@ -136,6 +140,7 @@ impl ScenePart for Boxes2DPart {
136140
ent_path,
137141
world_from_obj,
138142
highlights,
143+
depth_offsets.get(ent_path).unwrap_or(depth_offsets.box2d),
139144
)?;
140145
}
141146
Ok(())

0 commit comments

Comments
 (0)