Skip to content

Commit

Permalink
Labels for 3D objects have now a color can now be selected & hovered (#…
Browse files Browse the repository at this point in the history
…1438)

Overall they behave just like the labels for 2D objects because it's finally the exact same code \o/
Fixes  #429
  • Loading branch information
Wumpf committed Feb 28, 2023
1 parent 5c504fb commit d49d497
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 188 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"cSpell.words": [
"andreas",
"bbox",
"emath",
"framebuffer",
"hoverable",
"Keypoint",
Expand Down
2 changes: 1 addition & 1 deletion crates/re_renderer/shader/point_cloud.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ fn fs_main(in: VertexOut) -> @location(0) Vec4 {

// Sphere intersection with anti-aliasing as described by Iq here
// https://www.shadertoy.com/view/MsSSWV
// (but rearranged and labled to it's easier to understand!)
// (but rearranged and labeled to it's easier to understand!)
let d = sphere_distance(ray, in.point_center, in.radius);
let smallest_distance_to_sphere = d.x;
let closest_ray_dist = d.y;
Expand Down
4 changes: 1 addition & 3 deletions crates/re_viewer/src/ui/view_spatial/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ mod ui_2d;
mod ui_3d;
mod ui_renderer_bridge;

pub use self::scene::{
Image, Label2D, Label2DTarget, Label3D, MeshSource, MeshSourceData, SceneSpatial,
};
pub use self::scene::{Image, MeshSource, MeshSourceData, SceneSpatial, UiLabel, UiLabelTarget};
pub use self::space_camera_3d::SpaceCamera3D;
pub use ui::{SpatialNavigationMode, ViewSpatialState};
pub use ui_2d::view_2d;
Expand Down
26 changes: 10 additions & 16 deletions crates/re_viewer/src/ui/view_spatial/scene/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,38 +74,32 @@ pub struct Image {
pub annotations: Arc<Annotations>,
}

pub enum Label2DTarget {
pub enum UiLabelTarget {
/// Labels a given rect (in scene coordinates)
Rect(egui::Rect),

/// Labels a given point (in scene coordinates)
Point(egui::Pos2),
Point2D(egui::Pos2),

/// A point in space.
Position3D(glam::Vec3),
}

// TODO(andreas): Merge Label2D and Label3D
pub struct Label2D {
pub struct UiLabel {
pub text: String,
pub color: Color32,

/// The shape being labeled.
pub target: Label2DTarget,
/// The shape/position being labeled.
pub target: UiLabelTarget,

/// What is hovered if this label is hovered.
pub labled_instance: InstancePathHash,
}

pub struct Label3D {
pub(crate) text: String,

/// Origin of the label
pub(crate) origin: glam::Vec3,
pub labeled_instance: InstancePathHash,
}

/// Data necessary to setup the ui [`SceneSpatial`] but of no interest to `re_renderer`.
#[derive(Default)]
pub struct SceneSpatialUiData {
pub labels_3d: Vec<Label3D>,
pub labels_2d: Vec<Label2D>,
pub labels: Vec<UiLabel>,

/// Picking any any of these rects cause the referred instance to be hovered.
/// Only use this for 2d overlays!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
ui::{
scene::SceneQuery,
view_spatial::{
scene::scene_part::instance_path_hash_for_picking, Label2D, Label2DTarget, SceneSpatial,
scene::scene_part::instance_path_hash_for_picking, SceneSpatial, UiLabel, UiLabelTarget,
},
DefaultColor,
},
Expand Down Expand Up @@ -71,14 +71,14 @@ impl Boxes2DPart {
.user_data(instance_path_hash);

if let Some(label) = label {
scene.ui.labels_2d.push(Label2D {
scene.ui.labels.push(UiLabel {
text: label,
color,
target: Label2DTarget::Rect(egui::Rect::from_min_size(
target: UiLabelTarget::Rect(egui::Rect::from_min_size(
rect.top_left_corner().into(),
egui::vec2(rect.width(), rect.height()),
)),
labled_instance: instance_path_hash,
labeled_instance: instance_path_hash,
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
misc::{OptionalSpaceViewEntityHighlight, SpaceViewHighlights, TransformCache, ViewerContext},
ui::{
scene::SceneQuery,
view_spatial::{Label3D, SceneSpatial},
view_spatial::{SceneSpatial, UiLabel, UiLabelTarget},
DefaultColor,
},
};
Expand Down Expand Up @@ -82,9 +82,11 @@ impl Boxes3DPart {
.user_data(instance_hash);

if let Some(label) = annotation_info.label(label.as_ref().map(|s| &s.0)) {
scene.ui.labels_3d.push(Label3D {
scene.ui.labels.push(UiLabel {
text: label,
origin: world_from_obj.transform_point3(tran),
target: UiLabelTarget::Position3D(world_from_obj.transform_point3(tran)),
color,
labeled_instance: instance_hash,
});
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::{
misc::{OptionalSpaceViewEntityHighlight, SpaceViewHighlights, TransformCache, ViewerContext},
ui::{
scene::SceneQuery,
view_spatial::{scene::Keypoints, Label2D, Label2DTarget, SceneSpatial},
view_spatial::{scene::Keypoints, SceneSpatial, UiLabel, UiLabelTarget},
DefaultColor,
},
};
Expand Down Expand Up @@ -101,11 +101,11 @@ impl Points2DPart {

if let Some(label) = label {
if label_batch.len() < max_num_labels {
label_batch.push(Label2D {
label_batch.push(UiLabel {
text: label,
color,
target: Label2DTarget::Point(egui::pos2(pos.x, pos.y)),
labled_instance: instance_hash,
target: UiLabelTarget::Point2D(egui::pos2(pos.x, pos.y)),
labeled_instance: instance_hash,
});
}
}
Expand All @@ -115,7 +115,7 @@ impl Points2DPart {
drop(point_batch); // Drop batch so we have access to the scene again (batches need to be dropped before starting new ones).

if label_batch.len() < max_num_labels {
scene.ui.labels_2d.extend(label_batch.into_iter());
scene.ui.labels.extend(label_batch.into_iter());
}

// Generate keypoint connections if any.
Expand Down
52 changes: 36 additions & 16 deletions crates/re_viewer/src/ui/view_spatial/scene/scene_part/points3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::sync::Arc;
use ahash::{HashMap, HashMapExt};
use glam::Mat4;

use re_data_store::{EntityPath, EntityProperties};
use re_data_store::{EntityPath, EntityProperties, InstancePathHash};
use re_log_types::{
component_types::{ClassId, ColorRGBA, InstanceKey, KeypointId, Label, Point3D, Radius},
msg_bundle::Component,
Expand All @@ -21,7 +21,7 @@ use crate::{
scene::SceneQuery,
view_spatial::{
scene::{scene_part::instance_path_hash_for_picking, Keypoints},
Label3D, SceneSpatial,
SceneSpatial, UiLabel, UiLabelTarget,
},
Annotations, DefaultColor,
},
Expand Down Expand Up @@ -117,24 +117,34 @@ impl Points3DPart {

fn process_labels<'a>(
entity_view: &'a EntityView<Point3D>,
instance_path_hashes: &'a [InstancePathHash],
colors: &'a [egui::Color32],
annotation_infos: &'a [ResolvedAnnotationInfo],
world_from_obj: Mat4,
) -> Result<impl Iterator<Item = Label3D> + 'a, QueryError> {
) -> Result<impl Iterator<Item = UiLabel> + 'a, QueryError> {
let labels = itertools::izip!(
annotation_infos.iter(),
entity_view.iter_primary()?,
entity_view.iter_component::<Label>()?
entity_view.iter_component::<Label>()?,
colors,
instance_path_hashes,
)
.filter_map(move |(annotation_info, point, label)| {
let label = annotation_info.label(label.map(|l| l.0).as_ref());
match (point, label) {
(Some(point), Some(label)) => Some(Label3D {
text: label,
origin: world_from_obj.transform_point3(point.into()),
}),
_ => None,
}
});
.filter_map(
move |(annotation_info, point, label, color, labeled_instance)| {
let label = annotation_info.label(label.map(|l| l.0).as_ref());
match (point, label) {
(Some(point), Some(label)) => Some(UiLabel {
text: label,
color: *color,
target: UiLabelTarget::Position3D(
world_from_obj.transform_point3(point.into()),
),
labeled_instance: *labeled_instance,
}),
_ => None,
}
},
);
Ok(labels)
}

Expand Down Expand Up @@ -192,10 +202,20 @@ impl Points3DPart {
let colors = Self::process_colors(entity_view, ent_path, &highlights, &annotation_infos)?;

let radii = Self::process_radii(entity_view, &highlights)?;
let labels = Self::process_labels(entity_view, &annotation_infos, world_from_obj)?;

if show_labels && instance_path_hashes.len() <= self.max_labels {
scene.ui.labels_3d.extend(labels);
// Max labels is small enough that we can afford iterating on the colors again.
let colors =
Self::process_colors(entity_view, ent_path, &highlights, &annotation_infos)?
.collect::<Vec<_>>();

scene.ui.labels.extend(Self::process_labels(
entity_view,
&instance_path_hashes,
&colors,
&annotation_infos,
world_from_obj,
)?);
}

scene
Expand Down
105 changes: 101 additions & 4 deletions crates/re_viewer/src/ui/view_spatial/ui.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
use eframe::epaint::text::TextWrapping;
use re_data_store::{query_latest_single, EditableAutoValue, EntityPath};
use re_format::format_f32;

use egui::{NumExt, WidgetText};
use macaw::BoundingBox;

use crate::{
misc::{space_info::query_view_coordinates, SpaceViewHighlights, ViewerContext},
ui::{data_blueprint::DataBlueprintTree, SpaceViewId},
misc::{
space_info::query_view_coordinates, SelectionHighlight, SpaceViewHighlights, ViewerContext,
},
ui::{data_blueprint::DataBlueprintTree, view_spatial::UiLabelTarget, SpaceViewId},
};

use super::{ui_2d::View2DState, ui_3d::View3DState, SceneSpatial, SpaceSpecs};
use super::{
eye::Eye, scene::SceneSpatialUiData, ui_2d::View2DState, ui_3d::View3DState, SceneSpatial,
SpaceSpecs,
};

/// Describes how the scene is navigated, determining if it is a 2D or 3D experience.
#[derive(Clone, Copy, Default, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
Expand Down Expand Up @@ -344,7 +350,7 @@ impl ViewSpatialState {
let coordinates =
query_view_coordinates(&ctx.log_db.entity_db, space, &ctx.current_query());
self.state_3d.space_specs = SpaceSpecs::from_view_coordinates(coordinates);
super::view_3d(ctx, ui, self, space, space_view_id, scene);
super::view_3d(ctx, ui, self, space, space_view_id, scene, highlights);
}
SpatialNavigationMode::TwoD => {
let scene_rect_accum = egui::Rect::from_min_max(
Expand Down Expand Up @@ -459,3 +465,94 @@ fn axis_name(axis: Option<glam::Vec3>) -> String {
"—".to_owned()
}
}

pub fn create_labels(
scene_ui: &mut SceneSpatialUiData,
ui_from_space2d: egui::emath::RectTransform,
space2d_from_ui: egui::emath::RectTransform,
eye3d: &Eye,
parent_ui: &mut egui::Ui,
highlights: &SpaceViewHighlights,
) -> Vec<egui::Shape> {
crate::profile_function!();

let mut label_shapes = Vec::with_capacity(scene_ui.labels.len() * 2);

let ui_from_world_3d = eye3d.ui_from_world(ui_from_space2d.to());

for label in &scene_ui.labels {
let (wrap_width, text_anchor_pos) = match label.target {
UiLabelTarget::Rect(rect) => {
let rect_in_ui = ui_from_space2d.transform_rect(rect);
(
// Place the text centered below the rect
(rect_in_ui.width() - 4.0).at_least(60.0),
rect_in_ui.center_bottom() + egui::vec2(0.0, 3.0),
)
}
UiLabelTarget::Point2D(pos) => {
let pos_in_ui = ui_from_space2d.transform_pos(pos);
(f32::INFINITY, pos_in_ui + egui::vec2(0.0, 3.0))
}
UiLabelTarget::Position3D(pos) => {
let pos_in_ui = ui_from_world_3d * pos.extend(1.0);
if pos_in_ui.w <= 0.0 {
continue; // behind camera
}
let pos_in_ui = pos_in_ui / pos_in_ui.w;
(f32::INFINITY, egui::pos2(pos_in_ui.x, pos_in_ui.y))
}
};

let font_id = egui::TextStyle::Body.resolve(parent_ui.style());
let galley = parent_ui.fonts(|fonts| {
fonts.layout_job({
egui::text::LayoutJob {
sections: vec![egui::text::LayoutSection {
leading_space: 0.0,
byte_range: 0..label.text.len(),
format: egui::TextFormat::simple(font_id, label.color),
}],
text: label.text.clone(),
wrap: TextWrapping {
max_width: wrap_width,
..Default::default()
},
break_on_newline: true,
halign: egui::Align::Center,
..Default::default()
}
})
});

let text_rect = egui::Align2::CENTER_TOP
.anchor_rect(egui::Rect::from_min_size(text_anchor_pos, galley.size()));
let bg_rect = text_rect.expand2(egui::vec2(4.0, 2.0));

let hightlight = highlights
.entity_highlight(label.labeled_instance.entity_path_hash)
.index_highlight(label.labeled_instance.instance_key);
let fill_color = match hightlight.hover {
crate::misc::HoverHighlight::None => match hightlight.selection {
SelectionHighlight::None => parent_ui.style().visuals.widgets.inactive.bg_fill,
SelectionHighlight::SiblingSelection => {
parent_ui.style().visuals.widgets.active.bg_fill
}
SelectionHighlight::Selection => parent_ui.style().visuals.widgets.active.bg_fill,
},
crate::misc::HoverHighlight::Hovered => {
parent_ui.style().visuals.widgets.hovered.bg_fill
}
};

label_shapes.push(egui::Shape::rect_filled(bg_rect, 3.0, fill_color));
label_shapes.push(egui::Shape::galley(text_rect.center_top(), galley));

scene_ui.pickable_ui_rects.push((
space2d_from_ui.transform_rect(bg_rect),
label.labeled_instance,
));
}

label_shapes
}

1 comment on commit d49d497

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust Benchmark

Benchmark suite Current: d49d497 Previous: 6be85c0 Ratio
datastore/insert/batch/rects/insert 537107 ns/iter (± 5889) 554445 ns/iter (± 1992) 0.97
datastore/latest_at/batch/rects/query 1787 ns/iter (± 21) 1835 ns/iter (± 5) 0.97
datastore/latest_at/missing_components/primary 348 ns/iter (± 4) 356 ns/iter (± 0) 0.98
datastore/latest_at/missing_components/secondaries 426 ns/iter (± 6) 425 ns/iter (± 0) 1.00
datastore/range/batch/rects/query 149453 ns/iter (± 1783) 152743 ns/iter (± 634) 0.98
mono_points_arrow/generate_message_bundles 44626339 ns/iter (± 996814) 51783651 ns/iter (± 961527) 0.86
mono_points_arrow/generate_messages 122502032 ns/iter (± 1257084) 138039450 ns/iter (± 2010763) 0.89
mono_points_arrow/encode_log_msg 151498213 ns/iter (± 1364572) 166925473 ns/iter (± 2195985) 0.91
mono_points_arrow/encode_total 325801151 ns/iter (± 2680920) 358085797 ns/iter (± 1987665) 0.91
mono_points_arrow/decode_log_msg 172175925 ns/iter (± 1531099) 186764021 ns/iter (± 1577474) 0.92
mono_points_arrow/decode_message_bundles 62936640 ns/iter (± 767077) 74871661 ns/iter (± 1004334) 0.84
mono_points_arrow/decode_total 234335768 ns/iter (± 2303149) 256482272 ns/iter (± 1879406) 0.91
batch_points_arrow/generate_message_bundles 324135 ns/iter (± 3675) 333103 ns/iter (± 1002) 0.97
batch_points_arrow/generate_messages 6118 ns/iter (± 77) 6235 ns/iter (± 22) 0.98
batch_points_arrow/encode_log_msg 352587 ns/iter (± 2894) 358383 ns/iter (± 1762) 0.98
batch_points_arrow/encode_total 701931 ns/iter (± 5896) 721998 ns/iter (± 2642) 0.97
batch_points_arrow/decode_log_msg 345868 ns/iter (± 2445) 352479 ns/iter (± 1235) 0.98
batch_points_arrow/decode_message_bundles 1997 ns/iter (± 29) 2060 ns/iter (± 7) 0.97
batch_points_arrow/decode_total 351766 ns/iter (± 2960) 353770 ns/iter (± 1347) 0.99
arrow_mono_points/insert 5963914061 ns/iter (± 16387428) 6951746375 ns/iter (± 30499253) 0.86
arrow_mono_points/query 1708319 ns/iter (± 19333) 1759417 ns/iter (± 25501) 0.97
arrow_batch_points/insert 2609044 ns/iter (± 32920) 2658971 ns/iter (± 36380) 0.98
arrow_batch_points/query 17196 ns/iter (± 237) 17632 ns/iter (± 18) 0.98
tuid/Tuid::random 34 ns/iter (± 0) 45 ns/iter (± 1) 0.76

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.