truck_modeling/
geom_impls.rs

1use crate::*;
2use std::f64::consts::PI;
3
4pub(super) fn circle_arc_by_three_points(
5    point0: Point3,
6    point1: Point3,
7    transit: Point3,
8) -> NurbsCurve<Vector4> {
9    let origin = circum_center(point0, point1, transit);
10    let (vec0, vec1) = (point0 - transit, point1 - transit);
11    let axis = vec1.cross(vec0).normalize();
12    let angle = Rad(PI) - vec0.angle(vec1);
13    circle_arc(point0, origin, axis, angle * 2.0)
14}
15
16fn circum_center(pt0: Point3, pt1: Point3, pt2: Point3) -> Point3 {
17    let (vec0, vec1) = (pt1 - pt0, pt2 - pt0);
18    let (a2, ab, b2) = (vec0.dot(vec0), vec0.dot(vec1), vec1.dot(vec1));
19    let (det, u, v) = (a2 * b2 - ab * ab, a2 * b2 - ab * b2, a2 * b2 - ab * a2);
20    pt0 + u / (2.0 * det) * vec0 + v / (2.0 * det) * vec1
21}
22
23fn unit_circle_arc(angle: Rad<f64>) -> NurbsCurve<Vector4> {
24    let (cos2, sin2) = (Rad::cos(angle / 2.0), Rad::sin(angle / 2.0));
25    let mut curve = NurbsCurve::new(BSplineCurve::new(
26        KnotVec::bezier_knot(2),
27        vec![
28            Vector4::new(1.0, 0.0, 0.0, 1.0),
29            Vector4::new(cos2, sin2, 0.0, cos2),
30            Vector4::new(Rad::cos(angle), Rad::sin(angle), 0.0, 1.0),
31        ],
32    ));
33    curve.add_knot(0.25);
34    curve.add_knot(0.5);
35    curve.add_knot(0.75);
36    curve
37}
38
39pub(super) fn circle_arc(
40    point: Point3,
41    origin: Point3,
42    axis: Vector3,
43    angle: Rad<f64>,
44) -> NurbsCurve<Vector4> {
45    let origin = origin + (axis.dot(point - origin)) * axis;
46    let diag = point - origin;
47    let axis_trsf = Matrix4::from_cols(
48        diag.extend(0.0),
49        axis.cross(diag).extend(0.0),
50        axis.extend(0.0),
51        origin.to_homogeneous(),
52    );
53    let mut unit_curve = unit_circle_arc(angle);
54    unit_curve.transform_by(axis_trsf);
55    unit_curve
56}
57
58fn closed_polyline_orientation<'a>(pts: impl IntoIterator<Item = &'a Vec<Point3>>) -> bool {
59    pts.into_iter()
60        .flat_map(|vec| vec.windows(2))
61        .map(|p| (p[1][0] + p[0][0]) * (p[1][1] - p[0][1]))
62        .sum::<f64>()
63        >= 0.0
64}
65
66fn take_one_axis_by_normal(n: Vector3) -> Vector3 {
67    let a = n.map(f64::abs);
68    if a.x > a.z || a.y > a.z {
69        Vector3::new(-n.y, n.x, 0.0).normalize()
70    } else {
71        Vector3::new(-n.z, 0.0, n.x).normalize()
72    }
73}
74
75pub(super) fn attach_plane(mut pts: Vec<Vec<Point3>>) -> Option<Plane> {
76    let center = pts
77        .iter()
78        .flatten()
79        .fold(Point3::origin(), |sum, pt| sum + pt.to_vec())
80        / pts.len() as f64;
81    let normal = pts
82        .iter()
83        .flat_map(|vec| vec.windows(2))
84        .fold(Vector3::zero(), |sum, p| {
85            sum + (p[0] - center).cross(p[1] - center)
86        });
87    let n = match normal.so_small() {
88        true => return None,
89        false => normal.normalize(),
90    };
91    let a = take_one_axis_by_normal(n);
92    let mat: Matrix4 = Matrix3::from_cols(a, n.cross(a), n).into();
93    pts.iter_mut()
94        .flatten()
95        .for_each(|pt| *pt = mat.invert().unwrap().transform_point(*pt));
96    let bnd_box: BoundingBox<Point3> = pts.iter().flatten().collect();
97    let diag = bnd_box.diagonal();
98    if !diag[2].so_small() {
99        return None;
100    }
101    let (max, min) = match closed_polyline_orientation(&pts) {
102        true => (bnd_box.max(), bnd_box.min()),
103        false => (bnd_box.min(), bnd_box.max()),
104    };
105    let plane = Plane::new(
106        Point3::new(min[0], min[1], min[2]),
107        Point3::new(max[0], min[1], min[2]),
108        Point3::new(min[0], max[1], min[2]),
109    )
110    .transformed(mat);
111    Some(plane)
112}
113
114#[cfg(test)]
115mod test_geom_impl {
116    use super::*;
117    use proptest::*;
118
119    fn pole_to_normal(pole: [f64; 2]) -> Vector3 {
120        let theta = PI * pole[0];
121        let z = pole[1];
122        let zi = f64::sqrt(f64::max(1.0 - z * z, 0.0));
123        Vector3::new(f64::cos(theta) * zi, f64::sin(theta) * zi, z)
124    }
125
126    fn complex_boundary(angles: [f64; 10]) -> Vec<Point3> {
127        let mut angle_store = 0.0;
128        angles
129            .into_iter()
130            .enumerate()
131            .flat_map(move |(i, angle)| {
132                let prev_angle = angle_store;
133                angle_store = angle;
134                let r = 10.0 - i as f64;
135                let min_theta = f64::acos(1.0 - 0.01 / r);
136                let divs = 1 + (f64::abs(angle - prev_angle) / min_theta) as usize;
137                (0..=divs).map(move |i| {
138                    let t = i as f64 / divs as f64;
139                    let theta = (1.0 - t) * prev_angle + t * angle;
140                    Point3::new(r * f64::cos(theta), r * f64::sin(theta), 0.0)
141                })
142            })
143            .chain([Point3::origin(), Point3::new(10.0, 0.0, 0.0)])
144            .collect()
145    }
146
147    fn dist_square(p: Point3, a: f64, b: f64) -> f64 {
148        let absp = p.map(f64::abs);
149        f64::min(a - absp.x, b - absp.y)
150    }
151
152    fn multiple_boundary(points: [Point3; 4], radius_ratios: [f64; 4]) -> Vec<Vec<Point3>> {
153        let mut res = vec![vec![
154            Point3::new(10.0, 10.0, 0.0),
155            Point3::new(-10.0, 10.0, 0.0),
156            Point3::new(-10.0, -10.0, 0.0),
157            Point3::new(10.0, -10.0, 0.0),
158            Point3::new(10.0, 10.0, 0.0),
159        ]];
160        let mut radii = Vec::<f64>::new();
161        res.extend((0..4).map(|i| {
162            let mut dist = dist_square(points[i], 10.0, 10.0);
163            (0..i).for_each(|j| {
164                dist = f64::min(dist, points[i].distance(points[j]) - radii[j]);
165            });
166            (i + 1..4).for_each(|j| {
167                dist = f64::min(dist, points[i].distance(points[j]));
168            });
169            radii.push(radius_ratios[i] * dist);
170            (0..=10)
171                .map(|j| {
172                    let theta = j as f64 / 10.0 * 2.0 * PI;
173                    Point3::new(f64::sin(theta), f64::cos(theta), 0.0)
174                })
175                .collect()
176        }));
177        res
178    }
179
180    proptest! {
181        #[test]
182        fn test_circum_center(
183            p0 in array::uniform3(-10.0f64..10.0),
184            p1 in array::uniform3(-10.0f64..10.0),
185            p2 in array::uniform3(-10.0f64..10.0),
186        ) {
187            let p0 = Point3::from(p0);
188            let p1 = Point3::from(p1);
189            let p2 = Point3::from(p2);
190            let c = circum_center(p0, p1, p2);
191
192            // The point `c` exists at the same distance from the three points.
193            let d0 = c.distance2(p0);
194            let d1 = c.distance2(p1);
195            let d2 = c.distance2(p2);
196            assert!(d0.near(&d1) && d1.near(&d2) && d2.near(&d0));
197        }
198
199        #[test]
200        fn test_circle_arc_three_point(
201            p0 in array::uniform3(-10.0f64..10.0),
202            p1 in array::uniform3(-10.0f64..10.0),
203            p2 in array::uniform3(-10.0f64..10.0),
204            t in TOLERANCE..(1.0 - TOLERANCE),
205        ) {
206            let p0 = Point3::from(p0);
207            let p1 = Point3::from(p1);
208            let p2 = Point3::from(p2);
209            let curve = circle_arc_by_three_points(p0, p1, p2);
210
211            // The curve `curve` is from `p0` to `p1`.
212            assert_near!(curve.front(), p0);
213            assert_near!(curve.back(), p1);
214
215            // Any point on the curve is on the same side as point `p2`.
216            // Check by the circular angle theorem.
217            let p3 = curve.subs(t);
218            let angle2 = (p2 - p1).angle(p2 - p0);
219            let angle3 = (p3 - p1).angle(p3 - p0);
220            assert_near!(angle2, angle3);
221        }
222
223        #[test]
224        fn test_circle_arc(
225            origin in array::uniform3(-10.0f64..10.0),
226            axis_pole in array::uniform2(-1.0f64..1.0),
227            angle in 0.0f64..(1.5 * PI),
228            pt0 in array::uniform3(-10.0f64..10.0),
229            t in TOLERANCE..(1.0 - TOLERANCE),
230        ) {
231            let origin = Point3::from(origin);
232            let axis = pole_to_normal(axis_pole);
233            let angle = Rad(angle);
234            let pt0 = Point3::from(pt0);
235            let curve = circle_arc(pt0, origin, axis, angle);
236
237            // front point and back point
238            let trans = Matrix4::from_translation(origin.to_vec())
239                * Matrix4::from_axis_angle(axis, angle)
240                * Matrix4::from_translation(-origin.to_vec());
241            let pt1 = trans.transform_point(pt0);
242            assert_near!(curve.front(), pt0);
243            assert_near!(curve.back(), pt1);
244
245            // Any point on the curve lies in the same plane perpendicular to the axis.
246            let pt2 = curve.subs(t);
247            let vec0 = pt0 - origin;
248            let vec2 = pt2 - origin;
249            assert_near!(vec0.dot(axis), vec2.dot(axis));
250
251            // Any point on the curve lies in the circle arc from `p0` to `p1`.
252            // Check by the circular angle theorem.
253            let angle0 = (pt2 - pt1).angle(pt2 - pt0);
254            assert_near!(angle0 * 2.0, Rad(2.0 * PI) - angle);
255        }
256
257        #[test]
258        fn test_take_one_axis_by_normal(normal in array::uniform3(-100.0f64..100.0)) {
259            let normal = Vector3::from(normal);
260            let axis = take_one_axis_by_normal(normal);
261            assert!(normal.so_small() || (!axis.so_small() && axis.dot(normal).so_small()));
262        }
263
264        #[test]
265        fn test_attach_plane_with_single_boundary(
266            axis_pole in array::uniform2(-1.0f64..1.0),
267            origin in array::uniform3(-10.0f64..10.0),
268            angles in array::uniform10(0.01f64..(2.0 * PI - 0.01)),
269        ) {
270            let axis = pole_to_normal(axis_pole);
271            let origin = Point3::from(origin);
272            let diag = take_one_axis_by_normal(axis);
273            let trsf = Matrix4::from_cols(
274                diag.extend(0.0),
275                axis.cross(diag).extend(0.0),
276                axis.extend(0.0),
277                origin.to_homogeneous(),
278            );
279            let boundary: Vec<_> = complex_boundary(angles)
280                .into_iter()
281                .map(|p| trsf.transform_point(p))
282                .collect();
283            let plane = attach_plane(vec![boundary]).unwrap();
284            assert_near!(plane.normal(), axis);
285        }
286
287        #[test]
288        fn test_attach_plane_with_multiple_boundary(
289            axis_pole in array::uniform2(-1.0f64..1.0),
290            origin in array::uniform3(-10.0f64..10.0),
291            points in array::uniform8(1.0f64..9.0),
292            radius_ratios in array::uniform4(0.1f64..0.9),
293        ) {
294            let axis = pole_to_normal(axis_pole);
295            let origin = Point3::from(origin);
296            let diag = take_one_axis_by_normal(axis);
297            let trsf = Matrix4::from_cols(
298                diag.extend(0.0),
299                axis.cross(diag).extend(0.0),
300                axis.extend(0.0),
301                origin.to_homogeneous(),
302            );
303            let points = [
304                Point3::new(points[0], points[1], 0.0),
305                Point3::new(points[2] - 10.0, points[3], 0.0),
306                Point3::new(points[4] - 10.0, points[5] - 10.0, 0.0),
307                Point3::new(points[6], points[7] - 10.0, 0.0),
308            ];
309            let mut multiple_boundary = multiple_boundary(points, radius_ratios);
310            multiple_boundary
311                .iter_mut()
312                .flatten()
313                .for_each(|p| *p = trsf.transform_point(*p));
314            let plane = attach_plane(multiple_boundary).unwrap();
315            assert_near!(plane.normal(), axis);
316        }
317    }
318}