| 21 pub angle : Angle, |
21 pub angle : Angle, |
| 22 pub z : f64 |
22 pub z : f64 |
| 23 } |
23 } |
| 24 |
24 |
| 25 impl CylCoords { |
25 impl CylCoords { |
| |
26 /// Convert to cartesian coordinates. |
| 26 #[inline] |
27 #[inline] |
| 27 pub fn to_cartesian(&self) -> Loc<f64, 3> { |
28 pub fn to_cartesian(&self) -> Loc<f64, 3> { |
| 28 let &CylCoords{r, angle, z} = self; |
29 let &CylCoords{r, angle, z} = self; |
| 29 [r * angle.cos(), r * angle.sin(), z].into() |
30 [r * angle.cos(), r * angle.sin(), z].into() |
| 30 } |
31 } |
| 31 |
32 |
| |
33 /// Form cylindrical coordinates from cartesian coordinates. |
| 32 #[inline] |
34 #[inline] |
| 33 #[allow(dead_code)] |
35 #[allow(dead_code)] |
| 34 pub fn from_cartesian(coords : Loc<f64, 3>) -> Self { |
36 pub fn from_cartesian(coords : Loc<f64, 3>) -> Self { |
| 35 let [x, y, z] = coords.into(); |
37 let [x, y, z] = coords.into(); |
| 36 let r = (x*x + y*y).sqrt(); |
38 let r = (x*x + y*y).sqrt(); |
| 224 Top, |
228 Top, |
| 225 Bottom, |
229 Bottom, |
| 226 Side, |
230 Side, |
| 227 } |
231 } |
| 228 |
232 |
| |
233 /// Point on a specific [`Cylinder`] |
| 229 #[derive(Clone, Debug, PartialEq)] |
234 #[derive(Clone, Debug, PartialEq)] |
| 230 pub struct OnCylinder<'a> { |
235 pub struct OnCylinder<'a> { |
| |
236 /// Face and coordinates of the point |
| |
237 point : Point, |
| |
238 /// The specific cylinder the `point` lies on. |
| 231 cylinder : &'a Cylinder, |
239 cylinder : &'a Cylinder, |
| 232 point : Point, |
|
| 233 } |
240 } |
| 234 |
241 |
| 235 |
242 |
| 236 /// Cylinder configuration |
243 /// Cylinder configuration |
| 237 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] |
244 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] |
| 238 pub struct CylinderConfig { |
245 pub struct CylinderConfig { |
| |
246 /// Number of Newton iterates two take to solve geodesics. |
| 239 pub newton_iters : usize, |
247 pub newton_iters : usize, |
| 240 } |
248 } |
| 241 |
249 |
| 242 impl Default for CylinderConfig { |
250 impl Default for CylinderConfig { |
| 243 fn default() -> Self { |
251 fn default() -> Self { |
| 288 } |
297 } |
| 289 |
298 |
| 290 // Tangent vector |
299 // Tangent vector |
| 291 type Tangent = Loc<f64, 2>; |
300 type Tangent = Loc<f64, 2>; |
| 292 |
301 |
| |
302 /// Goes through list of potential tangents (corresponding to several geodesics taking |
| |
303 /// different paths), and retuns the shortest tangent vector and the corresponding length. |
| 293 #[inline] |
304 #[inline] |
| 294 fn best_tangent<I>(tangents : I) -> (Tangent, f64) |
305 fn best_tangent<I>(tangents : I) -> (Tangent, f64) |
| 295 where I : IntoIterator<Item = Tangent> { |
306 where I : IntoIterator<Item = Tangent> { |
| 296 tangents.into_iter() |
307 tangents.into_iter() |
| 297 .map(|t| (t, t.norm(L2))) |
308 .map(|t| (t, t.norm(L2))) |
| 318 Point::Bottom(cap) => { cap.cyl_coords(self.bottom_z()) }, |
330 Point::Bottom(cap) => { cap.cyl_coords(self.bottom_z()) }, |
| 319 Point::Side(side) => { side.cyl_coords(self.radius) }, |
331 Point::Side(side) => { side.cyl_coords(self.radius) }, |
| 320 } |
332 } |
| 321 } |
333 } |
| 322 |
334 |
| |
335 /// Returns the z coordinate of the top (cap) of the cylinder. |
| 323 #[inline] |
336 #[inline] |
| 324 pub fn top_z(&self) -> f64 { |
337 pub fn top_z(&self) -> f64 { |
| 325 self.height / 2.0 |
338 self.height / 2.0 |
| 326 } |
339 } |
| 327 |
340 |
| |
341 /// Returns the z coordinate of the bottom (cap) of the cylinder. |
| 328 #[inline] |
342 #[inline] |
| 329 pub fn bottom_z(&self) -> f64 { |
343 pub fn bottom_z(&self) -> f64 { |
| 330 -self.height / 2.0 |
344 -self.height / 2.0 |
| 331 } |
345 } |
| 332 |
346 |
| 333 /// Find angle where a geodesic from `side` to `(cap, z)` crosses the cap edge. |
347 /// Find angle where a geodesic from `side` to `(cap, z)` crosses the cap edge. |
| 334 /// |
348 /// |
| 335 /// Uses `newton_sym1x1`. |
349 /// Uses [`newton_sym1x1`]. |
| 336 fn side_cap_crossing( |
350 fn side_cap_crossing( |
| 337 &self, |
351 &self, |
| 338 side : &SidePoint, |
352 side : &SidePoint, |
| 339 cap : &CapPoint, z : f64, // `z` is the z coordinate of cap |
353 cap : &CapPoint, z : f64, // `z` is the z coordinate of cap |
| 340 ) -> Angle { |
354 ) -> Angle { |
| 413 |
427 |
| 414 /// Find angles where the geodesic passing through the side from `cap1` at height `z_1` |
428 /// Find angles where the geodesic passing through the side from `cap1` at height `z_1` |
| 415 /// to `cap2` at height `z_2` crosses the cap edges. |
429 /// to `cap2` at height `z_2` crosses the cap edges. |
| 416 /// **Panics if `cap2.angle < cap1.angle`.** |
430 /// **Panics if `cap2.angle < cap1.angle`.** |
| 417 /// |
431 /// |
| 418 /// Uses `newton_sym2x2`. |
432 /// Uses [`newton_sym2x2`]. |
| 419 fn cap_side_cap_crossing( |
433 fn cap_side_cap_crossing( |
| 420 &self, |
434 &self, |
| 421 cap1 : &CapPoint, z_1 : f64, |
435 cap1 : &CapPoint, z_1 : f64, |
| 422 cap2 : &CapPoint, z_2 : f64, |
436 cap2 : &CapPoint, z_2 : f64, |
| 423 init_by_cap2 : bool |
437 init_by_cap2 : bool |
| 478 }; |
492 }; |
| 479 let [α_1, α_2] = newton_sym2x2(f, α_init, self.config.newton_iters); |
493 let [α_1, α_2] = newton_sym2x2(f, α_init, self.config.newton_iters); |
| 480 (normalise_angle(φ_1 + α_1), normalise_angle(φ_2 - α_2)) |
494 (normalise_angle(φ_1 + α_1), normalise_angle(φ_2 - α_2)) |
| 481 } |
495 } |
| 482 |
496 |
| |
497 /// Calculates the log between points on a `cap` and a `side` of the cylinder, in this direction. |
| |
498 /// The `z` coordinate of the cap and an indication whether it is a `top` or bottom cap must |
| |
499 /// also be given. |
| 483 fn cap_side_log( |
500 fn cap_side_log( |
| 484 &self, |
501 &self, |
| 485 cap : &CapPoint, (z, top) : (f64, bool), |
502 cap : &CapPoint, (z, top) : (f64, bool), |
| 486 side : &SidePoint |
503 side : &SidePoint |
| 487 ) -> Tangent { |
504 ) -> Tangent { |
| 594 (t1/n)*(n + t2.norm(L2) + t3.norm(L2)) |
617 (t1/n)*(n + t2.norm(L2) + t3.norm(L2)) |
| 595 } |
618 } |
| 596 } |
619 } |
| 597 } |
620 } |
| 598 |
621 |
| |
622 /// Calculates the log between points on two caps of the cylinder, assuming the corresonding |
| |
623 /// geodesic crosses the side. The heights of the caps and indications whether they are |
| |
624 /// top caps, must also be given. |
| 599 fn cap_side_cap_log( |
625 fn cap_side_cap_log( |
| 600 &self, |
626 &self, |
| 601 cap1 : &CapPoint, (z1, top1) : (f64, bool), |
627 cap1 : &CapPoint, (z1, top1) : (f64, bool), |
| 602 cap2 : &CapPoint, (z2, top2) : (f64, bool), |
628 cap2 : &CapPoint, (z2, top2) : (f64, bool), |
| 603 init_by_cap2 : bool, |
629 init_by_cap2 : bool, |
| 642 (t1/n)*(n + t2.norm(L2) + t3.norm(L2)) |
668 (t1/n)*(n + t2.norm(L2) + t3.norm(L2)) |
| 643 } |
669 } |
| 644 } |
670 } |
| 645 } |
671 } |
| 646 |
672 |
| 647 /// Calculates both the logarithmic map and distance to another point |
673 /// Calculates both the logarithmic map and distance between two points. |
| 648 fn log_dist(&self, source : &Point, destination : &Point) -> (Tangent, f64) { |
674 fn log_dist(&self, source : &Point, destination : &Point) -> (Tangent, f64) { |
| 649 use Point::*; |
675 use Point::*; |
| 650 match (source, destination) { |
676 match (source, destination) { |
| 651 (Top(cap1), Top(cap2)) => { |
677 (Top(cap1), Top(cap2)) => { |
| 652 best_tangent([cap1.log(cap2)]) |
678 best_tangent([cap1.log(cap2)]) |
| 704 ]) |
730 ]) |
| 705 }, |
731 }, |
| 706 } |
732 } |
| 707 } |
733 } |
| 708 |
734 |
| |
735 /// Calculates the exponential map of `point` in the direction `t` until the next edge. |
| |
736 /// If `t` was fully exhausted before reaching an edge, the second return value is [`None`], |
| |
737 /// otherwise it is a [`Some`] of the remaining tangent, translated at the returned point. |
| 709 #[allow(unreachable_code)] |
738 #[allow(unreachable_code)] |
| 710 #[allow(unused_variables)] |
739 #[allow(unused_variables)] |
| 711 fn partial_exp(&self, point : Point, t : Tangent) -> (Point, Option<Tangent>) { |
740 fn partial_exp(&self, point : Point, t : Tangent) -> (Point, Option<Tangent>) { |
| 712 match point { |
741 match point { |
| 713 Point::Top(cap) => { |
742 Point::Top(cap) => { |
| 929 radius : 1.0, |
959 radius : 1.0, |
| 930 height : 1.0, |
960 height : 1.0, |
| 931 config : CylinderConfig { newton_iters : 20 }, |
961 config : CylinderConfig { newton_iters : 20 }, |
| 932 }; |
962 }; |
| 933 |
963 |
| |
964 /// Tests for correct distance calculation beween two points on the same cap. |
| 934 #[test] |
965 #[test] |
| 935 fn intra_cap_log_dist() { |
966 fn intra_cap_dist() { |
| 936 let π = f64::PI; |
967 let π = f64::PI; |
| 937 let p1 = CYL.on_top(0.0, 0.5); |
968 let p1 = CYL.on_top(0.0, 0.5); |
| 938 let p2 = CYL.on_top(π, 0.5); |
969 let p2 = CYL.on_top(π, 0.5); |
| 939 let p3 = CYL.on_top(π/2.0, 0.5); |
970 let p3 = CYL.on_top(π/2.0, 0.5); |
| 940 |
971 |
| 941 check_distance!(p1.dist_to(&p2), 1.0); |
972 check_distance!(p1.dist_to(&p2), 1.0); |
| 942 check_distance!(p2.dist_to(&p3), 0.5_f64.sqrt()); |
973 check_distance!(p2.dist_to(&p3), 0.5_f64.sqrt()); |
| 943 check_distance!(p3.dist_to(&p1), 0.5_f64.sqrt()); |
974 check_distance!(p3.dist_to(&p1), 0.5_f64.sqrt()); |
| 944 } |
975 } |
| 945 |
976 |
| |
977 /// Tests for correct distance calculation beween two points on the side. |
| 946 #[test] |
978 #[test] |
| 947 fn intra_side_log_dist() { |
979 fn intra_side_dist() { |
| 948 let π = f64::PI; |
980 let π = f64::PI; |
| 949 let p1 = CYL.on_side(0.0, 0.0); |
981 let p1 = CYL.on_side(0.0, 0.0); |
| 950 let p2 = CYL.on_side(0.0, 0.4); |
982 let p2 = CYL.on_side(0.0, 0.4); |
| 951 let p3 = CYL.on_side(π/2.0, 0.0); |
983 let p3 = CYL.on_side(π/2.0, 0.0); |
| 952 |
984 |
| 953 check_distance!(p1.dist_to(&p2), 0.4); |
985 check_distance!(p1.dist_to(&p2), 0.4); |
| 954 check_distance!(p1.dist_to(&p3), π/2.0*CYL.radius); |
986 check_distance!(p1.dist_to(&p3), π/2.0*CYL.radius); |
| 955 } |
987 } |
| 956 |
988 |
| |
989 /// Tests for correct distance calculation beween two points on the side, when the corresponding |
| |
990 /// minimal geodesic has to cross a cap. |
| 957 #[test] |
991 #[test] |
| 958 fn intra_side_over_cap_log_dist() { |
992 fn intra_side_over_cap_dist() { |
| 959 let π = f64::PI; |
993 let π = f64::PI; |
| 960 let off = 0.05; |
994 let off = 0.05; |
| 961 let z = CYL.top_z() - off; |
995 let z = CYL.top_z() - off; |
| 962 let p1 = CYL.on_side(0.0, z); |
996 let p1 = CYL.on_side(0.0, z); |
| 963 let p2 = CYL.on_side(π, z); |
997 let p2 = CYL.on_side(π, z); |
| 964 |
998 |
| 965 check_distance!(p1.dist_to(&p2), 2.0 * (CYL.radius + off)); |
999 check_distance!(p1.dist_to(&p2), 2.0 * (CYL.radius + off)); |
| 966 } |
1000 } |
| 967 |
1001 |
| |
1002 /// Tests for correct distance calculation between points on top and bottom caps. |
| 968 #[test] |
1003 #[test] |
| 969 fn top_bottom_log_dist() { |
1004 fn top_bottom_dist() { |
| 970 let π = f64::PI; |
1005 let π = f64::PI; |
| 971 let p1 = CYL.on_top(0.0, 0.0); |
1006 let p1 = CYL.on_top(0.0, 0.0); |
| 972 let p2 = CYL.on_bottom(0.0, 0.0); |
1007 let p2 = CYL.on_bottom(0.0, 0.0); |
| 973 |
1008 |
| 974 check_distance!(p1.dist_to(&p2), 2.0 * CYL.radius + CYL.height); |
1009 check_distance!(p1.dist_to(&p2), 2.0 * CYL.radius + CYL.height); |
| 979 |
1014 |
| 980 check_distance!(p1.dist_to(&p2), CYL.radius + CYL.height); |
1015 check_distance!(p1.dist_to(&p2), CYL.radius + CYL.height); |
| 981 check_distance!(p1.dist_to(&p3), 2.0 * CYL.radius + CYL.height); |
1016 check_distance!(p1.dist_to(&p3), 2.0 * CYL.radius + CYL.height); |
| 982 } |
1017 } |
| 983 |
1018 |
| |
1019 /// Test for correct distance calculation between points on the side and a cap. |
| 984 #[test] |
1020 #[test] |
| 985 fn top_side_log_dist() { |
1021 fn top_side_dist() { |
| 986 let p1 = CYL.on_top(0.0, 0.0); |
1022 let p1 = CYL.on_top(0.0, 0.0); |
| 987 let p2 = CYL.on_side(0.0, 0.0); |
1023 let p2 = CYL.on_side(0.0, 0.0); |
| 988 |
1024 |
| 989 check_distance!(p1.dist_to(&p2), CYL.radius + CYL.height / 2.0); |
1025 check_distance!(p1.dist_to(&p2), CYL.radius + CYL.height / 2.0); |
| 990 } |
1026 } |
| 991 |
1027 |
| |
1028 /// Tests for correct tangent calculation from a cap to the side. |
| 992 #[test] |
1029 #[test] |
| 993 fn cap_side_partial_exp() { |
1030 fn cap_side_partial_exp() { |
| 994 let π = f64::PI; |
1031 let π = f64::PI; |
| 995 let angles = [0.0, π/2.0, π*5.0/6.0, π*7.0/5.0]; |
1032 let angles = [0.0, π/2.0, π*5.0/6.0, π*7.0/5.0]; |
| 996 |
1033 |