1mod caliper;
2
3use crate::caliper::Caliper;
4use geo::{prelude::*, Coord, Line};
5use itertools::{izip, Itertools};
6use nalgebra as na;
7use noisy_float::prelude::*;
8use std::f64::consts::{FRAC_PI_2, PI};
9
10const EPSILON: f64 = 1e-6;
11
12#[derive(Debug, Clone)]
13pub struct BBox3D {
14 pub center: [f64; 3],
16
17 pub extent: [f64; 3],
20
21 pub rotation: [f64; 4],
23}
24
25pub fn get_rotated_bbox3d(points: impl IntoIterator<Item = [f64; 3]>) -> Option<BBox3D> {
27 let (points3d, points2d): (Vec<na::Point3<f64>>, Vec<geo::Point>) = points
28 .into_iter()
29 .map(|[x, y, z]| {
30 let point3d = na::Point3::new(x, y, z);
31 let point2d = geo::Point::new(point3d.x, point3d.y);
32 (point3d, point2d)
33 })
34 .unzip();
35
36 let convex_hull: Vec<geo::Point<f64>> = {
37 let convex_hull = geo::MultiPoint::from(points2d.clone()).convex_hull();
38 let exterior = convex_hull.exterior();
39 let num_points = exterior.0.len();
40 exterior.points().take(num_points - 1).collect()
41 };
42
43 let ((bottom, _), (top, _)) = convex_hull
44 .iter()
45 .map(|point| point.y())
46 .map(r64)
47 .enumerate()
48 .minmax_by_key(|&(_, y)| y)
49 .into_option()?;
50 let ((left, _), (right, _)) = convex_hull
51 .iter()
52 .map(|point| point.x())
53 .map(r64)
54 .enumerate()
55 .minmax_by_key(|&(_, x)| x)
56 .into_option()?;
57
58 let mut top_caliper = Caliper {
59 convex_hull: convex_hull.clone(),
60 index: top,
61 angle: PI,
62 };
63 let mut bottom_caliper = Caliper {
64 convex_hull: convex_hull.clone(),
65 index: bottom,
66 angle: 0.0,
67 };
68 let mut right_caliper = Caliper {
69 convex_hull: convex_hull.clone(),
70 index: right,
71 angle: FRAC_PI_2,
72 };
73 let mut left_caliper = Caliper {
74 convex_hull,
75 index: left,
76 angle: PI + FRAC_PI_2,
77 };
78
79 let mut bboxes: Vec<[(f64, f64); 4]> = vec![];
80
81 while bottom_caliper.angle < FRAC_PI_2 {
82 let vertices = [
83 bottom_caliper.intersection(&right_caliper),
84 top_caliper.intersection(&right_caliper),
85 top_caliper.intersection(&left_caliper),
86 bottom_caliper.intersection(&left_caliper),
87 ];
88 bboxes.push(vertices);
89
90 let angle = top_caliper.rotate_to_next();
91 let angle = if bottom_caliper.rotate_to_next() < angle {
92 bottom_caliper.rotate_to_next()
93 } else {
94 angle
95 };
96 let angle = if right_caliper.rotate_to_next() < angle {
97 right_caliper.rotate_to_next()
98 } else {
99 angle
100 };
101 let angle = if left_caliper.rotate_to_next() < angle {
102 left_caliper.rotate_to_next()
103 } else {
104 angle
105 };
106
107 top_caliper.rotate(angle);
108 bottom_caliper.rotate(angle);
109 right_caliper.rotate(angle);
110 left_caliper.rotate(angle);
111 }
112
113 let haus_error: Vec<f64> = {
114 let loss: Vec<f64> = bboxes
115 .iter()
116 .map(|bbox| hausdorff_distance(bbox, &points2d))
117 .collect();
118
119 let (min_loss, max_loss) = loss.iter().copied().minmax().into_option()?;
120 let loss_range = max_loss - min_loss;
121 if loss_range <= EPSILON {
122 return None;
123 }
124 loss.iter().map(|x| (x - min_loss) / loss_range).collect()
125 };
126 let area_error: Vec<f64> = {
127 let loss: Vec<f64> = bboxes.iter().map(bbox_area).collect();
128 let (min_loss, max_loss) = loss.iter().copied().minmax().into_option()?;
129 let loss_range = max_loss - min_loss;
130 if loss_range <= EPSILON {
131 return None;
132 }
133 loss.iter().map(|x| (x - min_loss) / loss_range).collect()
134 };
135 let loss: Vec<f64> = haus_error
136 .iter()
137 .zip(area_error)
138 .map(|(a, b)| a * 0.7 + b * 0.3)
139 .collect();
140
141 let (minimal_bbox, _) = izip!(&bboxes, &loss).min_by_key(|(_, &loss)| r64(loss))?;
142
143 let rotation = {
144 let from = na::Point2::new(minimal_bbox[0].0, minimal_bbox[0].1);
145 let to = na::Point2::new(minimal_bbox[1].0, minimal_bbox[1].1);
146 let vector = to - from;
147 let yaw = vector.y.atan2(vector.x);
148 na::UnitQuaternion::from_euler_angles(0.0, 0.0, yaw)
149 };
150
151 let z_minmax = points3d.iter().map(|p| p.z).map(r64).minmax();
152 let (z_min, z_max) = z_minmax.into_option()?;
153 let z_range = (z_max - z_min).raw();
154 if z_range <= EPSILON {
155 return None;
156 }
157
158 let center_x =
159 (minimal_bbox[0].0 + minimal_bbox[1].0 + minimal_bbox[2].0 + minimal_bbox[3].0) / 4.0;
160 let center_y =
161 (minimal_bbox[0].1 + minimal_bbox[1].1 + minimal_bbox[2].1 + minimal_bbox[3].1) / 4.0;
162 let center_z = z_min.raw() + z_range / 2.0;
163 let size_x = ((minimal_bbox[1].0 - minimal_bbox[0].0).powf(2.0)
164 + (minimal_bbox[1].1 - minimal_bbox[0].1).powf(2.0))
165 .sqrt();
166 let size_y = ((minimal_bbox[0].0 - minimal_bbox[3].0).powf(2.0)
167 + (minimal_bbox[0].1 - minimal_bbox[3].1).powf(2.0))
168 .sqrt();
169 let size_z = z_range;
170
171 Some(BBox3D {
172 center: [center_x, center_y, center_z],
173 extent: [size_x, size_y, size_z],
174 rotation: rotation.coords.into(),
175 })
176}
177
178fn bbox_area(vertex: &[(f64, f64); 4]) -> f64 {
179 let w = distance(vertex[0], vertex[3]);
180 let h = distance(vertex[0], vertex[1]);
181 w * h
182}
183
184fn distance(lhs: (f64, f64), rhs: (f64, f64)) -> f64 {
185 ((lhs.0 - rhs.0).powi(2) + (lhs.1 - rhs.1).powi(2)).sqrt()
186}
187
188fn point_to_bbox(vertex: &[(f64, f64); 4], point: &geo::Point<f64>) -> f64 {
189 let line: Vec<_> = vertex
190 .iter()
191 .map(|&(x, y)| Coord { x, y })
192 .circular_tuple_windows()
193 .map(|(lhs, rhs)| Line::new(lhs, rhs))
194 .collect();
195 line.iter()
196 .map(|line| point.euclidean_distance(line))
197 .map(r64)
198 .min()
199 .unwrap()
200 .raw()
201}
202
203fn hausdorff_distance(vertex: &[(f64, f64); 4], covex_hull: &[geo::Point<f64>]) -> f64 {
205 covex_hull
206 .iter()
207 .map(|p| point_to_bbox(vertex, p))
208 .map(r64)
209 .sum::<R64>()
210 .raw()
211}