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 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 assert_near!(curve.front(), p0);
213 assert_near!(curve.back(), p1);
214
215 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 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 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 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}