doc_quad/geom/transform.rs
1// src/geom/transform.rs
2use glam::{Vec2, Mat3};
3
4/// 顶点排序与坐标变换
5pub struct Transformer;
6
7impl Transformer {
8 /// 将四个点排序为 [左上, 右上, 右下, 左下] 顺序。
9 ///
10 /// # 算法说明
11 /// 1. 计算四点质心。
12 /// 2. 基于各点相对质心的极角排序(图像坐标系 y 向下,atan2 产生顺时针顺序)。
13 /// 3. 旋转数组,使 x+y 最小的点(左上角)位于索引 0。
14 ///
15 /// # 坐标系说明
16 /// 输入为图像坐标系(y 轴向下)。atan2 在此坐标系下产生顺时针角度增长,
17 /// 排序结果为顺时针顶点序列,符合 [左上, 右上, 右下, 左下] 的预期语义。
18 pub fn sort_points(mut pts: [Vec2; 4]) -> [Vec2; 4] {
19 // P2 修复:移除不必要的 Instant 计时(sort_points 不在强制统计点列表中)
20
21 // 计算质心用于极角排序
22 let center = (pts[0] + pts[1] + pts[2] + pts[3]) * 0.25;
23
24 // P2 修复:将 unwrap() 替换为 unwrap_or,防止坐标含 NaN/inf 时 panic
25 pts.sort_by(|a, b| {
26 let angle_a = (a.y - center.y).atan2(a.x - center.x);
27 let angle_b = (b.y - center.y).atan2(b.x - center.x);
28 angle_a
29 .partial_cmp(&angle_b)
30 .unwrap_or(std::cmp::Ordering::Equal)
31 });
32
33 // 找到 x+y 最小的点(图像坐标系中最接近左上角的点)
34 // P2 修复:将 unwrap() 替换为 unwrap_or,防止坐标含 NaN 时 panic
35 let tl_idx = pts
36 .iter()
37 .enumerate()
38 .min_by(|(_, a), (_, b)| {
39 (a.x + a.y)
40 .partial_cmp(&(b.x + b.y))
41 .unwrap_or(std::cmp::Ordering::Equal)
42 })
43 .map(|(i, _)| i)
44 .unwrap_or(0);
45
46 // 旋转数组使左上角点位于索引 0
47 pts.rotate_left(tl_idx);
48
49 log::debug!(
50 "[Geom::Transform] - Points sorted: {:?}",
51 pts.map(|p| (p.x, p.y))
52 );
53
54 pts
55 }
56
57 /// 计算单应性矩阵(Homography),将透视四边形映射到标准矩形。
58 ///
59 /// # 算法说明
60 /// 标准实现需解 8×8 线性方程组 Ah=b(DLT 算法),利用 SVD 求最小二乘解。
61 /// 当前为占位实现,返回单位矩阵。
62 ///
63 /// # 参数
64 /// - `src`:源四边形顶点,顺序为 [左上, 右上, 右下, 左下]
65 /// - `dst_w`、`dst_h`:目标矩形宽高
66 ///
67 // TODO(feat): 实现 DLT 算法求解单应性矩阵,需引入 SVD 分解。
68 // 可考虑使用 nalgebra crate 或手动实现 4 点 DLT。
69 pub fn get_homography(_src: [Vec2; 4], _dst_w: f32, _dst_h: f32) -> Mat3 {
70 Mat3::IDENTITY
71 }
72}