diff --git a/src/geom/region/primitives.rs b/src/geom/region/primitives.rs index 3c3d5ca..277339f 100644 --- a/src/geom/region/primitives.rs +++ b/src/geom/region/primitives.rs @@ -157,6 +157,36 @@ impl Region for Cube { } } +/// a Spiral traces out a circle on the xy plane as z increases. +#[derive(Copy, Clone, Serialize, Deserialize)] +pub struct Spiral { + /// radius of the spiral + major: f32, + /// effective "width" of the spiral. + /// any point within this distance from the 1-dimensional spiral is considered to be in the + /// region. + minor: f32, + /// determines how stretched the spiral is in z-space. + /// the xy plane at z=0 looks identical again at t=period. + period: f32, +} + +impl Spiral { + pub fn new(major: f32, minor: f32, period: f32) -> Self { + Self { major, minor, period } + } +} + +#[typetag::serde] +impl Region for Spiral { + fn contains(&self, p: Meters) -> bool { + let revs = p.z() / self.period; + let expected_angle = (revs % 1.0) * std::f32::consts::PI * 2.0; + let expected = Meters::new(self.major * expected_angle.cos(), self.major * expected_angle.sin(), p.z()); + p.distance_sq(*expected) < self.minor * self.minor + } +} + #[cfg(test)] mod test { use super::*; @@ -286,6 +316,62 @@ mod test { assert!(tor.contains(Meters::new(1.0, 2.0, 12.0))); assert!(!tor.contains(Meters::new(1.0, 2.0, 13.0))); } + + #[test] + fn test_spiral_doesnt_contain_zero() { + let spiral = Spiral::new(10.0, 1.0, 4.0); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 0.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 1.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 2.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 3.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 4.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 5.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 6.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 7.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 8.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 9.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 10.0))); + assert!(!spiral.contains(Meters::new(0.0, 0.0, 11.0))); + } + + #[test] + fn test_spiral_exactly_on_course() { + let spiral = Spiral::new(10.0, 1.0, 4.0); + assert!(spiral.contains(Meters::new(10.0, 0.0, 0.0))); + assert!(spiral.contains(Meters::new(0.0, 10.0, 1.0))); + assert!(spiral.contains(Meters::new(-10.0, 0.0, 2.0))); + assert!(spiral.contains(Meters::new(0.0, -10.0, 3.0))); + assert!(spiral.contains(Meters::new(10.0, 0.0, 4.0))); + assert!(spiral.contains(Meters::new(0.0, 10.0, 5.0))); + } + + #[test] + fn test_spiral_exactly_on_course_negative() { + let spiral = Spiral::new(10.0, 1.0, 4.0); + assert!(spiral.contains(Meters::new(0.0, -10.0, -1.0))); + assert!(spiral.contains(Meters::new(-10.0, 0.0, -2.0))); + assert!(spiral.contains(Meters::new(0.0, 10.0, -3.0))); + assert!(spiral.contains(Meters::new(10.0, 0.0, -4.0))); + assert!(spiral.contains(Meters::new(0.0, -10.0, -5.0))); + } + + #[test] + fn test_spiral_within_minor_radius() { + let spiral = Spiral::new(10.0, 1.0, 4.0); + assert!(spiral.contains(Meters::new(10.5, 0.0, 0.0))); + assert!(spiral.contains(Meters::new(10.5, -0.5, 0.0))); + assert!(spiral.contains(Meters::new(9.1, 0.0, 0.0))); + assert!(!spiral.contains(Meters::new(8.9, 0.0, 0.0))); + assert!(!spiral.contains(Meters::new(9.1, 0.9, 0.0))); + + assert!(spiral.contains(Meters::new(0.5, 10.5, 1.0))); + assert!(spiral.contains(Meters::new(-0.9, 10.0, 1.0))); + assert!(!spiral.contains(Meters::new(-0.9, 10.9, 1.0))); + + assert!(spiral.contains(Meters::new(-10.9, 0.0, 2.0))); + assert!(spiral.contains(Meters::new(-10.0, 0.6, 2.0))); + assert!(!spiral.contains(Meters::new(-10.8, 0.8, 2.0))); + } }