1use crate::{Point, Rect, Size};
4
5#[derive(Clone, Copy, Debug, Default, PartialEq)]
7pub struct WindowCoordinates {
8 pub size: Size,
10 pub local_to_window: ProjectiveTransform,
12}
13
14impl WindowCoordinates {
15 pub fn bounds(self) -> Rect {
17 self.local_to_window
18 .bounds_for_rect(Rect::from_size(self.size))
19 }
20}
21
22#[derive(Clone, Copy, Debug, PartialEq)]
24pub struct ProjectiveTransform {
25 matrix: [[f32; 3]; 3],
26}
27
28impl ProjectiveTransform {
29 pub const fn identity() -> Self {
31 Self {
32 matrix: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
33 }
34 }
35
36 pub fn translation(tx: f32, ty: f32) -> Self {
38 Self {
39 matrix: [[1.0, 0.0, tx], [0.0, 1.0, ty], [0.0, 0.0, 1.0]],
40 }
41 }
42
43 pub fn uniform_scale(scale: f32) -> Self {
46 Self {
47 matrix: [[scale, 0.0, 0.0], [0.0, scale, 0.0], [0.0, 0.0, 1.0]],
48 }
49 }
50
51 pub fn from_rect_to_quad(rect: Rect, quad: [[f32; 2]; 4]) -> Self {
53 if rect.width.abs() <= f32::EPSILON || rect.height.abs() <= f32::EPSILON {
54 return Self::translation(quad[0][0], quad[0][1]);
55 }
56
57 if let Some(axis_aligned) = axis_aligned_rect_from_quad(quad) {
58 let scale_x = axis_aligned.width / rect.width;
59 let scale_y = axis_aligned.height / rect.height;
60 return Self {
61 matrix: [
62 [scale_x, 0.0, axis_aligned.x - rect.x * scale_x],
63 [0.0, scale_y, axis_aligned.y - rect.y * scale_y],
64 [0.0, 0.0, 1.0],
65 ],
66 };
67 }
68
69 let source = [
70 [rect.x, rect.y],
71 [rect.x + rect.width, rect.y],
72 [rect.x, rect.y + rect.height],
73 [rect.x + rect.width, rect.y + rect.height],
74 ];
75 let Some(coefficients) = solve_homography(source, quad) else {
76 return Self::identity();
77 };
78
79 Self {
80 matrix: [
81 [coefficients[0], coefficients[1], coefficients[2]],
82 [coefficients[3], coefficients[4], coefficients[5]],
83 [coefficients[6], coefficients[7], 1.0],
84 ],
85 }
86 }
87
88 pub fn from_homogeneous(matrix: [[f32; 3]; 3]) -> Self {
92 let w = matrix[2][2];
93 if w == 1.0 || w.abs() <= f32::EPSILON {
94 return Self { matrix };
95 }
96 Self {
97 matrix: matrix.map(|row| row.map(|value| value / w)),
98 }
99 }
100
101 pub fn then(self, next: Self) -> Self {
103 Self {
104 matrix: multiply_matrices(next.matrix, self.matrix),
105 }
106 }
107
108 pub fn inverse(self) -> Option<Self> {
110 let m = self.matrix;
111 let a = m[0][0];
112 let b = m[0][1];
113 let c = m[0][2];
114 let d = m[1][0];
115 let e = m[1][1];
116 let f = m[1][2];
117 let g = m[2][0];
118 let h = m[2][1];
119 let i = m[2][2];
120
121 let cofactor00 = e * i - f * h;
122 let cofactor01 = -(d * i - f * g);
123 let cofactor02 = d * h - e * g;
124 let cofactor10 = -(b * i - c * h);
125 let cofactor11 = a * i - c * g;
126 let cofactor12 = -(a * h - b * g);
127 let cofactor20 = b * f - c * e;
128 let cofactor21 = -(a * f - c * d);
129 let cofactor22 = a * e - b * d;
130
131 let determinant = a * cofactor00 + b * cofactor01 + c * cofactor02;
132 if determinant.abs() <= f32::EPSILON {
133 return None;
134 }
135 let inverse_determinant = 1.0 / determinant;
136
137 Some(Self {
138 matrix: [
139 [
140 cofactor00 * inverse_determinant,
141 cofactor10 * inverse_determinant,
142 cofactor20 * inverse_determinant,
143 ],
144 [
145 cofactor01 * inverse_determinant,
146 cofactor11 * inverse_determinant,
147 cofactor21 * inverse_determinant,
148 ],
149 [
150 cofactor02 * inverse_determinant,
151 cofactor12 * inverse_determinant,
152 cofactor22 * inverse_determinant,
153 ],
154 ],
155 })
156 }
157
158 pub fn matrix(self) -> [[f32; 3]; 3] {
160 self.matrix
161 }
162
163 pub fn map_point(&self, point: Point) -> Point {
165 let x = point.x;
166 let y = point.y;
167 let w = self.matrix[2][0] * x + self.matrix[2][1] * y + self.matrix[2][2];
168 let mapped_x = self.matrix[0][0] * x + self.matrix[0][1] * y + self.matrix[0][2];
169 let mapped_y = self.matrix[1][0] * x + self.matrix[1][1] * y + self.matrix[1][2];
170 if w == 1.0 {
173 return Point {
174 x: mapped_x,
175 y: mapped_y,
176 };
177 }
178 let safe_w = if w.abs() <= f32::EPSILON { 1.0 } else { w };
179
180 Point {
181 x: mapped_x / safe_w,
182 y: mapped_y / safe_w,
183 }
184 }
185
186 pub fn map_rect(&self, rect: Rect) -> [[f32; 2]; 4] {
188 [
189 self.map_point(Point {
190 x: rect.x,
191 y: rect.y,
192 }),
193 self.map_point(Point {
194 x: rect.x + rect.width,
195 y: rect.y,
196 }),
197 self.map_point(Point {
198 x: rect.x,
199 y: rect.y + rect.height,
200 }),
201 self.map_point(Point {
202 x: rect.x + rect.width,
203 y: rect.y + rect.height,
204 }),
205 ]
206 .map(|point| [point.x, point.y])
207 }
208
209 pub fn bounds_for_rect(&self, rect: Rect) -> Rect {
211 quad_bounds(self.map_rect(rect))
212 }
213}
214
215fn axis_aligned_rect_from_quad(quad: [[f32; 2]; 4]) -> Option<Rect> {
216 let top_left = quad[0];
217 let top_right = quad[1];
218 let bottom_left = quad[2];
219 let bottom_right = quad[3];
220 let x_epsilon = 1e-4;
221 let y_epsilon = 1e-4;
222
223 if (top_left[1] - top_right[1]).abs() > y_epsilon
224 || (bottom_left[1] - bottom_right[1]).abs() > y_epsilon
225 || (top_left[0] - bottom_left[0]).abs() > x_epsilon
226 || (top_right[0] - bottom_right[0]).abs() > x_epsilon
227 {
228 return None;
229 }
230
231 Some(Rect {
232 x: top_left[0],
233 y: top_left[1],
234 width: top_right[0] - top_left[0],
235 height: bottom_left[1] - top_left[1],
236 })
237}
238
239impl Default for ProjectiveTransform {
240 fn default() -> Self {
241 Self::identity()
242 }
243}
244
245pub fn quad_bounds(quad: [[f32; 2]; 4]) -> Rect {
247 let [[x0, y0], [x1, y1], [x2, y2], [x3, y3]] = quad;
250 let min_x = f32::INFINITY.min(x0).min(x1).min(x2).min(x3);
251 let min_y = f32::INFINITY.min(y0).min(y1).min(y2).min(y3);
252 let max_x = f32::NEG_INFINITY.max(x0).max(x1).max(x2).max(x3);
253 let max_y = f32::NEG_INFINITY.max(y0).max(y1).max(y2).max(y3);
254
255 Rect {
256 x: min_x,
257 y: min_y,
258 width: (max_x - min_x).max(0.0),
259 height: (max_y - min_y).max(0.0),
260 }
261}
262
263fn multiply_matrices(lhs: [[f32; 3]; 3], rhs: [[f32; 3]; 3]) -> [[f32; 3]; 3] {
264 let mut out = [[0.0; 3]; 3];
265 for row in 0..3 {
266 for col in 0..3 {
267 out[row][col] =
268 lhs[row][0] * rhs[0][col] + lhs[row][1] * rhs[1][col] + lhs[row][2] * rhs[2][col];
269 }
270 }
271 out
272}
273
274fn solve_homography(source: [[f32; 2]; 4], target: [[f32; 2]; 4]) -> Option<[f32; 8]> {
275 let mut matrix = [[0.0f32; 9]; 8];
276 for (index, (src, dst)) in source.into_iter().zip(target).enumerate() {
277 let row = index * 2;
278 let x = src[0];
279 let y = src[1];
280 let u = dst[0];
281 let v = dst[1];
282
283 matrix[row] = [x, y, 1.0, 0.0, 0.0, 0.0, -u * x, -u * y, u];
284 matrix[row + 1] = [0.0, 0.0, 0.0, x, y, 1.0, -v * x, -v * y, v];
285 }
286
287 for pivot in 0..8 {
288 let mut pivot_row = pivot;
289 let mut pivot_value = matrix[pivot][pivot].abs();
290 let mut candidate = pivot + 1;
291 while candidate < 8 {
292 let candidate_value = matrix[candidate][pivot].abs();
293 if candidate_value > pivot_value {
294 pivot_row = candidate;
295 pivot_value = candidate_value;
296 }
297 candidate += 1;
298 }
299
300 if pivot_value <= f32::EPSILON {
301 return None;
302 }
303
304 if pivot_row != pivot {
305 matrix.swap(pivot, pivot_row);
306 }
307
308 let divisor = matrix[pivot][pivot];
309 let mut col = pivot;
310 while col < 9 {
311 matrix[pivot][col] /= divisor;
312 col += 1;
313 }
314
315 for row in 0..8 {
316 if row == pivot {
317 continue;
318 }
319 let factor = matrix[row][pivot];
320 if factor.abs() <= f32::EPSILON {
321 continue;
322 }
323 let mut col = pivot;
324 while col < 9 {
325 matrix[row][col] -= factor * matrix[pivot][col];
326 col += 1;
327 }
328 }
329 }
330
331 let mut solution = [0.0f32; 8];
332 for index in 0..8 {
333 solution[index] = matrix[index][8];
334 }
335 Some(solution)
336}