| |
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 } |