diff --git a/crates/re_renderer/src/draw_phases/picking_layer.rs b/crates/re_renderer/src/draw_phases/picking_layer.rs index 931f8489189e..d2466a1d7329 100644 --- a/crates/re_renderer/src/draw_phases/picking_layer.rs +++ b/crates/re_renderer/src/draw_phases/picking_layer.rs @@ -228,8 +228,8 @@ impl PickingLayerProcessor { }); let cropped_projection_from_projection = RectTransform { - from: picking_rect.into(), - to: RectF32 { + region_of_interest: picking_rect.into(), + region: RectF32 { min: glam::Vec2::ZERO, extent: screen_resolution.as_vec2(), }, diff --git a/crates/re_renderer/src/rect.rs b/crates/re_renderer/src/rect.rs index 65fcd9cbfe9a..f62d59364a2d 100644 --- a/crates/re_renderer/src/rect.rs +++ b/crates/re_renderer/src/rect.rs @@ -65,6 +65,11 @@ impl RectF32 { self.min + self.extent } + #[inline] + pub fn center(self) -> glam::Vec2 { + self.min + self.extent / 2.0 + } + #[inline] pub fn scale_extent(self, factor: f32) -> RectF32 { RectF32 { diff --git a/crates/re_renderer/src/transform.rs b/crates/re_renderer/src/transform.rs index 81fbb3838822..dfff2f7b93ef 100644 --- a/crates/re_renderer/src/transform.rs +++ b/crates/re_renderer/src/transform.rs @@ -34,30 +34,36 @@ pub fn ndc_from_pixel(pixel_coord: glam::Vec2, screen_extent: glam::UVec2) -> gl ) } +/// Defines a transformation from a rectangular region of interest into a rectangular target region. +/// +/// Transforms map the range of `region_of_interest` to the range of `region`. #[derive(Clone, Debug)] pub struct RectTransform { - pub from: RectF32, - pub to: RectF32, + pub region_of_interest: RectF32, + pub region: RectF32, } impl RectTransform { /// No-op rect transform that transforms from a unit rectangle to a unit rectangle. pub const IDENTITY: RectTransform = RectTransform { - from: RectF32::UNIT, - to: RectF32::UNIT, + region_of_interest: RectF32::UNIT, + region: RectF32::UNIT, }; /// Computes a transformation matrix that applies the rect transform to the NDC space. /// + /// This matrix is expected to be the left most transformation in the vertex transformation chain. + /// It causes the area described by `region_of_interest` to be mapped to the area described by `region`. + /// Meaning, that `region` represents the full screen of the NDC space. /// - /// Note only the relation of the rectangles in `RectTransform` is important. + /// This means that only the relation of the rectangles in `RectTransform` is important. /// Scaling or moving both rectangles by the same amount does not change the result. pub fn to_ndc_scale_and_translation(&self) -> glam::Mat4 { // It's easier to think in texcoord space, and then transform to NDC. // This texcoord rect specifies the portion of the screen that should become the entire range of the NDC screen. let texcoord_rect = RectF32 { - min: (self.from.min - self.to.min) / self.to.extent, - extent: self.from.extent / self.to.extent, + min: (self.region_of_interest.min - self.region.min) / self.region.extent, + extent: self.region_of_interest.extent / self.region.extent, }; let texcoord_rect_min = texcoord_rect.min; let texcoord_rect_max = texcoord_rect.max(); @@ -74,6 +80,92 @@ impl RectTransform { } pub fn scale(&self) -> glam::Vec2 { - self.to.extent / self.from.extent + self.region.extent / self.region_of_interest.extent + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn to_ndc_scale_and_translation() { + let region = RectF32 { + min: glam::vec2(1.0, 1.0), + extent: glam::vec2(2.0, 3.0), + }; + + // Identity + { + let rect_transform = RectTransform { + region_of_interest: region, + region, + }; + let identity = rect_transform.to_ndc_scale_and_translation(); + assert_eq!(identity, glam::Mat4::IDENTITY); + } + + // Scale + { + let scale_factor = glam::vec2(2.0, 0.25); + + let rect_transform = RectTransform { + region_of_interest: RectF32 { + // Move the roi to the middle of the region. + min: region.center() - region.extent * scale_factor * 0.5, + extent: region.extent * scale_factor, + }, + region, + }; + let scale = rect_transform.to_ndc_scale_and_translation(); + assert_eq!( + scale, + glam::Mat4::from_scale(1.0 / scale_factor.extend(1.0)) + ); + } + + // Translation + { + let translation_vec = glam::vec2(1.0, 2.0); + + let rect_transform = RectTransform { + region_of_interest: RectF32 { + min: region.min + translation_vec * region.extent, + extent: region.extent, + }, + region, + }; + let translation = rect_transform.to_ndc_scale_and_translation(); + assert_eq!( + translation, + glam::Mat4::from_translation( + glam::vec3(-translation_vec.x, translation_vec.y, 0.0) * 2.0 + ) + ); + } + + // Scale + translation + { + let scale_factor = glam::vec2(2.0, 0.25); + let translation_vec = glam::vec2(1.0, 2.0); + + let rect_transform = RectTransform { + region_of_interest: RectF32 { + // Move the roi to the middle of the region and then apply translation + min: region.center() - region.extent * scale_factor * 0.5 + + translation_vec * region.extent, + extent: region.extent * scale_factor, + }, + region, + }; + let scale_and_translation = rect_transform.to_ndc_scale_and_translation(); + assert_eq!( + scale_and_translation, + glam::Mat4::from_scale(1.0 / scale_factor.extend(1.0)) + * glam::Mat4::from_translation( + glam::vec3(-translation_vec.x, translation_vec.y, 0.0) * 2.0 + ) + ); + } } } diff --git a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs index 0417ae659e95..e2123b513994 100644 --- a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs +++ b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs @@ -456,15 +456,15 @@ fn setup_target_config( // Cut to the portion of the currently visible ui area. let mut viewport_transformation = re_renderer::RectTransform { - from: egui_rect_to_re_renderer(painter.clip_rect()), - to: egui_rect_to_re_renderer(*canvas_from_ui.from()), + region_of_interest: egui_rect_to_re_renderer(painter.clip_rect()), + region: egui_rect_to_re_renderer(*canvas_from_ui.from()), }; // The principal point might not be quite centered. // We need to account for this translation in the viewport transformation. let principal_point_offset = default_principal_point - pinhole.principal_point(); let ui_from_canvas_scale = canvas_from_ui.inverse().scale(); - viewport_transformation.from.min += + viewport_transformation.region_of_interest.min += principal_point_offset * glam::vec2(ui_from_canvas_scale.x, ui_from_canvas_scale.y); Ok({