Skip to content

Commit

Permalink
split into modules
Browse files Browse the repository at this point in the history
  • Loading branch information
thehappycheese committed Aug 5, 2023
1 parent 0aa4ed2 commit fe2a811
Show file tree
Hide file tree
Showing 7 changed files with 755 additions and 688 deletions.
55 changes: 55 additions & 0 deletions geo/src/algorithm/line_split/line_split_result.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#[rustfmt::skip]
#[derive(PartialEq, Debug)]
pub enum LineSplitResult<T> {
First (T ),
Second ( T),
FirstSecond (T, T),
}

#[rustfmt::skip]
impl<T> LineSplitResult<T>{
pub fn first(&self) -> Option<&T> {
match self {
Self::First (x ) => Some(x),
Self::Second ( _) => None,
Self::FirstSecond(x, _) => Some(x),
}
}
pub fn into_first(self) -> Option<T> {
match self {
Self::First (x ) => Some(x),
Self::Second ( _) => None,
Self::FirstSecond(x, _) => Some(x),
}
}
pub fn second(&self) -> Option<&T> {
match self {
Self::First (_ ) => None,
Self::Second ( x) => Some(x),
Self::FirstSecond(_, x) => Some(x),
}
}
pub fn into_second(self) -> Option<T> {
match self {
Self::First (_ ) => None,
Self::Second ( x) => Some(x),
Self::FirstSecond(_, x) => Some(x),
}
}

pub fn as_tuple(&self) -> (Option<&T>, Option<&T>) {
match self {
Self::First (a ) => (Some(a), None ),
Self::Second ( b) => (None , Some(b)),
Self::FirstSecond(a, b) => (Some(a), Some(b)),
}
}

pub fn into_tuple(self) -> (Option<T>, Option<T>) {
match self {
Self::First (a ) => (Some(a), None ),
Self::Second ( b) => (None , Some(b)),
Self::FirstSecond(a, b) => (Some(a), Some(b)),
}
}
}
68 changes: 68 additions & 0 deletions geo/src/algorithm/line_split/line_split_trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use geo_types::CoordFloat;

use super::{LineSplitResult, LineSplitTwiceResult};


pub trait LineSplit<Scalar> where Self:Sized, Scalar: CoordFloat {

/// Note on choice of return type:
///
/// You may wonder why this does not return `Option<(Option<Line>, Option<Line>)>`?
/// It is because then the return type causes uncertainty; The user may expect to possibly
/// receive `Some((None, None))` which is never possible, this would lead to clutter in match
/// statements.
///
/// To make it easier to 'just get the first' or 'just get the second' you can use
/// `LineSplitResult::first()` and `LineSplitResult::second()` which return `Option<T>`
///
///
fn line_split(&self, fraction: Scalar) -> Option<LineSplitResult<Self>>;

/// Note on choice of return type:
///
/// You may wonder why this does not return `Option<(Option<Line>,Option<Line>,Option<Line>)>`?
/// It is because then the return type causes uncertainty; The user may expect to possibly
/// receive `Some((None, None, None))` which is never possible.
/// The user would have a hard time writing an exhaustive match statement.
///
/// To make it easier to 'just get the second' the `LineSplitResult` has a function called `first()->Option<T>`
///
// TODO: I only want to skip formatting the match block, but because attributes on expressions
// are experimental we are forced to put it on the function to avoid an error message.
#[rustfmt::skip]
fn line_split_twice(
&self,
start_fraction: Scalar,
end_fraction: Scalar,
) -> Option<LineSplitTwiceResult<Self>> {
// import enum variants
use LineSplitTwiceResult::*;

// forgive the user for passing in the wrong order
// because it simplifies the interface of the output type
let (start_fraction, end_fraction) = if start_fraction > end_fraction {
(end_fraction, start_fraction)
} else {
(start_fraction, end_fraction)
};
// TODO: check for nan
let second_fraction = (end_fraction - start_fraction) / (Scalar::one() - start_fraction);

match self.line_split(start_fraction) {
Some(LineSplitResult::FirstSecond(line1, line2)) => match line2.line_split(second_fraction) {
Some(LineSplitResult::FirstSecond(line2, line3)) => Some(FirstSecondThird(line1, line2, line3)),
Some(LineSplitResult::First (line2 )) => Some(FirstSecond (line1, line2 )),
Some(LineSplitResult::Second ( line3)) => Some(FirstThird (line1, line3)),
None => None,
},
Some(LineSplitResult::First (line1)) => Some(First(line1)),
Some(LineSplitResult::Second(line2)) => match line2.line_split(second_fraction) {
Some(LineSplitResult::FirstSecond(line2, line3)) => Some(SecondThird ( line2, line3)),
Some(LineSplitResult::First (line2 )) => Some(Second ( line2 )),
Some(LineSplitResult::Second ( line3)) => Some(Third ( line3)),
None => None,
},
None => None,
}
}
}
167 changes: 167 additions & 0 deletions geo/src/algorithm/line_split/line_split_trait_impl_for_line.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use geo_types::{Line, CoordFloat};

use super::{
LineSplitResult,
LineSplit,
};

