|
1 /*! |
|
2 Implementation of the surface of a 3D cylinder as a [`ManifoldPoint`]. |
|
3 */ |
|
4 |
|
5 use alg_tools::euclidean::Euclidean; |
|
6 use serde_repr::*; |
|
7 use serde::{Serialize, Deserialize}; |
|
8 use alg_tools::loc::Loc; |
|
9 use alg_tools::norms::{Norm, L2}; |
|
10 use alg_tools::types::Float; |
|
11 use crate::manifold::{ManifoldPoint, EmbeddedManifoldPoint, FacedManifoldPoint}; |
|
12 use crate::newton::{newton_sym1x1, newton_sym2x2}; |
|
13 |
|
14 /// Angle |
|
15 pub type Angle = f64; |
|
16 |
|
17 /// Cylindrical coordinates in ℝ^3 |
|
18 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] |
|
19 pub struct CylCoords { |
|
20 pub r : f64, |
|
21 pub angle : Angle, |
|
22 pub z : f64 |
|
23 } |
|
24 |
|
25 impl CylCoords { |
|
26 #[inline] |
|
27 pub fn to_cartesian(&self) -> Loc<f64, 3> { |
|
28 let &CylCoords{r, angle, z} = self; |
|
29 [r * angle.cos(), r * angle.sin(), z].into() |
|
30 } |
|
31 |
|
32 #[inline] |
|
33 #[allow(dead_code)] |
|
34 pub fn from_cartesian(coords : Loc<f64, 3>) -> Self { |
|
35 let [x, y, z] = coords.into(); |
|
36 let r = (x*x + y*y).sqrt(); |
|
37 let angle = y.atan2(x); |
|
38 CylCoords {r, angle, z} |
|
39 } |
|
40 } |
|
41 |
|
42 /// Coordinates on a cap |
|
43 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] |
|
44 pub struct CapPoint { |
|
45 pub r : f64, |
|
46 pub angle : Angle |
|
47 } |
|
48 |
|
49 #[inline] |
|
50 fn rotate(φ : f64, Loc([x, y]) : Loc<f64, 2>) -> Loc<f64, 2> { |
|
51 let sin_φ = φ.sin(); |
|
52 let cos_φ = φ.cos(); |
|
53 [cos_φ * x - sin_φ * y, sin_φ * x + cos_φ * y].into() |
|
54 } |
|
55 |
|
56 impl CapPoint { |
|
57 #[inline] |
|
58 /// Convert to cylindrical coordinates given z coordinate |
|
59 fn cyl_coords(&self, z : f64) -> CylCoords { |
|
60 let CapPoint { r, angle } = *self; |
|
61 CylCoords { r, angle, z } |
|
62 } |
|
63 |
|
64 #[inline] |
|
65 /// Convert to cylindrical coordinates given z coordinate |
|
66 fn cartesian_coords(&self) -> Loc<f64, 2> { |
|
67 let CapPoint { r, angle } = *self; |
|
68 [r * angle.cos(), r * angle.sin()].into() |
|
69 } |
|
70 |
|
71 #[inline] |
|
72 #[allow(dead_code)] |
|
73 /// Convert to cylindrical coordinates given z coordinate |
|
74 fn from_cartesian(coords : Loc<f64, 2>) -> Self { |
|
75 let [x, y] = coords.into(); |
|
76 let r = (x*x + y*y).sqrt(); |
|
77 let angle = y.atan2(x); |
|
78 CapPoint { r, angle } |
|
79 } |
|
80 |
|
81 #[inline] |
|
82 /// Calculate the vector between two points on the cap |
|
83 fn log(&self, other : &CapPoint) -> Loc<f64, 2> { |
|
84 other.cartesian_coords() - self.cartesian_coords() |
|
85 } |
|
86 |
|
87 #[inline] |
|
88 /// Calculate partial exponential map until boundary. |
|
89 /// Returns the final point within the cap as well as a remaining tangent on |
|
90 /// the side of the cylinder, if `t` wasn't fully used. |
|
91 fn partial_exp(&self, r : f64, t : &Tangent) -> (CapPoint, Option<Tangent>) { |
|
92 let q = self.cartesian_coords() + t; |
|
93 let n = q.norm2(); |
|
94 if n <= r { |
|
95 (Self::from_cartesian(q), None) |
|
96 } else { |
|
97 let p = q * r / n; |
|
98 (Self::from_cartesian(p), Some(q - p)) |
|
99 } |
|
100 } |
|
101 |
|
102 #[inline] |
|
103 /// Convert tangent from side tangent to cap tangent |
|
104 fn tangent_from_side(&self, top : bool, t : Tangent) -> Tangent { |
|
105 if top { |
|
106 // The angle is such that down would be rotated to self.angle, counterclockwise |
|
107 // The new tangent is R[down + t] - R[down] = Rt. |
|
108 rotate(self.angle+f64::PI/2.0, t) |
|
109 } else { |
|
110 // The angle is such that up would be rotated to self.angle, clockwise |
|
111 rotate(self.angle-f64::PI/2.0, t) |
|
112 } |
|
113 } |
|
114 |
|
115 #[inline] |
|
116 /// Convert tangent from cap tangent to tangent tangent |
|
117 fn tangent_to_side(&self, top : bool, t : Tangent) -> Tangent { |
|
118 if top { |
|
119 // The angle is such that self.angle would be rotated to down, clockwise |
|
120 rotate(-self.angle-f64::PI/2.0, t) |
|
121 } else { |
|
122 // The angle is such that self.angle would be rotated to up, counterclockwise |
|
123 rotate(f64::PI/2.0-self.angle, t) |
|
124 } |
|
125 } |
|
126 } |
|
127 |
|
128 /// Coordinates on a side |
|
129 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] |
|
130 pub struct SidePoint { |
|
131 pub z : f64, |
|
132 pub angle : Angle |
|
133 } |
|
134 |
|
135 #[inline] |
|
136 fn anglediff(mut φ1 : f64, mut φ2 : f64) -> f64 { |
|
137 let π = f64::PI; |
|
138 φ1 = normalise_angle(φ1); |
|
139 φ2 = normalise_angle(φ2); |
|
140 let α = φ2 - φ1; |
|
141 if α >= 0.0 { |
|
142 if α <= π { |
|
143 α |
|
144 } else { |
|
145 α - 2.0 * π |
|
146 } |
|
147 } else { |
|
148 if α >= -π { |
|
149 α |
|
150 } else { |
|
151 2.0 * π + α |
|
152 } |
|
153 } |
|
154 } |
|
155 |
|
156 #[inline] |
|
157 pub fn normalise_angle(φ : f64) -> f64 { |
|
158 let π = f64::PI; |
|
159 φ.rem_euclid(2.0 * π) |
|
160 } |
|
161 |
|
162 impl SidePoint { |
|
163 #[inline] |
|
164 /// Convert to cylindrical coordinates given radius |
|
165 fn cyl_coords(&self, r : f64) -> CylCoords { |
|
166 let SidePoint { z, angle } = *self; |
|
167 CylCoords { r, angle, z } |
|
168 } |
|
169 |
|
170 #[inline] |
|
171 /// Calculate tangent vector between two points on the side, given radius |
|
172 fn log(&self, r : f64, other : &SidePoint) -> Loc<f64, 2> { |
|
173 let SidePoint{ z : z1, angle : angle1 } = *self; |
|
174 let SidePoint{ z : z2, angle : angle2 } = *other; |
|
175 let φ = anglediff(angle1, angle2); |
|
176 // TODO: is this correct? |
|
177 [r*φ, z2-z1].into() |
|
178 } |
|
179 |
|
180 #[inline] |
|
181 /// Calculate partial exponential map under boundary |
|
182 /// Returns a point on the next face, as well as a remaining tangent on |
|
183 /// the side of the cylinder, if `t` wasn't fully used. |
|
184 fn partial_exp(&self, r : f64, (a, b) : (f64, f64), t : &Tangent) |
|
185 -> (SidePoint, Option<Tangent>) |
|
186 { |
|
187 assert!(a <= self.z && self.z <= b); |
|
188 let Loc([_, h]) = *t; |
|
189 let s = if h > 0.0 { |
|
190 ((b - self.z)/h).min(1.0) |
|
191 } else if h < 0.0 { |
|
192 ((a - self.z)/h).min(1.0) |
|
193 } else { |
|
194 1.0 |
|
195 }; |
|
196 let d = t * s; |
|
197 let p = self.unflatten(r, d); |
|
198 if s < 1.0 { |
|
199 (p, Some(t - d)) |
|
200 } else { |
|
201 (p, None) |
|
202 } |
|
203 } |
|
204 |
|
205 #[inline] |
|
206 /// Unflattens another point in the local coordinate system of self |
|
207 fn unflatten(&self, r : f64, Loc([v, h]) : Loc<f64, 2>) -> Self { |
|
208 SidePoint{ z : self.z + h, angle : normalise_angle(self.angle + v / r) } |
|
209 } |
|
210 |
|
211 } |
|
212 |
|
213 /// Point on a [`Cylinder`] |
|
214 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] |
|
215 pub enum Point { |
|
216 Top(CapPoint), |
|
217 Bottom(CapPoint), |
|
218 Side(SidePoint), |
|
219 } |
|
220 |
|
221 /// Face on a [`Cylinder`] |
|
222 #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize_repr, Deserialize_repr)] |
|
223 #[repr(u8)] |
|
224 pub enum Face { |
|
225 Top, |
|
226 Bottom, |
|
227 Side, |
|
228 } |
|
229 |
|
230 #[derive(Clone, Debug, PartialEq)] |
|
231 pub struct OnCylinder<'a> { |
|
232 cylinder : &'a Cylinder, |
|
233 point : Point, |
|
234 } |
|
235 |
|
236 |
|
237 /// Cylinder configuration |
|
238 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] |
|
239 pub struct CylinderConfig { |
|
240 pub newton_iters : usize, |
|
241 } |
|
242 |
|
243 impl Default for CylinderConfig { |
|
244 fn default() -> Self { |
|
245 CylinderConfig { newton_iters : 10 } |
|
246 } |
|
247 } |
|
248 |
|
249 /// A cylinder |
|
250 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] |
|
251 pub struct Cylinder { |
|
252 /// Radius of the cylinder |
|
253 pub radius : f64, |
|
254 /// Height of the cylinder |
|
255 pub height : f64, |
|
256 /// Configuration for numerical methods |
|
257 pub config : CylinderConfig |
|
258 } |
|
259 |
|
260 |
|
261 impl std::fmt::Display for Face { |
|
262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|
263 use Face::*; |
|
264 let s = match *self { |
|
265 Top => "Top", |
|
266 Bottom => "Bottom", |
|
267 Side => "Side", |
|
268 }; |
|
269 write!(f, "{}", s) |
|
270 } |
|
271 } |
|
272 |
|
273 impl Point { |
|
274 fn face(&self) -> Face { |
|
275 match *self { |
|
276 Point::Top(..) => Face::Top, |
|
277 Point::Bottom(_) => Face::Bottom, |
|
278 Point::Side(_) => Face::Side, |
|
279 } |
|
280 } |
|
281 } |
|
282 |
|
283 impl<'a> FacedManifoldPoint for OnCylinder<'a> { |
|
284 type Face = Face; |
|
285 /// Returns the face of this point. |
|
286 fn face(&self) -> Face { |
|
287 self.point.face() |
|
288 } |
|
289 } |
|
290 |
|
291 // Tangent vector |
|
292 type Tangent = Loc<f64, 2>; |
|
293 |
|
294 #[inline] |
|
295 fn best_tangent<I>(tangents : I) -> (Tangent, f64) |
|
296 where I : IntoIterator<Item = Tangent> { |
|
297 tangents.into_iter() |
|
298 .map(|t| (t, t.norm(L2))) |
|
299 .reduce(|a@(_, la), b@(_, lb)| if la < lb { a } else { b }) |
|
300 .unwrap() |
|
301 } |
|
302 |
|
303 /// Swap the elements of a two-tuple |
|
304 #[inline] |
|
305 fn swap<A>((a, b) : (A, A)) -> (A, A) { |
|
306 (b, a) |
|
307 } |
|
308 |
|
309 #[inline] |
|
310 fn indistinguishable(a : f64, b : f64) -> bool { |
|
311 a > b - f64::EPSILON && a < b + f64::EPSILON |
|
312 } |
|
313 |
|
314 impl Cylinder { |
|
315 /// Return the cylindrical coordinates of `point` on this cylinder |
|
316 fn cyl_coords(&self, point : &Point) -> CylCoords { |
|
317 match *point { |
|
318 Point::Top(cap) => { cap.cyl_coords(self.top_z()) }, |
|
319 Point::Bottom(cap) => { cap.cyl_coords(self.bottom_z()) }, |
|
320 Point::Side(side) => { side.cyl_coords(self.radius) }, |
|
321 } |
|
322 } |
|
323 |
|
324 #[inline] |
|
325 pub fn top_z(&self) -> f64 { |
|
326 self.height / 2.0 |
|
327 } |
|
328 |
|
329 #[inline] |
|
330 pub fn bottom_z(&self) -> f64 { |
|
331 -self.height / 2.0 |
|
332 } |
|
333 |
|
334 /// Find angle where a geodesic from `side` to `(cap, z)` crosses the cap edge. |
|
335 /// |
|
336 /// Uses `newton_sym1x1`. |
|
337 fn side_cap_crossing( |
|
338 &self, |
|
339 side : &SidePoint, |
|
340 cap : &CapPoint, z : f64, // `z` is the z coordinate of cap |
|
341 ) -> Angle { |
|
342 let &SidePoint { z : z_1, angle : φ_1 } = side; |
|
343 let &CapPoint { r : r_2, angle : φ_2 } = cap; |
|
344 assert_ne!(z, z_1); |
|
345 let d_1 = z - z_1; |
|
346 let d2_1 = d_1 * d_1; |
|
347 let r = self.radius; |
|
348 let r2 = r * r; |
|
349 let r2_2 = r_2 * r_2; |
|
350 let rr_2 = r * r_2; |
|
351 |
|
352 let g = |α_1 : f64| { |
|
353 let ψ = φ_2 - φ_1 - α_1; |
|
354 let ψ_cos = ψ.cos(); |
|
355 let ψ_sin = ψ.sin(); |
|
356 let ψ_sin2 = ψ_sin * ψ_sin; |
|
357 let ψ_cos2 = ψ_cos * ψ_cos; |
|
358 let α2_1 = α_1 * α_1; |
|
359 let c = d2_1 + r2 * α2_1; |
|
360 let e = r2 + r2_2 - 2.0 * rr_2 * ψ_cos; |
|
361 let g = r2 * α_1 / c.sqrt() - rr_2 * ψ_sin / e.sqrt(); |
|
362 let h = r2 * d2_1 / c.powf(3.0/2.0) |
|
363 + r * r_2 * ((r2 + r2_2) * ψ_cos - rr_2 * (ψ_sin2 - 2.0 * ψ_cos2)) |
|
364 / e.powf(3.0/2.0); |
|
365 (h, g) |
|
366 }; |
|
367 |
|
368 let α_1 = newton_sym1x1(g, 0.0, self.config.newton_iters); |
|
369 normalise_angle(φ_1 + α_1) |
|
370 |
|
371 } |
|
372 |
|
373 /// Find angles where the geodesic passing through a cap at height `z` from `side1` to `side2` |
|
374 /// crosses the cap edge. **Panics if `side2.angle < side1.angle`.** |
|
375 /// |
|
376 /// Uses `newton_sym2x2`. |
|
377 fn side_cap_side_crossing( |
|
378 &self, |
|
379 side1 : &SidePoint, |
|
380 z : f64, |
|
381 side2 : &SidePoint |
|
382 ) -> (Angle, Angle) { |
|
383 let &SidePoint { z : z_1, angle : φ_1 } = side1; |
|
384 let &SidePoint { z : z_2, angle : φ_2 } = side2; |
|
385 assert!(φ_2 >= φ_1); |
|
386 assert_ne!(z_1, z); |
|
387 assert_ne!(z_2, z); |
|
388 let r = self.radius; |
|
389 let r2 = r * r; |
|
390 let d_1 = z - z_1; |
|
391 let d_2 = z - z_2; |
|
392 let d2_1 = d_1 * d_1; |
|
393 let d2_2 = d_2 * d_2; |
|
394 let g = |α_1 : f64, α_2 : f64| { |
|
395 let α2_1 = α_1 * α_1; |
|
396 let α2_2 = α_2 * α_2; |
|
397 let ψ = (α_1 + α_2 + φ_1 - φ_2) / 2.0; |
|
398 let ψ_sin = ψ.sin(); |
|
399 let ψ_cos = ψ.cos(); |
|
400 let c_1 = d2_1 + r2 * α2_1; |
|
401 let c_2 = d2_2 + r2 * α2_2; |
|
402 let g_1 = r2 * α_1 / c_1.sqrt() - r * ψ_cos; |
|
403 let g_2 = r2 * α_2 / c_2.sqrt() - r * ψ_cos; |
|
404 let h_12 = (r / 2.0) * ψ_sin; |
|
405 let h_11 = r2 * d2_1 / c_1.powf(3.0 / 2.0) + h_12; |
|
406 let h_22 = r2 * d2_2 / c_2.powf(3.0 / 2.0) + h_12; |
|
407 ([h_11, h_12, h_22], [g_1, g_2]) |
|
408 }; |
|
409 |
|
410 let [α_1, α_2] = newton_sym2x2(g, [0.0, 0.0], self.config.newton_iters); |
|
411 (normalise_angle(φ_1 + α_1), normalise_angle(φ_2 + α_2)) |
|
412 |
|
413 } |
|
414 |
|
415 /// Find angles where the geodesic passing through the side from `cap1` at height `z_1` |
|
416 /// to `cap2` at height `z_2` crosses the cap edges. |
|
417 /// **Panics if `cap2.angle < cap1.angle`.** |
|
418 /// |
|
419 /// Uses `newton_sym2x2`. |
|
420 fn cap_side_cap_crossing( |
|
421 &self, |
|
422 cap1 : &CapPoint, z_1 : f64, |
|
423 cap2 : &CapPoint, z_2 : f64, |
|
424 init_by_cap2 : bool |
|
425 ) -> (Angle, Angle) { |
|
426 let r = self.radius; |
|
427 let &CapPoint { r : r_1, angle : φ_1 } = cap1; |
|
428 let &CapPoint { r : r_2, angle : φ_2 } = cap2; |
|
429 assert!(φ_2 >= φ_1); |
|
430 assert_ne!(r_1, r); |
|
431 assert_ne!(r_2, r); |
|
432 assert_ne!(z_2, z_1); |
|
433 if r_1 == 0.0 && r_2 == 0.0 { |
|
434 // Singular case: both points are in the middle of the caps. |
|
435 return (φ_1, φ_1) |
|
436 } |
|
437 let r2 = r * r; |
|
438 let d = (z_2 - z_1).abs(); |
|
439 let d2 = d * d; |
|
440 let r2_1 = r_1 * r_1; |
|
441 let r2_2 = r_2 * r_2; |
|
442 let rr_1 = r * r_1; |
|
443 let rr_2 = r * r_2; |
|
444 let f = |α_1 : f64, α_2 : f64| { |
|
445 let cos_α1 = α_1.cos(); |
|
446 let sin_α1 = α_1.sin(); |
|
447 let cos_α2 = α_2.cos(); |
|
448 let sin_α2 = α_2.sin(); |
|
449 //let cos2_α1 = cos_α1 * cos_α1; |
|
450 let sin2_α1 = sin_α1 * sin_α1; |
|
451 //let cos2_α2 = cos_α2 * cos_α2; |
|
452 let sin2_α2 = sin_α2 * sin_α2; |
|
453 let ψ = φ_2 - φ_1 - α_1 - α_2; |
|
454 let ψ2 = ψ * ψ; |
|
455 let ψ2r2 = ψ2 * r2; |
|
456 //let r4 = r2 * r2; |
|
457 let b = d2 + ψ2r2; |
|
458 let c = r2 * ψ / b.sqrt(); |
|
459 let e_1 = r2 + r2_1 - 2.0 * rr_1 * cos_α1; |
|
460 let e_2 = r2 + r2_2 - 2.0 * rr_2 * cos_α2; |
|
461 let g_1 = rr_1 * sin_α1 / e_1.sqrt() - c; |
|
462 let g_2 = rr_2 * sin_α2 / e_2.sqrt() - c; |
|
463 let h_12 = r2 * (1.0 - ψ2r2 / b) / b.sqrt(); |
|
464 // let h_11 = rr_1 * ( (r2 + r2_1) * cos_α1 - rr_1 * ( sin2_α1 + 2.0 * cos2_α1) ) |
|
465 // / e_1.powf(3.0/2.0) + h_12; |
|
466 // let h_22 = rr_2 * ( (r2 + r2_2) * cos_α2 - rr_2 * ( sin2_α2 + 2.0 * cos2_α2) ) |
|
467 // / e_2.powf(3.0/2.0) + h_12; |
|
468 // let h_11 = rr_1 * cos_α1 / e_1.sqrt() - rr_1*rr_1 * sin2_α1 / e_1.powf(3.0/2.0) + h_12; |
|
469 // let h_22 = rr_2 * cos_α2 / e_2.sqrt() - rr_2*rr_2 * sin2_α2 / e_2.powf(3.0/2.0) + h_12; |
|
470 let h_11 = rr_1 * (cos_α1 - rr_1 * sin2_α1 / e_1) / e_1.sqrt() + h_12; |
|
471 let h_22 = rr_2 * (cos_α2 - rr_2 * sin2_α2 / e_2) / e_2.sqrt() + h_12; |
|
472 ([h_11, h_12, h_22], [g_1, g_2]) |
|
473 }; |
|
474 |
|
475 let α_init = if init_by_cap2 { |
|
476 [φ_2 - φ_1, 0.0] |
|
477 } else { |
|
478 [0.0, φ_2 - φ_1] |
|
479 }; |
|
480 let [α_1, α_2] = newton_sym2x2(f, α_init, self.config.newton_iters); |
|
481 (normalise_angle(φ_1 + α_1), normalise_angle(φ_2 - α_2)) |
|
482 } |
|
483 |
|
484 fn cap_side_log( |
|
485 &self, |
|
486 cap : &CapPoint, (z, top) : (f64, bool), |
|
487 side : &SidePoint |
|
488 ) -> Tangent { |
|
489 let r = self.radius; |
|
490 if indistinguishable(side.z, z) { |
|
491 // Degenerate case |
|
492 let capedge = CapPoint{ angle : side.angle, r }; |
|
493 cap.log(&capedge) |
|
494 } else if indistinguishable(r, cap.r) |
|
495 && anglediff(side.angle, cap.angle).abs() < f64::PI/2.0 { |
|
496 // Degenerate case |
|
497 let sideedge = SidePoint{ angle : cap.angle, z}; |
|
498 cap.tangent_from_side(top, sideedge.log(r, side)) |
|
499 } else { |
|
500 let φ = self.side_cap_crossing(side, cap, z); |
|
501 let capedge = CapPoint{ angle : φ, r }; |
|
502 let sideedge = SidePoint{ angle : φ, z }; |
|
503 let t1 = cap.log(&capedge); |
|
504 let t2 = sideedge.log(r, side); |
|
505 // Either option should give the same result, but the first one avoids division. |
|
506 t1 + capedge.tangent_from_side(top, t2) |
|
507 // let n = t1.norm(L2); |
|
508 // (t1/n)*(n + t2.norm(L2)) |
|
509 } |
|
510 } |
|
511 |
|
512 fn side_cap_log( |
|
513 &self, |
|
514 side : &SidePoint, |
|
515 cap : &CapPoint, (z, top) : (f64, bool), |
|
516 ) -> Tangent { |
|
517 let r = self.radius; |
|
518 if indistinguishable(side.z, z) { |
|
519 // Degenerate case |
|
520 let capedge = CapPoint{ angle : side.angle, r }; |
|
521 capedge.tangent_to_side(top, capedge.log(cap)) |
|
522 } else if indistinguishable(r, cap.r) |
|
523 && anglediff(side.angle, cap.angle).abs() < f64::PI/2.0 { |
|
524 // Degenerate case |
|
525 side.log(r, &SidePoint{ z, angle : cap.angle }) |
|
526 } else { |
|
527 let φ = self.side_cap_crossing(side, cap, z); |
|
528 let capedge = CapPoint{ angle : φ, r }; |
|
529 let sideedge = SidePoint{ angle : φ, z }; |
|
530 let t1 = side.log(r, &sideedge); |
|
531 let t2 = capedge.log(cap); |
|
532 // Either option should give the same result, but the first one avoids division. |
|
533 t1 + capedge.tangent_to_side(top, t2) |
|
534 // let n = t1.norm(L2); |
|
535 // (t1/n)*(n + t2.norm(L2)) |
|
536 } |
|
537 } |
|
538 |
|
539 fn side_cap_side_log( |
|
540 &self, |
|
541 side1 : &SidePoint, |
|
542 (z, top) : (f64, bool), |
|
543 side2 : &SidePoint |
|
544 ) -> Tangent { |
|
545 let r = self.radius; |
|
546 if indistinguishable(side1.z, z) { |
|
547 // Degenerate case |
|
548 let capedge1 = CapPoint{ angle : side1.angle, r }; |
|
549 capedge1.tangent_to_side(top, self.cap_side_log(&capedge1, (z, top), side2)) |
|
550 } else if indistinguishable(side2.z, z) { |
|
551 // Degenerate case |
|
552 let capedge2 = CapPoint{ angle : side2.angle, r }; |
|
553 self.side_cap_log(side1, &capedge2, (z, top)) |
|
554 } else { |
|
555 let (φ1, φ2) = if side2.angle >= side1.angle { |
|
556 self.side_cap_side_crossing(side1, z, side2) |
|
557 } else { |
|
558 swap(self.side_cap_side_crossing(side2, z, side1)) |
|
559 }; |
|
560 let capedge1 = CapPoint{ angle : φ1, r }; |
|
561 let sideedge1 = SidePoint{ angle : φ1, z }; |
|
562 let capedge2 = CapPoint{ angle : φ2, r }; |
|
563 let sideedge2 = SidePoint{ angle : φ2, z }; |
|
564 let t1 = side1.log(r, &sideedge1); |
|
565 let t2 = capedge1.log(&capedge2); |
|
566 let t3 = sideedge2.log(r, &side2); |
|
567 // Any option should give the same result, but the first one avoids division. |
|
568 // t1 + capedge1.tangent_to_side(top, t2 + capedge2.tangent_from_side(top, t3)) |
|
569 // |
|
570 // let n = t2.norm(L2); |
|
571 // t1 + capedge1.tangent_to_side(top, (t2/n)*(n + t3.norm(L2))) |
|
572 // |
|
573 let n = t1.norm(L2); |
|
574 (t1/n)*(n + t2.norm(L2) + t3.norm(L2)) |
|
575 // |
|
576 // let n = t1.norm(L2); |
|
577 // let t23 = t2 + capedge2.tangent_from_side(top, t3); |
|
578 // (t1/n)*(n + t23.norm(L2)) |
|
579 } |
|
580 } |
|
581 |
|
582 fn cap_side_cap_log( |
|
583 &self, |
|
584 cap1 : &CapPoint, (z1, top1) : (f64, bool), |
|
585 cap2 : &CapPoint, (z2, top2) : (f64, bool), |
|
586 init_by_cap2 : bool, |
|
587 ) -> Tangent { |
|
588 let r = self.radius; |
|
589 if indistinguishable(cap1.r, r) { |
|
590 // Degenerate case |
|
591 let sideedge1 = SidePoint{ angle : cap1.angle, z : z1 }; |
|
592 cap1.tangent_from_side(top1, self.side_cap_log(&sideedge1, cap2, (z2, top2))) |
|
593 } else if indistinguishable(cap2.r, r) { |
|
594 // Degenerate case |
|
595 let sideedge2 = SidePoint{ angle : cap2.angle, z : z2 }; |
|
596 self.cap_side_log(cap1, (z1, top1), &sideedge2) |
|
597 } else { |
|
598 let (φ1, φ2) = if cap2.angle >= cap1.angle { |
|
599 self.cap_side_cap_crossing(cap1, z1, cap2, z2, init_by_cap2) |
|
600 } else { |
|
601 swap(self.cap_side_cap_crossing(cap2, z2, cap1, z1, !init_by_cap2)) |
|
602 }; |
|
603 let sideedge1 = SidePoint{ angle : φ1, z : z1 }; |
|
604 let capedge1 = CapPoint{ angle : φ1, r }; |
|
605 let sideedge2 = SidePoint{ angle : φ2, z : z2}; |
|
606 let capedge2 = CapPoint{ angle : φ2, r }; |
|
607 let t1 = cap1.log(&capedge1); |
|
608 let t2 = sideedge1.log(r, &sideedge2); |
|
609 let t3 = capedge2.log(cap2); |
|
610 // Either option should give the same result, but the first one avoids division. |
|
611 t1 + capedge1.tangent_from_side(top1, t2 + capedge2.tangent_to_side(top2, t3)) |
|
612 //let n = t1.norm(L2); |
|
613 //(t1/n)*(n + t2.norm(L2) + t3.norm(L2)) |
|
614 } |
|
615 } |
|
616 |
|
617 /// Calculates both the logarithmic map and distance to another point |
|
618 fn log_dist(&self, source : &Point, destination : &Point) -> (Tangent, f64) { |
|
619 use Point::*; |
|
620 match (source, destination) { |
|
621 (Top(cap1), Top(cap2)) => { |
|
622 best_tangent([cap1.log(cap2)]) |
|
623 }, |
|
624 (Bottom(cap1), Bottom(cap2)) => { |
|
625 best_tangent([cap1.log(cap2)]) |
|
626 }, |
|
627 (Bottom(cap), Side(side)) => { |
|
628 best_tangent([self.cap_side_log(cap, (self.bottom_z(), false), side)]) |
|
629 }, |
|
630 (Top(cap), Side(side)) => { |
|
631 best_tangent([self.cap_side_log(cap, (self.top_z(), true), side)]) |
|
632 }, |
|
633 (Side(side), Bottom(cap)) => { |
|
634 best_tangent([self.side_cap_log(side, cap, (self.bottom_z(), false))]) |
|
635 }, |
|
636 (Side(side), Top(cap)) => { |
|
637 best_tangent([self.side_cap_log(side, cap, (self.top_z(), true))]) |
|
638 }, |
|
639 (Side(side1), Side(side2)) => { |
|
640 best_tangent([ |
|
641 side1.log(self.radius, side2), |
|
642 self.side_cap_side_log(side1, (self.top_z(), true), side2), |
|
643 self.side_cap_side_log(side1, (self.bottom_z(), false), side2), |
|
644 ]) |
|
645 }, |
|
646 (Top(cap1), Bottom(cap2)) => { |
|
647 best_tangent([ |
|
648 // We try a few possible initialisations for Newton |
|
649 self.cap_side_cap_log( |
|
650 cap1, (self.top_z(), true), |
|
651 cap2, (self.bottom_z(), false), |
|
652 false |
|
653 ), |
|
654 self.cap_side_cap_log( |
|
655 cap1, (self.top_z(), true), |
|
656 cap2, (self.bottom_z(), false), |
|
657 true |
|
658 ), |
|
659 ]) |
|
660 }, |
|
661 (Bottom(cap1), Top(cap2)) => { |
|
662 best_tangent([ |
|
663 // We try a few possible initialisations for Newton |
|
664 self.cap_side_cap_log( |
|
665 cap1, (self.bottom_z(), false), |
|
666 cap2, (self.top_z(), true), |
|
667 false |
|
668 ), |
|
669 self.cap_side_cap_log( |
|
670 cap1, (self.bottom_z(), false), |
|
671 cap2, (self.top_z(), true), |
|
672 true |
|
673 ), |
|
674 ]) |
|
675 }, |
|
676 } |
|
677 } |
|
678 |
|
679 #[allow(unreachable_code)] |
|
680 #[allow(unused_variables)] |
|
681 fn partial_exp(&self, point : Point, t : Tangent) -> (Point, Option<Tangent>) { |
|
682 match point { |
|
683 Point::Top(cap) => { |
|
684 let (cap_new, t_new_basis) = cap.partial_exp(self.radius, &t); |
|
685 match t_new_basis { |
|
686 None => (Point::Top(cap_new), None), |
|
687 Some(t_new) => { |
|
688 let side_new = SidePoint{ angle : cap_new.angle, z : self.top_z() }; |
|
689 (Point::Side(side_new), Some(cap_new.tangent_to_side(true, t_new))) |
|
690 } |
|
691 } |
|
692 }, |
|
693 Point::Bottom(cap) => { |
|
694 let (cap_new, t_new_basis) = cap.partial_exp(self.radius, &t); |
|
695 match t_new_basis { |
|
696 None => (Point::Bottom(cap_new), None), |
|
697 Some(t_new) => { |
|
698 let side_new = SidePoint{ angle : cap_new.angle, z : self.bottom_z() }; |
|
699 (Point::Side(side_new), Some(cap_new.tangent_to_side(false, t_new))) |
|
700 } |
|
701 } |
|
702 }, |
|
703 Point::Side(side) => { |
|
704 let lims = (self.bottom_z(), self.top_z()); |
|
705 let (side_new, t_new_basis) = side.partial_exp(self.radius, lims, &t); |
|
706 match t_new_basis { |
|
707 None => (Point::Side(side_new), None), |
|
708 Some(t_new) => { |
|
709 if side_new.z >= self.top_z() - f64::EPSILON { |
|
710 let cap_new = CapPoint { angle : side_new.angle, r : self.radius }; |
|
711 (Point::Top(cap_new), Some(cap_new.tangent_from_side(true, t_new))) |
|
712 } else { |
|
713 let cap_new = CapPoint { angle : side_new.angle, r : self.radius }; |
|
714 (Point::Bottom(cap_new), Some(cap_new.tangent_from_side(false, t_new))) |
|
715 } |
|
716 } |
|
717 } |
|
718 } |
|
719 } |
|
720 } |
|
721 |
|
722 fn exp(&self, point : &Point, tangent : &Tangent) -> Point { |
|
723 let mut p = *point; |
|
724 let mut t = *tangent; |
|
725 loop { |
|
726 (p, t) = match self.partial_exp(p, t) { |
|
727 (p, None) => break p, |
|
728 (p, Some(t)) => (p, t), |
|
729 }; |
|
730 } |
|
731 } |
|
732 |
|
733 /// Check that `point` has valid coordinates, and normalise angles |
|
734 pub fn normalise(&self, point : Point) -> Option<Point> { |
|
735 match point { |
|
736 Point::Side(side) => { |
|
737 let a = self.bottom_z(); |
|
738 let b = self.top_z(); |
|
739 (a <= side.z && side.z <= b).then(|| { |
|
740 Point::Side(SidePoint{ angle : normalise_angle(side.angle), .. side }) |
|
741 }) |
|
742 }, |
|
743 Point::Bottom(cap) => { |
|
744 (cap.r <= self.radius).then(|| { |
|
745 Point::Bottom(CapPoint{ angle : normalise_angle(cap.angle), .. cap }) |
|
746 }) |
|
747 }, |
|
748 Point::Top(cap) => { |
|
749 (cap.r <= self.radius).then(|| { |
|
750 Point::Top(CapPoint{ angle : normalise_angle(cap.angle), .. cap }) |
|
751 }) |
|
752 }, |
|
753 } |
|
754 } |
|
755 |
|
756 /// Convert `p` into a a point associated with the cylinder. |
|
757 /// |
|
758 /// May panic if the coordinates are invalid. |
|
759 pub fn point_on(&self, point : Point) -> OnCylinder<'_> { |
|
760 match self.normalise(point) { |
|
761 None => panic!("{point:?} not on cylinder {self:?}"), |
|
762 Some(point) => OnCylinder { cylinder : self, point } |
|
763 } |
|
764 } |
|
765 |
|
766 /// Convert `p` into a a point on side associated with the cylinder. |
|
767 /// |
|
768 /// May panic if the coordinates are invalid. |
|
769 pub fn point_on_side(&self, side : SidePoint) -> OnCylinder<'_> { |
|
770 self.point_on(Point::Side(side)) |
|
771 } |
|
772 |
|
773 /// Convert `p` into a a point on top associated with the cylinder. |
|
774 /// |
|
775 /// May panic if the coordinates are invalid. |
|
776 pub fn point_on_top(&self, cap : CapPoint) -> OnCylinder<'_> { |
|
777 self.point_on(Point::Top(cap)) |
|
778 } |
|
779 |
|
780 /// Convert `p` into a a point on bottom associated with the cylinder. |
|
781 /// |
|
782 /// May panic if the coordinates are invalid. |
|
783 pub fn point_on_bottom(&self, cap : CapPoint) -> OnCylinder<'_> { |
|
784 self.point_on(Point::Bottom(cap)) |
|
785 } |
|
786 |
|
787 /// Convert `p` into a a point on side associated with the cylinder. |
|
788 /// |
|
789 /// May panic if the coordinates are invalid. |
|
790 pub fn on_side(&self, angle : Angle, z : f64) -> OnCylinder<'_> { |
|
791 self.point_on_side(SidePoint{ angle, z }) |
|
792 } |
|
793 |
|
794 /// Convert `p` into a a point on top associated with the cylinder. |
|
795 /// |
|
796 /// May panic if the coordinates are invalid. |
|
797 pub fn on_top(&self, angle : Angle, r : f64) -> OnCylinder<'_> { |
|
798 self.point_on_top(CapPoint{ angle, r }) |
|
799 } |
|
800 |
|
801 /// Convert `p` into a a point on bottom associated with the cylinder. |
|
802 /// |
|
803 /// May panic if the coordinates are invalid. |
|
804 pub fn on_bottom(&self, angle : Angle, r : f64) -> OnCylinder<'_> { |
|
805 self.point_on_bottom(CapPoint{ angle, r }) |
|
806 } |
|
807 } |
|
808 |
|
809 impl<'a> OnCylinder<'a> { |
|
810 /// Return the cylindrical coordinates of this point |
|
811 pub fn cyl_coords(&self) -> CylCoords { |
|
812 self.cylinder.cyl_coords(&self.point) |
|
813 } |
|
814 } |
|
815 |
|
816 impl<'a> EmbeddedManifoldPoint for OnCylinder<'a> { |
|
817 type EmbeddedCoords = Loc<f64, 3>; |
|
818 |
|
819 /// Get embedded 3D coordinates |
|
820 fn embedded_coords(&self) -> Loc<f64, 3> { |
|
821 self.cyl_coords().to_cartesian() |
|
822 } |
|
823 } |
|
824 |
|
825 impl<'a> ManifoldPoint for OnCylinder<'a> { |
|
826 type Tangent = Tangent; |
|
827 |
|
828 fn exp(self, tangent : &Self::Tangent) -> Self { |
|
829 let cylinder = self.cylinder; |
|
830 let point = cylinder.exp(&self.point, tangent); |
|
831 OnCylinder { cylinder, point } |
|
832 } |
|
833 |
|
834 fn log(&self, other : &Self) -> Self::Tangent { |
|
835 assert!(self.cylinder == other.cylinder); |
|
836 self.cylinder.log_dist(&self.point, &other.point).0 |
|
837 } |
|
838 |
|
839 fn dist_to(&self, other : &Self) -> f64 { |
|
840 assert!(self.cylinder == other.cylinder); |
|
841 self.cylinder.log_dist(&self.point, &other.point).1 |
|
842 } |
|
843 |
|
844 fn tangent_origin(&self) -> Self::Tangent { |
|
845 Loc([0.0, 0.0]) |
|
846 } |
|
847 } |
|
848 |
|
849 #[cfg(test)] |
|
850 mod tests { |
|
851 use super::*; |
|
852 |
|
853 static CYL : Cylinder = Cylinder { |
|
854 radius : 1.0, |
|
855 height : 1.0, |
|
856 config : CylinderConfig { newton_iters : 20 }, |
|
857 }; |
|
858 |
|
859 fn check_distance(distance : f64, expected : f64) { |
|
860 let tol = 1e-10; |
|
861 assert!( |
|
862 (distance-expected).abs() < tol, |
|
863 "distance = {distance}, expected = {expected}" |
|
864 ); |
|
865 } |
|
866 |
|
867 // fn check_distance_less(distance : f64, expected : f64) { |
|
868 // let tol = 1e-10; |
|
869 // assert!( |
|
870 // distance < expected + tol, |
|
871 // "distance = {distance}, expected = {expected}" |
|
872 // ); |
|
873 // } |
|
874 |
|
875 #[test] |
|
876 fn intra_cap_log_dist() { |
|
877 let π = f64::PI; |
|
878 let p1 = CYL.on_top(0.0, 0.5); |
|
879 let p2 = CYL.on_top(π, 0.5); |
|
880 let p3 = CYL.on_top(π/2.0, 0.5); |
|
881 |
|
882 check_distance(p1.dist_to(&p2), 1.0); |
|
883 check_distance(p2.dist_to(&p3), 0.5_f64.sqrt()); |
|
884 check_distance(p3.dist_to(&p1), 0.5_f64.sqrt()); |
|
885 } |
|
886 |
|
887 #[test] |
|
888 fn intra_side_log_dist() { |
|
889 let π = f64::PI; |
|
890 let p1 = CYL.on_side(0.0, 0.0); |
|
891 let p2 = CYL.on_side(0.0, 0.4); |
|
892 let p3 = CYL.on_side(π/2.0, 0.0); |
|
893 |
|
894 check_distance(p1.dist_to(&p2), 0.4); |
|
895 check_distance(p1.dist_to(&p3), π/2.0*CYL.radius); |
|
896 } |
|
897 |
|
898 #[test] |
|
899 fn intra_side_over_cap_log_dist() { |
|
900 let π = f64::PI; |
|
901 let off = 0.05; |
|
902 let z = CYL.top_z() - off; |
|
903 let p1 = CYL.on_side(0.0, z); |
|
904 let p2 = CYL.on_side(π, z); |
|
905 |
|
906 check_distance(p1.dist_to(&p2), 2.0 * (CYL.radius + off)); |
|
907 } |
|
908 |
|
909 #[test] |
|
910 fn top_bottom_log_dist() { |
|
911 let π = f64::PI; |
|
912 let p1 = CYL.on_top(0.0, 0.0); |
|
913 let p2 = CYL.on_bottom(0.0, 0.0); |
|
914 |
|
915 check_distance(p1.dist_to(&p2), 2.0 * CYL.radius + CYL.height); |
|
916 |
|
917 let p1 = CYL.on_top(0.0, CYL.radius / 2.0); |
|
918 let p2 = CYL.on_bottom(0.0, CYL.radius / 2.0); |
|
919 let p3 = CYL.on_bottom(π, CYL.radius / 2.0); |
|
920 |
|
921 check_distance(p1.dist_to(&p2), CYL.radius + CYL.height); |
|
922 check_distance(p1.dist_to(&p3), 2.0 * CYL.radius + CYL.height); |
|
923 } |
|
924 |
|
925 #[test] |
|
926 fn top_side_log_dist() { |
|
927 let p1 = CYL.on_top(0.0, 0.0); |
|
928 let p2 = CYL.on_side(0.0, 0.0); |
|
929 |
|
930 check_distance(p1.dist_to(&p2), CYL.radius + CYL.height / 2.0); |
|
931 } |
|
932 } |