src/cylinder.rs

changeset 46
90cc221eb52b
parent 41
56d609b70a3b
child 56
34f8ec636368
equal deleted inserted replaced
45:cac6978dc7dd 46:90cc221eb52b
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();
129 pub struct SidePoint { 131 pub struct SidePoint {
130 pub z : f64, 132 pub z : f64,
131 pub angle : Angle 133 pub angle : Angle
132 } 134 }
133 135
136 /// Calculates the difference between two angles, normalised to [–π, π].
134 #[inline] 137 #[inline]
135 fn anglediff(mut φ1 : f64, mut φ2 : f64) -> f64 { 138 fn anglediff(mut φ1 : f64, mut φ2 : f64) -> f64 {
136 let π = f64::PI; 139 let π = f64::PI;
137 φ1 = normalise_angle(φ1); 140 φ1 = normalise_angle(φ1);
138 φ2 = normalise_angle(φ2); 141 φ2 = normalise_angle(φ2);
150 2.0 * π + α 153 2.0 * π + α
151 } 154 }
152 } 155 }
153 } 156 }
154 157
158 /// Normalises an angle to [0, 2π].
155 #[inline] 159 #[inline]
156 pub fn normalise_angle(φ : f64) -> f64 { 160 pub fn normalise_angle(φ : f64) -> f64 {
157 let π = f64::PI; 161 let π = f64::PI;
158 φ.rem_euclid(2.0 * π) 162 φ.rem_euclid(2.0 * π)
159 } 163 }
207 SidePoint{ z : self.z + h, angle : normalise_angle(self.angle + v / r) } 211 SidePoint{ z : self.z + h, angle : normalise_angle(self.angle + v / r) }
208 } 212 }
209 213
210 } 214 }
211 215
212 /// Point on a [`Cylinder`] 216 /// Point on some [`Cylinder`]
213 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 217 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
214 pub enum Point { 218 pub enum Point {
215 Top(CapPoint), 219 Top(CapPoint),
216 Bottom(CapPoint), 220 Bottom(CapPoint),
217 Side(SidePoint), 221 Side(SidePoint),
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 {
268 write!(f, "{}", s) 276 write!(f, "{}", s)
269 } 277 }
270 } 278 }
271 279
272 impl Point { 280 impl Point {
281 /// Returns the face of the point
273 fn face(&self) -> Face { 282 fn face(&self) -> Face {
274 match *self { 283 match *self {
275 Point::Top(..) => Face::Top, 284 Point::Top(..) => Face::Top,
276 Point::Bottom(_) => Face::Bottom, 285 Point::Bottom(_) => Face::Bottom,
277 Point::Side(_) => Face::Side, 286 Point::Side(_) => Face::Side,
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)))
303 #[inline] 314 #[inline]
304 fn swap<A>((a, b) : (A, A)) -> (A, A) { 315 fn swap<A>((a, b) : (A, A)) -> (A, A) {
305 (b, a) 316 (b, a)
306 } 317 }
307 318
319 /// Indicates whether the `a` and `b` are a distance of less than [`f64::EPSILON`].
308 #[inline] 320 #[inline]
309 fn indistinguishable(a : f64, b : f64) -> bool { 321 fn indistinguishable(a : f64, b : f64) -> bool {
310 a > b - f64::EPSILON && a < b + f64::EPSILON 322 a > b - f64::EPSILON && a < b + f64::EPSILON
311 } 323 }
312 324
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 {
370 } 384 }
371 385
372 /// Find angles where the geodesic passing through a cap at height `z` from `side1` to `side2` 386 /// Find angles where the geodesic passing through a cap at height `z` from `side1` to `side2`
373 /// crosses the cap edge. **Panics if `side2.angle < side1.angle`.** 387 /// crosses the cap edge. **Panics if `side2.angle < side1.angle`.**
374 /// 388 ///
375 /// Uses `newton_sym2x2`. 389 /// Uses [`newton_sym2x2`].
376 fn side_cap_side_crossing( 390 fn side_cap_side_crossing(
377 &self, 391 &self,
378 side1 : &SidePoint, 392 side1 : &SidePoint,
379 z : f64, 393 z : f64,
380 side2 : &SidePoint 394 side2 : &SidePoint
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 {
512 (t1/n)*(n + t2.norm(L2)) 529 (t1/n)*(n + t2.norm(L2))
513 } 530 }
514 } 531 }
515 } 532 }
516 533
534 /// Calculates the log between points on a `side` and a `cap` of the cylinder, in this direction.
535 /// The `z` coordinate of the cap and an indication whether it is a `top` or bottom cap must
536 /// also be given.
517 fn side_cap_log( 537 fn side_cap_log(
518 &self, 538 &self,
519 side : &SidePoint, 539 side : &SidePoint,
520 cap : &CapPoint, (z, top) : (f64, bool), 540 cap : &CapPoint, (z, top) : (f64, bool),
521 ) -> Tangent { 541 ) -> Tangent {
545 (t1/n)*(n + t2.norm(L2)) 565 (t1/n)*(n + t2.norm(L2))
546 } 566 }
547 } 567 }
548 } 568 }
549 569
570 /// Calculates the log between points on two sides of the cylinder, assuming the corresonding
571 /// geodesic crosses a cap at height `z`. The `top` parameter indicates whether this is a
572 /// top cap.
550 fn side_cap_side_log( 573 fn side_cap_side_log(
551 &self, 574 &self,
552 side1 : &SidePoint, 575 side1 : &SidePoint,
553 (z, top) : (f64, bool), 576 (z, top) : (f64, bool),
554 side2 : &SidePoint 577 side2 : &SidePoint
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) => {
749 } 778 }
750 } 779 }
751 } 780 }
752 } 781 }
753 782
783 /// Calculates the exponential map from `point` in the direction of `tangent`.
754 fn exp(&self, point : &Point, tangent : &Tangent) -> Point { 784 fn exp(&self, point : &Point, tangent : &Tangent) -> Point {
755 let mut p = *point; 785 let mut p = *point;
756 let mut t = *tangent; 786 let mut t = *tangent;
757 loop { 787 loop {
758 (p, t) = match self.partial_exp(p, t) { 788 (p, t) = match self.partial_exp(p, t) {
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
1021 panic!("No tangent left"); 1058 panic!("No tangent left");
1022 } 1059 }
1023 } 1060 }
1024 } 1061 }
1025 1062
1063 /// Tests for correct tangent calculation from the side to a cap.
1026 #[test] 1064 #[test]
1027 fn side_top_exp() { 1065 fn side_top_exp() {
1028 let π = f64::PI; 1066 let π = f64::PI;
1029 let angles = [0.0, π/2.0, π*5.0/6.0, π*7.0/5.0]; 1067 let angles = [0.0, π/2.0, π*5.0/6.0, π*7.0/5.0];
1030 1068

mercurial