Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix arrows requiring a radius to be visible #1720

Merged
merged 6 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 62 additions & 39 deletions crates/re_renderer/shader/lines.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ const POSITION_DATA_TEXTURE_SIZE: i32 = 256;
// See lines.rs#LineStripFlags
const CAP_END_TRIANGLE: u32 = 1u;
const CAP_END_ROUND: u32 = 2u;
const CAP_START_TRIANGLE: u32 = 4u;
const CAP_START_ROUND: u32 = 8u;
const NO_COLOR_GRADIENT: u32 = 16u;
const CAP_END_EXTEND_OUTWARDS: u32 = 4u;
const CAP_START_TRIANGLE: u32 = 8u;
const CAP_START_ROUND: u32 = 16u;
const CAP_START_EXTEND_OUTWARDS: u32 = 32u;
const NO_COLOR_GRADIENT: u32 = 64u;

// A lot of the attributes don't need to be interpolated across triangles.
// To document that and safe some time we mark them up with @interpolate(flat)
Expand All @@ -63,10 +65,10 @@ struct VertexOut {
active_radius: f32,

@location(4) @interpolate(perspective)
closest_strip_position: Vec3,
round_cap_circle_center: Vec3,

@location(5) @interpolate(flat)
currently_active_flags: u32,
fragment_flags: u32,
};

struct LineStripData {
Expand Down Expand Up @@ -140,14 +142,19 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
let local_idx = vertex_idx % 6u;
let top_bottom = select(-1.0, 1.0, (local_idx <= 1u || local_idx == 3u)); // 1 for a top vertex, -1 for a bottom vertex.

// There's a left and a right triangle in every quad, see sketch above.
// Right triangle can form end caps, left triangles can form start caps.
let is_right_triangle = local_idx >= 3u;

// Let's assume for starters this vertex is part of a regular quad in a line strip.
// Fetch position data at the beginning and the end of that quad.
// Fetch position data at the beginning and the end of that quad, as well as the position data before and after this quad.
let pos_data_quad_before = read_position_data(pos_data_idx - 1u);
let pos_data_quad_begin = read_position_data(pos_data_idx);
let pos_data_quad_end = read_position_data(pos_data_idx + 1u);
let pos_data_quad_after = read_position_data(pos_data_idx + 2u);

// If the strip indices don't match up for start/end, then we're in a cap triangle!
let is_cap_triangle = pos_data_quad_begin.strip_index != pos_data_quad_end.strip_index;
let is_end_cap = is_cap_triangle && local_idx >= 3u;

// Let's determine which one of the two position data is closer to our vertex.
// Which tells us things:
Expand All @@ -156,75 +163,90 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
//
// For caps, we determine the "only valid one" (as one of them belongs to another strip)
var pos_data_current: PositionData;
if is_end_cap || (!is_cap_triangle && !is_at_quad_end) {
if (is_cap_triangle && is_right_triangle) || (!is_cap_triangle && !is_at_quad_end) {
pos_data_current = pos_data_quad_begin;
} else {
pos_data_current = pos_data_quad_end;
}

// Note that for caps triangles, the pos_data_current.pos stays constant over the entire triangle!
// However, to handle things in the fragment shader we need to add a seceond position which is different
// for start/end of the cap triangle.
// The closest "line strip skeleton" position to the current vertex.
// Various things like end cap or radius boosting can cause adjustments to it.
var center_position = pos_data_current.pos;

// Data valid for the entire strip that this vertex belongs to.
let strip_data = read_strip_data(pos_data_current.strip_index);

// Active flags are all flags that we react to at the current vertex.
// I.e. cap flags are only active in the respective cap triangle.
var currently_active_flags = strip_data.flags & (~(CAP_START_TRIANGLE | CAP_END_TRIANGLE | CAP_START_ROUND | CAP_END_ROUND));

// Compute quad_dir and correct the currently_active_flags & correct center_position triangle caps.
// Compute quad_dir & correct center_position for triangle caps.
var quad_dir: Vec3;
var is_at_pointy_end = false;
if is_cap_triangle {
if is_end_cap && has_any_flag(strip_data.flags, CAP_END_TRIANGLE | CAP_END_ROUND) {
currently_active_flags |= strip_data.flags & (CAP_END_TRIANGLE | CAP_END_ROUND);
is_at_pointy_end = is_at_quad_end;
quad_dir = pos_data_quad_begin.pos - read_position_data(pos_data_idx - 1u).pos; // Go one pos data back
} else if !is_end_cap && has_any_flag(strip_data.flags, CAP_START_TRIANGLE | CAP_START_ROUND) {
currently_active_flags |= strip_data.flags & (CAP_START_TRIANGLE | CAP_START_ROUND);
is_at_pointy_end = !is_at_quad_end;
quad_dir = read_position_data(pos_data_idx + 2u).pos - pos_data_quad_end.pos; // Go one pos data forward
} else {
// Discard vertex.
center_position = Vec3(0.0/0.0, 0.0/0.0, 0.0/0.0);
}
quad_dir = normalize(quad_dir);
let is_end_cap_triangle = is_cap_triangle && is_right_triangle && has_any_flag(strip_data.flags, CAP_END_TRIANGLE | CAP_END_ROUND);
let is_start_cap_triangle = is_cap_triangle && !is_right_triangle && has_any_flag(strip_data.flags, CAP_START_TRIANGLE | CAP_START_ROUND);
if is_end_cap_triangle {
is_at_pointy_end = is_at_quad_end;
quad_dir = pos_data_quad_begin.pos - pos_data_quad_before.pos; // Go one pos data back.
} else if is_start_cap_triangle {
is_at_pointy_end = !is_at_quad_end;
quad_dir = pos_data_quad_after.pos - pos_data_quad_end.pos; // Go one pos data forward.
} else if is_cap_triangle {
// Discard vertex.
center_position = Vec3(0.0/0.0, 0.0/0.0, 0.0/0.0);
} else {
quad_dir = normalize(pos_data_quad_end.pos - pos_data_quad_begin.pos);
quad_dir = pos_data_quad_end.pos - pos_data_quad_begin.pos;
}
quad_dir = normalize(quad_dir);

// Resolve radius.
// (slight inaccuracy: End caps are going to adjust their center_position)
let camera_ray = camera_ray_to_world_pos(center_position);
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);

// 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, 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, CAP_END_ROUND)) +
f32(has_any_flag(strip_data.flags, CAP_END_TRIANGLE)) * 4.0;
center_position -= quad_dir * (cap_length * strip_radius);
}
if !has_any_flag(strip_data.flags, 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, CAP_START_ROUND)) +
f32(has_any_flag(strip_data.flags, CAP_START_TRIANGLE)) * 4.0;
center_position += quad_dir * (cap_length * strip_radius);
}

