Skip to content
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
24 changes: 10 additions & 14 deletions crates/maps/src/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::grid_options::{GridLineDimension, GridOptions, LineType};
use crate::map_state::MapState;
use crate::movable::Draggable;
use maps_io_ros::MapPose;
use maps_rendering::{ImagePlacement, RotatedCropRequest, TextureRequest};
use maps_rendering::{ImagePlacement, TextureRequest, TransformedTextureRequest};

/// Grid area for displaying metric objects in screen space (points).
pub struct Grid {
Expand Down Expand Up @@ -143,39 +143,35 @@ impl Grid {

let relation = GridMapRelation::new(self, map);

let rect = egui::Rect::from_min_size(
self.origin_in_points + relation.ulc_to_origin_in_points,
relation.scaled_size,
);
let scaled_rect = egui::Rect::from_min_size(egui::Pos2::ZERO, relation.scaled_size);

let pose_rotation = map.pose.rot2().inverse(); // RHS to LHS
let origin_rotation = map.meta.origin_theta.inverse();

let uncropped = TextureRequest::new(map_name.to_string(), rect)
let base_request = TextureRequest::new(map_name.to_string(), scaled_rect)
.with_tint(map.tint)
.with_color_to_alpha(map.color_to_alpha)
.with_thresholding(map.get_value_interpretation())
.with_texture_options(map.texture_filter.to_egui());

let placement = ImagePlacement {
rotation: pose_rotation * origin_rotation,
translation: relation.ulc_to_origin_in_points_translated
- relation.ulc_to_origin_in_points,
translation: self.origin_in_points.to_vec2()
+ relation.ulc_to_origin_in_points_translated,
rotation_center: relation.ulc_to_origin_in_points,
points_per_pixel: relation.points_per_cell,
points_per_texel: relation.points_per_cell,
original_image_size: map.image_pyramid.original_size,
};

let request = RotatedCropRequest::from_visible(
ui,
&self.painter.clip_rect(),
uncropped,
let transformed_request = TransformedTextureRequest::from_visible(
&self.painter,
base_request,
&placement,
self.texture_crop_threshold,
);

map.get_or_create_texture_state(self.name.as_str())
.crop_and_put(ui, &request);
.transform_and_put(ui, &transformed_request);

if options.marker_visibility.maps_visible() {
self.draw_axes(options, Some(&map.pose));
Expand Down
2 changes: 1 addition & 1 deletion crates/maps/src/lens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ impl<'a> Lens<'a> {
);

// When partially visible, we deal with a UV rect inside an UV rect.
let texture_uv = texture_state.desired_uv;
let texture_uv = texture_state.desired_crop_uv;

let original_image = &texture_state.image_pyramid.original;
let original_width = original_image.width() as f32;
Expand Down
4 changes: 2 additions & 2 deletions crates/maps/tests/snapshots/fixed_lens.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions crates/maps/tests/snapshots/fixed_lens_background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions crates/maps/tests/snapshots/reload_session_config.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion crates/maps_rendering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ pub mod texture_state;
// Re-export commonly used structs and types.
pub use image_pyramid::ImagePyramid;
pub use render_options::TextureFilter;
pub use texture_request::{ImagePlacement, NO_TINT, RotatedCropRequest, TextureRequest};
pub use texture_request::{ImagePlacement, NO_TINT, TextureRequest, TransformedTextureRequest};
pub use texture_state::TextureState;
8 changes: 4 additions & 4 deletions crates/maps_rendering/src/rect_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use eframe::egui;
use log::{Level, log_enabled};

/// Rotates a rectangle around an origin point and returns the bounding rectangle.
pub fn rotate(rect: &egui::Rect, rot: egui::emath::Rot2, origin: egui::Vec2) -> egui::Rect {
/// Rotates a rectangle around an origin point and returns the axis-aligned bounding rectangle.
pub fn rotate_aabb(rect: &egui::Rect, rot: egui::emath::Rot2, origin: egui::Vec2) -> egui::Rect {
let a = origin + rot * (rect.left_top() - origin.to_pos2());
let b = origin + rot * (rect.right_top() - origin.to_pos2());
let c = origin + rot * (rect.left_bottom() - origin.to_pos2());
Expand Down Expand Up @@ -54,9 +54,9 @@ pub fn quantized_intersection(
}

/// Paints a rectangle with a color when trace logging is enabled.
pub fn debug_paint(ui: &egui::Ui, rect: egui::Rect, color: egui::Color32, label: &str) {
pub fn debug_paint(painter: &egui::Painter, rect: egui::Rect, color: egui::Color32, label: &str) {
if !log_enabled!(Level::Trace) {
return;
}
ui.painter().debug_rect(rect, color, label);
painter.debug_rect(rect, color, label);
}
127 changes: 68 additions & 59 deletions crates/maps_rendering/src/texture_request.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use eframe::egui;
use eframe::emath::GuiRounding as _;

use crate::rect_helpers::{debug_paint, quantized_intersection, rotate};
use crate::rect_helpers::{debug_paint, quantized_intersection, rotate_aabb};
use maps_io_ros::ValueInterpretation;

pub const NO_TINT: egui::Color32 = egui::Color32::WHITE;
Expand Down Expand Up @@ -78,21 +78,22 @@ impl TextureRequest {
/// Extended request for rendering scaled textures with arbitrary rotated pose
/// and support for cropping (e.g. to viewport).
#[derive(Debug)]
pub struct RotatedCropRequest {
/// Base texture request for the bare unrotated & uncropped image rect.
pub uncropped: TextureRequest,
/// Bounding box rectangle of the visible area of the final rotated & cropped texture.
pub visible_rect: egui::Rect,
pub struct TransformedTextureRequest {
/// Base texture request for the bare unrotated & untransformed image rect.
pub base_request: TextureRequest,
/// Rectangle in the scaled image coordinate space defining the
/// potentially cropped region to extract before applying transformations.
pub crop_rect: egui::Rect,
/// Image crop specified in UV image coordinates.
pub uv: [egui::Pos2; 2],
pub crop_uv: [egui::Pos2; 2],
/// Desired rotation of the texture.
pub rotation: eframe::emath::Rot2,
/// Desired translation of the texture.
pub translation: egui::Vec2,
/// Rotation center of the image in UV image coordinates.
pub rotation_center_in_uv: egui::Vec2,
/// Scale of the texture, i.e. desired screen points per pixel.
pub points_per_pixel: f32,
/// Scale of the texture, i.e. desired screen points per texel.
pub points_per_texel: f32,
}

/// Information needed for placement of an image as a scaled texture at a 2D pose.
Expand All @@ -103,66 +104,76 @@ pub struct ImagePlacement {
pub translation: egui::Vec2,
/// Position of the image's rotation center in points relative to the viewport.
pub rotation_center: egui::Vec2,
/// Amount of points occupied by a pixel of the image, for scaling.
pub points_per_pixel: f32,
/// Amount of points occupied by a texel of the image, for scaling.
pub points_per_texel: f32,
/// Size of the unscaled, uncropped source image in pixels.
pub original_image_size: egui::Vec2,
}

impl RotatedCropRequest {
impl TransformedTextureRequest {
/// Returns true if this request represents a full texture (not a crop).
pub fn is_full_texture(&self) -> bool {
self.uv[0] == egui::Pos2::ZERO && self.uv[1] == egui::pos2(1.0, 1.0)
self.crop_uv[0] == egui::Pos2::ZERO && self.crop_uv[1] == egui::pos2(1.0, 1.0)
}

/// Pre-calculate the minimal, unrotated crop that is needed to show the rotated surface in the viewport.
/// I.e. neither clipping too much nor making the texture unnecessarily large / inefficient.
/// Enable trace log level to see what is going on (I spent too much time figuring this out).
fn min_crop(
ui: &egui::Ui,
viewport_clip_rect: &egui::Rect,
image_rect: &egui::Rect,
paint_context: &egui::Painter,
scaled_rect: &egui::Rect,
rotation: eframe::emath::Rot2,
translation: egui::Vec2,
rotation_center_in_points: egui::Vec2,
points_per_pixel: f32,
points_per_texel: f32,
) -> egui::Rect {
let origin_in_points = (image_rect.min - rotation_center_in_points).to_vec2();
let origin_in_points = (scaled_rect.min - rotation_center_in_points).to_vec2();

let rotated = rotate(image_rect, rotation, origin_in_points);
let transformed = rotated.translate(translation);
debug_paint(ui, transformed, egui::Color32::RED, "transformed");
let transformed_aabb =
rotate_aabb(scaled_rect, rotation, origin_in_points).translate(translation);
debug_paint(
paint_context,
transformed_aabb,
egui::Color32::RED,
"transformed_aabb",
);

let transformed_visible = transformed.intersect(*viewport_clip_rect);
let transformed_aabb_visible = transformed_aabb.intersect(paint_context.clip_rect());
debug_paint(
ui,
transformed_visible,
paint_context,
transformed_aabb_visible,
egui::Color32::GOLD,
"transformed_visible",
"transformed_aabb_visible",
);

let min_crop = rotate(
&transformed_visible.translate(-translation),
let min_crop = rotate_aabb(
&transformed_aabb_visible.translate(-translation),
rotation.inverse(),
origin_in_points,
);
debug_paint(ui, min_crop, egui::Color32::BLUE, "min_crop");
let ui_offset = paint_context.clip_rect().min.to_vec2();
debug_paint(
paint_context,
min_crop.translate(ui_offset),
egui::Color32::BLUE,
"min_crop",
);

// The minimal rectangle is the instersection of crop rectangle and image rectangle.
// The image cropping happens in pixel space, so we have to also quantize the rectangle
// to the next best multiple of the scaled pixel size.
// Otherwise the texture size/placement is not exact, especially at high zoom levels.
let visible_rect = quantized_intersection(image_rect, &min_crop, points_per_pixel);
// Round visible_rect matching egui 0.32's "pixel-perfect" paint_at behavior.
let crop_rect = quantized_intersection(scaled_rect, &min_crop, points_per_texel);
// Round crop_rect matching egui 0.32's "pixel-perfect" paint_at behavior.
// See also: https://github.com/emilk/egui/pull/7078
let visible_rect = visible_rect.round_to_pixels(ui.pixels_per_point());
let crop_rect = crop_rect.round_to_pixels(paint_context.ctx().pixels_per_point());
debug_paint(
ui,
visible_rect,
paint_context,
crop_rect.translate(ui_offset),
egui::Color32::GREEN,
"visible_rect_quantized",
"crop_rect_quantized",
);
visible_rect
crop_rect
}

/// Creates a request for displaying an image with the desired `placement`
Expand All @@ -171,53 +182,51 @@ impl RotatedCropRequest {
/// cropped to the viewport. Use this to support displaying large images
/// at high zoom levels as cropped textures to avoid texture buffer size limits.
pub fn from_visible(
ui: &egui::Ui,
viewport_clip_rect: &egui::Rect,
uncropped: TextureRequest,
paint_context: &egui::Painter,
base_request: TextureRequest,
placement: &ImagePlacement,
crop_threshold: u32,
) -> RotatedCropRequest {
let image_rect = uncropped.desired_rect;
let visible_rect = if uncropped.desired_rect.size().max_elem() as u32 <= crop_threshold
) -> TransformedTextureRequest {
let scaled_rect = base_request.desired_rect;
let crop_rect = if scaled_rect.size().max_elem() as u32 <= crop_threshold
|| placement.original_image_size.max_elem() as u32 <= crop_threshold
{
// Desired texture is small enough to not need cropping.
image_rect
scaled_rect
} else {
// Desired texture is large, crop to the viewport.
Self::min_crop(
ui,
viewport_clip_rect,
&image_rect,
paint_context,
&scaled_rect,
placement.rotation,
placement.translation,
placement.rotation_center,
placement.points_per_pixel,
placement.points_per_texel,
)
};

RotatedCropRequest {
uncropped,
visible_rect,
uv: [
TransformedTextureRequest {
base_request,
crop_rect,
crop_uv: [
egui::Pos2::new(
(visible_rect.min.x - image_rect.min.x) / image_rect.width(),
(visible_rect.min.y - image_rect.min.y) / image_rect.height(),
(crop_rect.min.x - scaled_rect.min.x) / scaled_rect.width(),
(crop_rect.min.y - scaled_rect.min.y) / scaled_rect.height(),
),
egui::Pos2::new(
(visible_rect.max.x - image_rect.min.x) / image_rect.width(),
(visible_rect.max.y - image_rect.min.y) / image_rect.height(),
(crop_rect.max.x - scaled_rect.min.x) / scaled_rect.width(),
(crop_rect.max.y - scaled_rect.min.y) / scaled_rect.height(),
),
],
rotation: placement.rotation,
translation: placement.translation,
rotation_center_in_uv: egui::Vec2::new(
-(placement.rotation_center.x + (visible_rect.min.x - image_rect.min.x))
/ visible_rect.width(),
-(placement.rotation_center.y + (visible_rect.min.y - image_rect.min.y))
/ visible_rect.height(),
-(placement.rotation_center.x + (crop_rect.min.x - scaled_rect.min.x))
/ crop_rect.width(),
-(placement.rotation_center.y + (crop_rect.min.y - scaled_rect.min.y))
/ crop_rect.height(),
),
points_per_pixel: placement.points_per_pixel,
points_per_texel: placement.points_per_texel,
}
}
}
Loading
Loading