impl<Scalar> LineSplit<Scalar> for Line<Scalar> where Scalar: CoordFloat {
fn line_split(&self, fraction: Scalar) -> Option<LineSplitResult<Self>> {
if fraction.is_nan() {
return None
}
if fraction <= Scalar::zero() {
Some(LineSplitResult::Second(self.clone()))
} else if fraction >= Scalar::one() {
Some(LineSplitResult::First(self.clone()))
} else {
let new_midpoint = self.start + self.delta() * fraction;
Some(LineSplitResult::FirstSecond(
Line::new(self.start, new_midpoint),
Line::new(new_midpoint, self.end),
))
}
}
}

#[cfg(test)]
mod test{
use geo_types::coord;
use super::super::LineSplitTwiceResult;
use super::*;

// =============================================================================================
// Line::line_split()
// =============================================================================================

#[test]
fn test_line_split_first_second() {
// simple x-axis aligned check
let line = Line::new(
coord!{x: 0.0_f32, y:0.0_f32},
coord!{x:10.0_f32, y:0.0_f32},
);
let result = line.line_split(0.6);
assert_eq!(result, Some(LineSplitResult::FirstSecond(
Line::new(
coord!{x: 0.0_f32, y:0.0_f32},
coord!{x: 6.0_f32, y:0.0_f32},
),
Line::new(
coord!{x: 6.0_f32, y:0.0_f32},
coord!{x:10.0_f32, y:0.0_f32},
)
)));

// simple y-axis aligned check
let line = Line::new(
coord!{x:0.0_f32, y: 0.0_f32},
coord!{x:0.0_f32, y:10.0_f32},
);
let result = line.line_split(0.3);
assert_eq!(result, Some(LineSplitResult::FirstSecond(
Line::new(
coord!{x:0.0_f32, y:0.0_f32},
coord!{x:0.0_f32, y:3.0_f32},
),
Line::new(
coord!{x:0.0_f32, y:3.0_f32},
coord!{x:0.0_f32, y:10.0_f32},
)
)));

// non_trivial check
let line = Line::new(
coord!{x: 1.0_f32, y: 1.0_f32},
coord!{x:10.0_f32, y:-10.0_f32},
);
let split_point = line.start + line.delta() * 0.7;
let result = line.line_split(0.7);
assert_eq!(result, Some(LineSplitResult::FirstSecond(
Line::new(
line.start,
split_point,
),
Line::new(
split_point,
line.end,
)
)));
}

#[test]
fn test_line_split_first() {
// test one
let line = Line::new(
coord!{x: 0.0_f32, y:0.0_f32},
coord!{x:10.0_f32, y:0.0_f32},
);
let result = line.line_split(1.0);
assert_eq!(result, Some(LineSplitResult::First(line)));

// Test numbers larger than one
let line = Line::new(
coord!{x: 0.0_f32, y:0.0_f32},
coord!{x:10.0_f32, y:0.0_f32},
);
let result = line.line_split(2.0);
assert_eq!(result, Some(LineSplitResult::First(line)));
}
#[test]
fn test_line_split_second() {
// test zero
let line = Line::new(
coord!{x: 0.0_f32, y:0.0_f32},
coord!{x:10.0_f32, y:0.0_f32},
);
let result = line.line_split(0.0);
assert_eq!(result, Some(LineSplitResult::Second(line)));

// Test negative numbers
let line = Line::new(
coord!{x: 0.0_f32, y:0.0_f32},
coord!{x:10.0_f32, y:0.0_f32},
);
let result = line.line_split(-2.0);
assert_eq!(result, Some(LineSplitResult::Second(line)));
}


// =============================================================================================
// Line::line_split_twice()
// =============================================================================================

macro_rules! test_line_split_twice_helper{
($a:expr, $b:expr, $enum_variant:ident, $(($x1:expr, $x2:expr)),*)=>{{
let line = Line::new(
coord!{x: 0.0_f32, y:0.0_f32},
coord!{x:10.0_f32, y:0.0_f32},
);
let result = line.line_split_twice($a, $b).unwrap();
// println!("{result:?}");
assert_eq!(
result,
LineSplitTwiceResult::$enum_variant(
$(
Line::new(
coord!{x: $x1, y:0.0_f32},
coord!{x: $x2, y:0.0_f32},
),
)*
)
);
}}
}

#[test]
fn test_line_split_twice(){
test_line_split_twice_helper!(0.6, 0.8, FirstSecondThird, (0.0, 6.0), (6.0, 8.0), (8.0, 10.0));
test_line_split_twice_helper!(0.6, 1.0, FirstSecond, (0.0, 6.0), (6.0, 10.0));
test_line_split_twice_helper!(0.6, 0.6, FirstThird, (0.0, 6.0), (6.0, 10.0));
test_line_split_twice_helper!(0.0, 0.6, SecondThird, (0.0, 6.0), (6.0, 10.0));
test_line_split_twice_helper!(1.0, 1.0, First, (0.0, 10.0));
test_line_split_twice_helper!(0.0, 1.0, Second, (0.0, 10.0));
test_line_split_twice_helper!(0.0, 0.0, Third, (0.0, 10.0));
}

}
Loading

0 comments on commit fe2a811

Please sign in to comment.