// 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;
// 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));
center_position += quad_dir * (size_boost * select(-1.0, 1.0, is_right_triangle));
}

var active_radius = strip_radius;
// If this is a triangle cap, we blow up our ("virtual") quad by twice the size.
if has_any_flag(currently_active_flags, CAP_START_TRIANGLE | CAP_END_TRIANGLE) {
if (is_end_cap_triangle && has_any_flag(strip_data.flags, CAP_END_TRIANGLE)) ||
(is_start_cap_triangle && has_any_flag(strip_data.flags, CAP_START_TRIANGLE)) {
active_radius *= 2.0;
}

// Span up the vertex away from the line's axis, orthogonal to the direction to the camera
let dir_up = normalize(cross(camera_ray.direction, quad_dir));

let round_cap_circle_center = center_position;

var pos: Vec3;
if is_cap_triangle && is_at_pointy_end {
// We extend the cap triangle far enough to handle triangle caps,
// 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 = pos_data_current.pos + quad_dir * (strip_radius * 4.0 * select(-1.0, 1.0, is_end_cap));
center_position += quad_dir * (strip_radius * 4.0 * select(-1.0, 1.0, is_right_triangle));
pos = center_position;
} else {
pos = center_position + (active_radius * top_bottom) * dir_up;
Expand All @@ -235,18 +257,19 @@ fn vs_main(@builtin(vertex_index) vertex_idx: u32) -> VertexOut {
out.position = frame.projection_from_world * Vec4(pos, 1.0);
out.position_world = pos;
out.center_position = center_position;
out.closest_strip_position = pos_data_current.pos;
out.round_cap_circle_center = round_cap_circle_center;
out.color = strip_data.color;
out.active_radius = active_radius;
out.currently_active_flags = currently_active_flags;
out.fragment_flags = strip_data.flags &
(NO_COLOR_GRADIENT | (u32(is_cap_triangle) * select(CAP_START_ROUND, CAP_END_ROUND, is_right_triangle)));

return out;
}

fn compute_coverage(in: VertexOut) -> f32 {
var coverage = 1.0;
if has_any_flag(in.currently_active_flags, CAP_START_ROUND | CAP_END_ROUND) {
let distance_to_skeleton = length(in.position_world - in.closest_strip_position);
if has_any_flag(in.fragment_flags, CAP_START_ROUND | CAP_END_ROUND) {
let distance_to_skeleton = length(in.position_world - in.round_cap_circle_center);
let pixel_world_size = approx_pixel_world_size_at(length(in.position_world - frame.camera_position));

// It's important that we do antialias both inwards and outwards of the exact border.
Expand All @@ -267,7 +290,7 @@ fn fs_main(in: VertexOut) -> @location(0) Vec4 {

// TODO(andreas): lighting setup
var shading = 1.0;
if !has_any_flag(in.currently_active_flags, NO_COLOR_GRADIENT) { // TODO(andreas): Flip flag meaning.
if !has_any_flag(in.fragment_flags, NO_COLOR_GRADIENT) { // TODO(andreas): Flip flag meaning.
let to_center = in.position_world - in.center_position;
let relative_distance_to_center_sq = dot(to_center, to_center) / (in.active_radius * in.active_radius);
shading = max(0.2, 1.0 - relative_distance_to_center_sq) * 0.9;
Expand Down
8 changes: 6 additions & 2 deletions crates/re_renderer/src/line_strip_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@ where
.flags(
LineStripFlags::CAP_END_ROUND
| LineStripFlags::CAP_START_ROUND
| LineStripFlags::NO_COLOR_GRADIENT,
| LineStripFlags::NO_COLOR_GRADIENT
| LineStripFlags::CAP_END_EXTEND_OUTWARDS
| LineStripFlags::CAP_START_EXTEND_OUTWARDS,
)
}

Expand Down Expand Up @@ -325,7 +327,9 @@ where
.flags(
LineStripFlags::CAP_END_ROUND
| LineStripFlags::CAP_START_ROUND
| LineStripFlags::NO_COLOR_GRADIENT,
| LineStripFlags::NO_COLOR_GRADIENT
| LineStripFlags::CAP_END_EXTEND_OUTWARDS
| LineStripFlags::CAP_START_EXTEND_OUTWARDS,
)
}

Expand Down
22 changes: 11 additions & 11 deletions crates/re_renderer/src/renderer/lines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,22 +213,22 @@ bitflags! {
/// Adds a round cap at the end of a line strip (excludes other end caps).
const CAP_END_ROUND = 0b0000_0010;

/// By default, line caps end at the last/first position of the the line strip.
/// This flag makes end caps extend outwards.
const CAP_END_EXTEND_OUTWARDS = 0b0000_0100;

/// Puts a equilateral triangle at the start of the line strip (excludes other start caps).
const CAP_START_TRIANGLE = 0b0000_0100;
const CAP_START_TRIANGLE = 0b0000_1000;

/// Adds a round cap at the start of a line strip (excludes other start caps).
const CAP_START_ROUND = 0b0000_1000;
const CAP_START_ROUND = 0b0001_0000;

/// Disable color gradient which is on by default
const NO_COLOR_GRADIENT = 0b0001_0000;
}
}
/// By default, line caps end at the last/first position of the the line strip.
/// This flag makes end caps extend outwards.
const CAP_START_EXTEND_OUTWARDS = 0b0010_0000;

impl LineStripFlags {
pub fn get_triangle_cap_tip_length(line_radius: f32) -> f32 {
// hardcoded in lines.wgsl
// Alternatively we could declare the entire last segment to be a tip, making the line length configurable!
line_radius * 4.0
/// Disable color gradient which is on by default
const NO_COLOR_GRADIENT = 0b0100_0000;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use re_log_types::{
Arrow3D, Component,
};
use re_query::{query_primary_with_history, EntityView, QueryError};
use re_renderer::{renderer::LineStripFlags, Size};
use re_renderer::Size;

use crate::{
misc::{SpaceViewHighlights, TransformCache, ViewerContext},
Expand Down Expand Up @@ -67,15 +67,17 @@ impl Arrows3DPart {
let origin = glam::Vec3::from(origin);

let radius = radius.map_or(Size::AUTO, |r| Size(r.0));
let tip_length = LineStripFlags::get_triangle_cap_tip_length(radius.0);
let vector_len = vector.length();
let end = origin + vector * ((vector_len - tip_length) / vector_len);
let end = origin + vector;
Wumpf marked this conversation as resolved.
Show resolved Hide resolved

let segment = line_batch
.add_segment(origin, end)
.radius(radius)
.color(color)
.flags(re_renderer::renderer::LineStripFlags::CAP_END_TRIANGLE)
.flags(
re_renderer::renderer::LineStripFlags::CAP_END_TRIANGLE
| re_renderer::renderer::LineStripFlags::CAP_START_ROUND
| re_renderer::renderer::LineStripFlags::CAP_START_EXTEND_OUTWARDS,
)
.user_data(picking_instance_hash);

if let Some(outline_mask_ids) = entity_highlight.instances.get(&instance_key) {
Expand Down