Skip to content

Commit

Permalink
Consistent transform visualization for all entities with transforms (#…
Browse files Browse the repository at this point in the history
…2577)

### What

Fixes #753. Fixes #2503. Fixes #2269
* #753 
* #2503
* #2269

Adds a new scene part to visualize all transform arrows - decided to
avoid the term "gizmo" since gizmos as they are used for manipulating an
object (e.g. Blender, Unity3D, Unreal etc.) do have quite different
properties in what they need to show and are typically *not* used to
visualize the actual transform.

Introduces two new properties that (like all other object properties)
will need to be componetized and be part of the blueprint apis:
* transform visibility: On by default iff the entity or any of its
children has a pinhole transform or if the entity doesn't have any other
components than `Transform3D`
* transform arrow length: By default a heuristic world bounding box
based scale, can be chosen freely per object.

Polished the api_demo#transform_test a little bit to be able to use it
as a test case for this:

![image](https://github.com/rerun-io/rerun/assets/1220815/538a8cee-36f9-4af0-a5a6-51723f6e925f)

Note in particular that skew & scale works as expected:

![image](https://github.com/rerun-io/rerun/assets/1220815/4482d9b8-6e12-405a-9431-bf18ac7d413f)

(this shows two transforms on the same spot, one rotated along x, one
scaled along x)

![image](https://github.com/rerun-io/rerun/assets/1220815/b18d18c0-4ba6-4e84-a1e0-f125320655e8)


Pinhole cameras no longer are hacked to have their private gizmo -
clicking it clicks the entity with the transform now, now the pinhole
camera (this _may_ be the same, see #2568):

![image](https://github.com/rerun-io/rerun/assets/1220815/ce04c4a4-0d77-4a91-abbc-1331b3b5f36e)



### 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 [demo.rerun.io](https://demo.rerun.io/pr/2577) (if
applicable)

- [PR Build Summary](https://build.rerun.io/pr/2577)
- [Docs
preview](https://rerun.io/preview/pr%3Aandreas%2Ftransform-gizmos/docs)
- [Examples
preview](https://rerun.io/preview/pr%3Aandreas%2Ftransform-gizmos/examples)
  • Loading branch information
Wumpf authored Jul 17, 2023
1 parent fcad6c4 commit 227b49c
Show file tree
Hide file tree
Showing 17 changed files with 478 additions and 176 deletions.
18 changes: 18 additions & 0 deletions crates/re_data_store/src/entity_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ pub struct EntityProperties {

/// Used to scale the radii of the points in the resulting point cloud.
pub backproject_radius_scale: EditableAutoValue<f32>,

/// Whether to show the 3D transform visualization at all.
pub transform_3d_visible: EditableAutoValue<bool>,

/// The length of the arrows in the entity's own coordinate system (space).
pub transform_3d_size: EditableAutoValue<f32>,
}

#[cfg(feature = "serde")]
Expand All @@ -96,6 +102,8 @@ impl Default for EntityProperties {
backproject_depth: EditableAutoValue::Auto(true),
depth_from_world_scale: EditableAutoValue::default(),
backproject_radius_scale: EditableAutoValue::Auto(1.0),
transform_3d_visible: EditableAutoValue::Auto(false),
transform_3d_size: EditableAutoValue::Auto(1.0),
}
}
}
Expand Down Expand Up @@ -125,6 +133,12 @@ impl EntityProperties {
.backproject_radius_scale
.or(&child.backproject_radius_scale)
.clone(),

transform_3d_visible: self
.transform_3d_visible
.or(&child.transform_3d_visible)
.clone(),
transform_3d_size: self.transform_3d_size.or(&child.transform_3d_size).clone(),
}
}

Expand All @@ -139,6 +153,8 @@ impl EntityProperties {
backproject_depth,
depth_from_world_scale,
backproject_radius_scale,
transform_3d_visible,
transform_3d_size,
} = self;

visible != &other.visible
Expand All @@ -149,6 +165,8 @@ impl EntityProperties {
|| backproject_depth.has_edits(&other.backproject_depth)
|| depth_from_world_scale.has_edits(&other.depth_from_world_scale)
|| backproject_radius_scale.has_edits(&other.backproject_radius_scale)
|| transform_3d_visible.has_edits(&other.transform_3d_visible)
|| transform_3d_size.has_edits(&other.transform_3d_size)
}
}

Expand Down
24 changes: 23 additions & 1 deletion crates/re_renderer/examples/2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,28 @@ impl framework::Example for Render2D {
}
}

// Lines with non-default arrow heads - long thin arrows.
{
let mut line_batch = line_strip_builder
.batch("larger arrowheads")
.triangle_cap_length_factor(15.0)
.triangle_cap_width_factor(3.0);
for (i, flags) in [
LineStripFlags::FLAG_CAP_START_TRIANGLE | LineStripFlags::FLAG_CAP_END_ROUND,
LineStripFlags::FLAG_CAP_START_ROUND | LineStripFlags::FLAG_CAP_END_TRIANGLE,
LineStripFlags::FLAG_CAP_START_TRIANGLE | LineStripFlags::FLAG_CAP_END_TRIANGLE,
]
.iter()
.enumerate()
{
let y = (i + 1) as f32 * 40.0 + 650.0;
line_batch
.add_segment_2d(glam::vec2(70.0, y), glam::vec2(400.0, y))
.radius(Size::new_scene(5.0))
.flags(*flags);
}
}

// Lines with different kinds of radius
// The first two lines are the same thickness if there no (!) scaling.
// Moving the windows to a high dpi screen makes the second one bigger.
Expand Down Expand Up @@ -170,7 +192,7 @@ impl framework::Example for Render2D {
// Do in individual batches to test depth offset.
{
let num_lines = 20_i16;
let y_range = 700.0..780.0;
let y_range = 800.0..880.0;

// Cycle through which line is on top.
let top_line = ((time.seconds_since_startup() * 6.0) as i16 % (num_lines * 2 - 1)
Expand Down
30 changes: 20 additions & 10 deletions crates/re_renderer/shader/lines.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ struct BatchUniformBuffer {
outline_mask_ids: UVec2,
picking_layer_object_id: UVec2,
depth_offset: f32,
triangle_cap_length_factor: f32,
triangle_cap_width_factor: f32,
};
@group(2) @binding(0)
var<uniform> batch: BatchUniformBuffer;
Expand Down Expand Up @@ -198,7 +200,8 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
} else {
quad_dir = pos_data_quad_end.pos - pos_data_quad_begin.pos;
}
quad_dir = normalize(quad_dir);
let quad_length = length(quad_dir);
quad_dir = quad_dir / quad_length;

// Resolve radius.
// (slight inaccuracy: End caps are going to adjust their center_position)
Expand All @@ -211,38 +214,45 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
let camera_distance = distance(camera_ray.origin, center_position);
var strip_radius = unresolved_size_to_world(strip_data.unresolved_radius, camera_distance, frame.auto_size_lines);

// If the triangle cap is longer than the quad would be otherwise, we need to stunt it, otherwise we'd get artifacts.
var triangle_cap_length = batch.triangle_cap_length_factor * strip_radius;
let max_triangle_cap_length = quad_length * 0.75; // Having the entire arrow be just triangle head already looks pretty bad, so we're stopping at 75% of the quad length.
let triangle_cap_size_factor = min(1.0, max_triangle_cap_length / triangle_cap_length);
triangle_cap_length *= triangle_cap_size_factor;

// Make space for the end cap if this is either the cap itself or the cap follows right after/before this quad.
if !has_any_flag(strip_data.flags, FLAG_CAP_END_EXTEND_OUTWARDS) &&
(is_end_cap_triangle || (is_at_quad_end && pos_data_current.strip_index != pos_data_quad_after.strip_index)) {
var cap_length =
f32(has_any_flag(strip_data.flags, FLAG_CAP_END_ROUND)) +
f32(has_any_flag(strip_data.flags, FLAG_CAP_END_TRIANGLE)) * 4.0;
center_position -= quad_dir * (cap_length * strip_radius);
f32(has_any_flag(strip_data.flags, FLAG_CAP_END_ROUND)) * strip_radius +
f32(has_any_flag(strip_data.flags, FLAG_CAP_END_TRIANGLE)) * triangle_cap_length;
center_position -= quad_dir * cap_length;
}
if !has_any_flag(strip_data.flags, FLAG_CAP_START_EXTEND_OUTWARDS) &&
(is_start_cap_triangle || (!is_at_quad_end && pos_data_current.strip_index != pos_data_quad_before.strip_index)) {
var cap_length =
f32(has_any_flag(strip_data.flags, FLAG_CAP_START_ROUND)) +
f32(has_any_flag(strip_data.flags, FLAG_CAP_START_TRIANGLE)) * 4.0;
center_position += quad_dir * (cap_length * strip_radius);
f32(has_any_flag(strip_data.flags, FLAG_CAP_START_ROUND)) * strip_radius +
f32(has_any_flag(strip_data.flags, FLAG_CAP_START_TRIANGLE)) * triangle_cap_length;
center_position += quad_dir * cap_length;
}

// Boost radius only now that we subtracted/added the cap length.
// This way we don't get a gap for the outline at the cap.
if draw_data.radius_boost_in_ui_points > 0.0 {
let size_boost = world_size_from_point_size(draw_data.radius_boost_in_ui_points, camera_distance);
strip_radius += size_boost;
triangle_cap_length += size_boost;
// Push out positions as well along the quad dir.
// This is especially important if there's no miters on a line-strip (TODO(#829)),
// as this would enhance gaps between lines otherwise.
center_position += quad_dir * (size_boost * select(-1.0, 1.0, is_at_quad_end));
}

var active_radius = strip_radius;
// If this is a triangle cap, we blow up our ("virtual") quad by twice the size.
// If this is a triangle cap, we blow up our ("virtual") quad by a given factor.
if (is_end_cap_triangle && has_any_flag(strip_data.flags, FLAG_CAP_END_TRIANGLE)) ||
(is_start_cap_triangle && has_any_flag(strip_data.flags, FLAG_CAP_START_TRIANGLE)) {
active_radius *= 2.0;
active_radius *= batch.triangle_cap_width_factor * triangle_cap_size_factor;
}

// Span up the vertex away from the line's axis, orthogonal to the direction to the camera
Expand All @@ -256,7 +266,7 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
// and far enough to do rounded caps without any visible clipping.
// There is _some_ clipping, but we can't see it ;)
// If we want to do it properly, we would extend the radius for rounded caps too.
center_position += quad_dir * (strip_radius * 4.0 * select(-1.0, 1.0, is_right_triangle));
center_position += quad_dir * (triangle_cap_length * select(-1.0, 1.0, is_right_triangle));
pos = center_position;
} else {
pos = center_position + (active_radius * top_bottom) * dir_up;
Expand Down
27 changes: 21 additions & 6 deletions crates/re_renderer/src/line_strip_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,7 @@ impl LineStripSeriesBuilder {
pub fn batch(&mut self, label: impl Into<DebugLabel>) -> LineBatchBuilder<'_> {
self.batches.push(LineBatchInfo {
label: label.into(),
world_from_obj: glam::Affine3A::IDENTITY,
line_vertex_count: 0,
overall_outline_mask_ids: OutlineMaskPreference::NONE,
additional_outline_mask_ids_vertex_ranges: Vec::new(),
picking_object_id: PickingLayerObjectId::default(),
depth_offset: 0,
..LineBatchInfo::default()
});

LineBatchBuilder(self)
Expand Down Expand Up @@ -171,6 +166,26 @@ impl<'a> LineBatchBuilder<'a> {
self
}

/// Sets the length factor as multiple of a line's radius applied to all triangle caps in this batch.
///
/// This controls how far the "pointy end" of the triangle/arrow-head extends.
/// (defaults to 4.0)
#[inline]
pub fn triangle_cap_length_factor(mut self, triangle_cap_length_factor: f32) -> Self {
self.batch_mut().triangle_cap_length_factor = triangle_cap_length_factor;
self
}

/// Sets the width factor as multiple of a line's radius applied to all triangle caps in this batch.
///
/// This controls how wide the triangle/arrow-head is orthogonal to the line's direction.
/// (defaults to 2.0)
#[inline]
pub fn triangle_cap_width_factor(mut self, triangle_cap_width_factor: f32) -> Self {
self.batch_mut().triangle_cap_width_factor = triangle_cap_width_factor;
self
}

/// Adds a 3D series of line connected points.
pub fn add_strip(&mut self, points: impl Iterator<Item = glam::Vec3>) -> LineStripBuilder<'_> {
let old_strip_count = self.0.strips.len();
Expand Down
82 changes: 57 additions & 25 deletions crates/re_renderer/src/renderer/lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@ pub mod gpu_data {
pub outline_mask_ids: wgpu_buffer_types::UVec2,
pub picking_object_id: PickingLayerObjectId,

pub depth_offset: wgpu_buffer_types::F32RowPadded,
pub depth_offset: f32,
pub triangle_cap_length_factor: f32,
pub triangle_cap_width_factor: f32,
pub _padding: f32,

pub end_padding: [wgpu_buffer_types::PaddingRow; 16 - 6],
}
Expand Down Expand Up @@ -277,6 +280,34 @@ pub struct LineBatchInfo {

/// Depth offset applied after projection.
pub depth_offset: DepthOffset,

/// Length factor as multiple of a line's radius applied to all triangle caps in this batch.
///
/// This controls how far the "pointy end" of the triangle/arrow-head extends.
/// (defaults to 4.0)
pub triangle_cap_length_factor: f32,

/// Width factor as multiple of a line's radius applied to all triangle caps in this batch.
///
/// This controls how wide the triangle/arrow-head is orthogonal to the line's direction.
/// (defaults to 2.0)
pub triangle_cap_width_factor: f32,
}

impl Default for LineBatchInfo {
fn default() -> Self {
Self {
label: "unknown_line_batch".into(),
world_from_obj: glam::Affine3A::IDENTITY,
line_vertex_count: 0,
overall_outline_mask_ids: OutlineMaskPreference::NONE,
additional_outline_mask_ids_vertex_ranges: Vec::new(),
picking_object_id: PickingLayerObjectId::default(),
depth_offset: 0,
triangle_cap_length_factor: 4.0,
triangle_cap_width_factor: 2.0,
}
}
}

/// Style information for a line strip.
Expand Down Expand Up @@ -367,13 +398,9 @@ impl LineDrawData {

let batches = if batches.is_empty() {
vec![LineBatchInfo {
world_from_obj: glam::Affine3A::IDENTITY,
label: "LineDrawData::fallback_batch".into(),
line_vertex_count: vertices.len() as _,
overall_outline_mask_ids: OutlineMaskPreference::NONE,
picking_object_id: PickingLayerObjectId::default(),
additional_outline_mask_ids_vertex_ranges: Vec::new(),
depth_offset: 0,
..Default::default()
}]
} else {
batches
Expand Down Expand Up @@ -613,22 +640,31 @@ impl LineDrawData {
// Process batches
let mut batches_internal = Vec::with_capacity(batches.len());
{
fn uniforms_from_batch_info(
batch_info: &LineBatchInfo,
outline_mask_ids: [u8; 2],
) -> gpu_data::BatchUniformBuffer {
gpu_data::BatchUniformBuffer {
world_from_obj: batch_info.world_from_obj.into(),
outline_mask_ids: outline_mask_ids.into(),
picking_object_id: batch_info.picking_object_id,
depth_offset: batch_info.depth_offset as f32,
triangle_cap_length_factor: batch_info.triangle_cap_length_factor,
triangle_cap_width_factor: batch_info.triangle_cap_width_factor,
_padding: 0.0,
end_padding: Default::default(),
}
}

let uniform_buffer_bindings = create_and_fill_uniform_buffer_batch(
ctx,
"lines batch uniform buffers".into(),
batches
.iter()
.map(|batch_info| gpu_data::BatchUniformBuffer {
world_from_obj: batch_info.world_from_obj.into(),
outline_mask_ids: batch_info
.overall_outline_mask_ids
.0
.unwrap_or_default()
.into(),
picking_object_id: batch_info.picking_object_id,
depth_offset: (batch_info.depth_offset as f32).into(),
end_padding: Default::default(),
}),
batches.iter().map(|batch_info| {
uniforms_from_batch_info(
batch_info,
batch_info.overall_outline_mask_ids.0.unwrap_or_default(),
)
}),
);

// Generate additional "micro batches" for each line vertex range that has a unique outline setting.
Expand All @@ -643,12 +679,8 @@ impl LineDrawData {
batch_info
.additional_outline_mask_ids_vertex_ranges
.iter()
.map(|(_, mask)| gpu_data::BatchUniformBuffer {
world_from_obj: batch_info.world_from_obj.into(),
outline_mask_ids: mask.0.unwrap_or_default().into(),
picking_object_id: batch_info.picking_object_id,
depth_offset: (batch_info.depth_offset as f32).into(),
end_padding: Default::default(),
.map(|(_, mask)| {
uniforms_from_batch_info(batch_info, mask.0.unwrap_or_default())
})
})
.collect::<Vec<_>>()
Expand Down
Loading

0 comments on commit 227b49c

Please sign in to comment.