maps/
texture_request.rs

1use eframe::egui;
2
3use crate::rect_helpers::{debug_paint, quantized_intersection, rotate};
4use crate::value_interpretation::ValueInterpretation;
5
6pub const NO_TINT: egui::Color32 = egui::Color32::WHITE;
7
8#[derive(Debug)]
9pub struct TextureRequest {
10    pub client: String,
11    pub desired_rect: egui::Rect,
12    pub tint: egui::Color32,
13    pub color_to_alpha: Option<egui::Color32>,
14    pub thresholding: Option<ValueInterpretation>,
15    pub sense: egui::Sense,
16    pub texture_options: Option<egui::TextureOptions>,
17}
18
19impl TextureRequest {
20    pub fn new(client: String, desired_rect: egui::Rect) -> TextureRequest {
21        TextureRequest {
22            client,
23            desired_rect,
24            tint: NO_TINT,
25            color_to_alpha: None,
26            thresholding: None,
27            sense: egui::Sense::hover(),
28            texture_options: None,
29        }
30    }
31
32    pub fn with_sense(mut self, sense: egui::Sense) -> TextureRequest {
33        self.sense = sense;
34        self
35    }
36
37    pub fn with_tint(mut self, tint: Option<egui::Color32>) -> TextureRequest {
38        match tint {
39            Some(tint) => {
40                self.tint = tint;
41            }
42            None => {
43                self.tint = NO_TINT;
44            }
45        }
46        self
47    }
48
49    pub fn with_color_to_alpha(mut self, color_to_alpha: Option<egui::Color32>) -> TextureRequest {
50        self.color_to_alpha = color_to_alpha;
51        self
52    }
53
54    pub fn with_thresholding(
55        mut self,
56        thresholding: Option<&ValueInterpretation>,
57    ) -> TextureRequest {
58        self.thresholding = thresholding.copied();
59        self
60    }
61
62    pub fn with_texture_options(
63        mut self,
64        texture_options: &egui::TextureOptions,
65    ) -> TextureRequest {
66        self.texture_options = Some(*texture_options);
67        self
68    }
69}
70
71#[derive(Debug)]
72pub struct RotatedCropRequest {
73    pub uncropped: TextureRequest,
74    pub visible_rect: egui::Rect,
75    pub uv: [egui::Pos2; 2],
76    pub rotation: eframe::emath::Rot2,
77    pub translation: egui::Vec2,
78    pub rotation_center_in_uv: egui::Vec2,
79    pub points_per_pixel: f32,
80}
81
82impl RotatedCropRequest {
83    /// Pre-calculate the minimal, unrotated crop that is needed to show the rotated surface in the viewport.
84    /// I.e. neither clipping too much nor making the texture unnecessarily large / inefficient.
85    /// Enable trace log level to see what is going on (I spent too much time figuring this out).
86    fn min_crop(
87        ui: &egui::Ui,
88        image_rect: &egui::Rect,
89        rotation: &eframe::emath::Rot2,
90        translation: &egui::Vec2,
91        rotation_center_in_points: &egui::Vec2,
92        points_per_pixel: f32,
93    ) -> egui::Rect {
94        let viewport_rect = ui.clip_rect();
95        let origin_in_points = (image_rect.min - *rotation_center_in_points).to_vec2();
96
97        let rotated = rotate(&image_rect, *rotation, origin_in_points);
98        let transformed = rotated.translate(*translation);
99        debug_paint(ui, transformed, egui::Color32::RED, "transformed");
100
101        let transformed_visible = transformed.intersect(viewport_rect);
102        debug_paint(
103            ui,
104            transformed_visible,
105            egui::Color32::GOLD,
106            "transformed_visible",
107        );
108
109        let min_crop = rotate(
110            &transformed_visible.translate(-*translation),
111            rotation.inverse(),
112            origin_in_points,
113        );
114        debug_paint(ui, min_crop, egui::Color32::BLUE, "min_crop");
115
116        // The minimal rectangle is the instersection of crop rectangle and image rectangle.
117        // The image cropping happens in pixel space, so we have to also quantize the rectangle
118        // to the next best multiple of the scaled pixel size.
119        // Otherwise the texture size/placement is not exact, especially at high zoom levels.
120        let visible_rect = quantized_intersection(&image_rect, &min_crop, points_per_pixel);
121        debug_paint(
122            ui,
123            visible_rect,
124            egui::Color32::GREEN,
125            "visible_rect_quantized",
126        );
127        visible_rect
128    }
129
130    pub fn from_visible(
131        ui: &egui::Ui,
132        uncropped: TextureRequest,
133        rotation: egui::emath::Rot2,
134        translation: egui::Vec2,
135        rotation_center_in_points: egui::Vec2,
136        points_per_pixel: f32,
137        crop_threshold: u32,
138        original_image_size: egui::Vec2,
139    ) -> RotatedCropRequest {
140        let image_rect = uncropped.desired_rect;
141        let visible_rect = if uncropped.desired_rect.size().max_elem() as u32 <= crop_threshold
142            || original_image_size.max_elem() as u32 <= crop_threshold
143        {
144            // Desired texture is small enough to not need cropping.
145            image_rect
146        } else {
147            // Desired texture is large, crop to the viewport.
148            Self::min_crop(
149                ui,
150                &image_rect,
151                &rotation,
152                &translation,
153                &rotation_center_in_points,
154                points_per_pixel,
155            )
156        };
157
158        RotatedCropRequest {
159            uncropped,
160            visible_rect,
161            uv: [
162                egui::Pos2::new(
163                    (visible_rect.min.x - image_rect.min.x) / image_rect.width(),
164                    (visible_rect.min.y - image_rect.min.y) / image_rect.height(),
165                ),
166                egui::Pos2::new(
167                    (visible_rect.max.x - image_rect.min.x) / image_rect.width(),
168                    (visible_rect.max.y - image_rect.min.y) / image_rect.height(),
169                ),
170            ],
171            rotation,
172            translation,
173            rotation_center_in_uv: egui::Vec2::new(
174                -(rotation_center_in_points.x + (visible_rect.min.x - image_rect.min.x))
175                    / visible_rect.width(),
176                -(rotation_center_in_points.y + (visible_rect.min.y - image_rect.min.y))
177                    / visible_rect.height(),
178            ),
179            points_per_pixel,
180        }
181    }
182}