Skip to content

Commit

Permalink
LineSegmentIntersectionType as enum for clarity
Browse files Browse the repository at this point in the history
  • Loading branch information
thehappycheese committed Nov 18, 2022
1 parent effc9e7 commit 6f03cd0
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 44 deletions.
75 changes: 63 additions & 12 deletions geo/src/algorithm/offset/line_intersection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,46 @@ use super::cross_product::cross_product_2d;
use crate::{CoordFloat, CoordNum};
use geo_types::Coord;

// No nested enums :( Goes into the enum below
#[derive(PartialEq, Eq, Debug)]
pub(super) enum FalseIntersectionPointType{
/// The intersection point is 'false' or 'virtual': it lies on the infinite
/// ray defined by the line segment, but before the start of the line segment.
///
/// Abbreviated to `NFIP` in original paper (Negative)
BeforeStart,
/// The intersection point is 'false' or 'virtual': it lies on the infinite
/// ray defined by the line segment, but after the end of the line segment.
///
/// Abbreviated to `PFIP` in original paper (Positive)
AfterEnd
}

/// Used to encode the relationship between a segment (e.g. between [Coord] `a` and `b`)
/// and an intersection point ([Coord] `p`)
#[derive(PartialEq, Eq, Debug)]
pub(super) enum LineSegmentIntersectionType {
/// The intersection point lies between the start and end of the line segment.
///
/// Abbreviated to `TIP` in original paper
TrueIntersectionPoint,
/// The intersection point is 'false' or 'virtual': it lies on the infinite
/// ray defined by the line segment, but not between the start and end points
///
/// Abbreviated to `FIP` in original paper
FalseIntersectionPoint(FalseIntersectionPointType)
}

use LineSegmentIntersectionType::{FalseIntersectionPoint, TrueIntersectionPoint};
use FalseIntersectionPointType::{BeforeStart, AfterEnd};

/// Struct to contain the result for [line_intersection_with_parameter]
pub(super) struct LineIntersectionWithParameterResult<T>
where
T: CoordNum,
{
pub t_ab: T,
pub t_cd: T,
pub ab: LineSegmentIntersectionType,
pub cd: LineSegmentIntersectionType,
pub intersection: Coord<T>,
}

Expand Down Expand Up @@ -135,14 +168,32 @@ where
let t_ab = cross_product_2d(ac, cd) / ab_cross_cd;
let t_cd = -cross_product_2d(ab, ac) / ab_cross_cd;
let intersection = *a + ab * t_ab;

let zero = num_traits::zero::<T>();
let one = num_traits::one::<T>();

Some(LineIntersectionWithParameterResult {
t_ab,
t_cd,
ab: if zero <= t_ab && t_ab <= one {
TrueIntersectionPoint
}else if t_ab < zero {
FalseIntersectionPoint(BeforeStart)
}else{
FalseIntersectionPoint(AfterEnd)
},
cd: if zero <= t_cd && t_cd <= one {
TrueIntersectionPoint
}else if t_cd < zero {
FalseIntersectionPoint(BeforeStart)
}else{
FalseIntersectionPoint(AfterEnd)
},
intersection,
})
}
}

// TODO: add more relationship tests;

#[cfg(test)]
mod test {
use super::*;
Expand All @@ -154,13 +205,13 @@ mod test {
let c = Coord { x: 0f64, y: 1f64 };
let d = Coord { x: 1f64, y: 0f64 };
if let Some(LineIntersectionWithParameterResult {
t_ab,
t_cd,
ab: t_ab,
cd: t_cd,
intersection,
}) = line_intersection_with_parameter(&a, &b, &c, &d)
{
assert_eq!(t_ab, 0.25f64);
assert_eq!(t_cd, 0.5f64);
assert_eq!(t_ab, TrueIntersectionPoint);
assert_eq!(t_cd, TrueIntersectionPoint);
assert_eq!(
intersection,
Coord {
Expand All @@ -180,13 +231,13 @@ mod test {
let c = Coord { x: 7f64, y: 7f64 };
let d = Coord { x: 10f64, y: 9f64 };
if let Some(LineIntersectionWithParameterResult {
t_ab,
t_cd,
ab: t_ab,
cd: t_cd,
intersection,
}) = line_intersection_with_parameter(&a, &b, &c, &d)
{
assert_eq!(t_ab, 0.25f64);
assert_eq!(t_cd, 0.5f64);
assert_eq!(t_ab, TrueIntersectionPoint);
assert_eq!(t_cd, TrueIntersectionPoint);
assert_eq!(
intersection,
Coord {
Expand Down
127 changes: 99 additions & 28 deletions geo/src/algorithm/offset/offset_trait.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
use super::line_intersection::{line_intersection_with_parameter, LineIntersectionWithParameterResult};
use super::line_intersection::FalseIntersectionPointType::AfterEnd;
use super::line_intersection::LineSegmentIntersectionType::{
FalseIntersectionPoint, TrueIntersectionPoint,
};
use super::line_intersection::{
line_intersection_with_parameter, LineIntersectionWithParameterResult,
};
use super::slice_itertools::pairwise;

use crate::{
Coord,
CoordFloat,
// Kernel,
// Orientation,
Line,
LineString,
MultiLineString,
Polygon
// Polygon,
};
use geo_types::Coord;

/// # Offset Trait
///
Expand Down Expand Up @@ -80,7 +86,6 @@ where
}
}


impl<T> Offset<T> for LineString<T>
where
T: CoordFloat,
Expand All @@ -105,32 +110,25 @@ where
result.extend(pairwise(&offset_segments[..]).flat_map(
|(Line { start: a, end: b }, Line { start: c, end: d })| {
match line_intersection_with_parameter(a, b, c, d) {
None => vec![*b], // colinear
None => {
// TODO: this is the colinear case;
// we are potentially creating a redundant point in the
// output here. Colinear segments should maybe get
// removed before or after this algorithm
vec![*b]
},
Some(LineIntersectionWithParameterResult {
t_ab,
t_cd,
ab,
cd,
intersection,
}) => {
let zero = num_traits::zero::<T>();
let one = num_traits::one::<T>();

let tip_ab = zero <= t_ab && t_ab <= one;
let fip_ab = !tip_ab;
let pfip_ab = fip_ab && t_ab > zero;

let tip_cd = zero <= t_cd && t_cd <= one;
let fip_cd = !tip_cd;

if tip_ab && tip_cd {
// TODO: test for mitre limit
vec![intersection]
} else if fip_ab && fip_cd && pfip_ab {
// TODO: test for mitre limit
}) => match (ab, cd) {
(TrueIntersectionPoint, TrueIntersectionPoint) => vec![intersection],
(FalseIntersectionPoint(AfterEnd), FalseIntersectionPoint(_)) => {
// TODO: Mitre limit logic goes here
vec![intersection]
} else {
vec![*b, *c]
}
}
_ => vec![*b, *c],
},
}
},
));
Expand All @@ -150,7 +148,6 @@ where
}
}


// impl<T> Offset<T> for Polygon<T>
// where
// T: CoordFloat,
Expand All @@ -167,7 +164,9 @@ where
#[cfg(test)]
mod test {

use crate::{line_string, Coord, Line, MultiLineString, Offset};
use crate::{line_string, Coord, Line, LineString, MultiLineString, Offset};

use super::super::slice_itertools::pairwise;

#[test]
fn test_offset_line() {
Expand Down Expand Up @@ -231,4 +230,76 @@ mod test {
let output_actual = input.offset(1f64);
assert_eq!(output_actual, output_expected);
}

/// Function to draw test output to geogebra.org for inspection
///
/// Paste the output into the javascript console on geogebra.org to
/// visualize the result
///
/// The following snippet will extract existing (points and vectors) from geogebra:
///
/// ```javascript
/// console.log([
/// "line_string![",
/// ...ggbApplet.getAllObjectNames().filter(item=>item==item.toUpperCase()).map(name=>` Coord{x:${ggbApplet.getXcoord(name)}f64, y:${ggbApplet.getYcoord(name)}f64},`),
/// "]",
/// ].join("\n"))
/// ```
///
fn print_geogebra_draw_commands(input: &LineString, prefix: &str, r: u8, g: u8, b: u8) {
let prefix_upper = prefix.to_uppercase();
let prefix_lower = prefix.to_lowercase();
input
.coords()
.enumerate()
.for_each(|(index, Coord { x, y })| {
println!(r#"ggbApplet.evalCommand("{prefix_upper}_{{{index}}} = ({x:?},{y:?})")"#)
});
let x: Vec<_> = input.coords().enumerate().collect();
pairwise(&x[..]).for_each(|((a, _), (b, _))|{
println!(r#"ggbApplet.evalCommand("{prefix_lower}_{{{a},{b}}} = Vector({prefix_upper}_{a},{prefix_upper}_{b})")"#);
()
});
let (dim_r, dim_g, dim_b) = (r / 2, g / 2, b / 2);
println!(
r#"ggbApplet.getAllObjectNames().filter(item=>item.startsWith("{prefix_upper}_")).forEach(item=>ggbApplet.setColor(item,{r},{g},{b}))"#
);
println!(
r#"ggbApplet.getAllObjectNames().filter(item=>item.startsWith("{prefix_lower}_")).forEach(item=>ggbApplet.setColor(item,{dim_r},{dim_g},{dim_b}))"#
);
}

#[test]
fn test_offset_line_string_all_branch() {
// attempts to hit all branches of the line extension / cropping test
let input = line_string![
Coord { x: 3f64, y: 2f64 },
Coord {
x: 2.740821628422733f64,
y: 2.2582363315313816f64
},
Coord {
x: 5.279039119779313f64,
y: 2.516847170273373f64
},
Coord { x: 5f64, y: 2f64 },
Coord {
x: 3.2388869474813826f64,
y: 4.489952088082639f64
},
Coord { x: 3f64, y: 4f64 },
Coord { x: 4f64, y: 4f64 },
Coord { x: 5.5f64, y: 4f64 },
Coord {
x: 5.240726402928647f64,
y: 4.250497607765981f64
},
];
print_geogebra_draw_commands(&input, "I", 90, 90, 90);
print_geogebra_draw_commands(&input.offset(-0.1f64), "L", 0, 200, 0);
print_geogebra_draw_commands(&input.offset(0.1f64), "R", 200, 0, 0);

// TODO: test always fails
assert!(false);
}
}
10 changes: 6 additions & 4 deletions rfcs/2022-11-11-offset.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ Priority for implementing the trait is as follows:

- [X] `Line<impl CoordFloat>`
- [X] `LineString<impl CoordFloat>`
- [ ] `MultiLineString<impl CoordFloat>`
- [X] `MultiLineString<impl CoordFloat>`
- [ ] `Triangle<impl CoordFloat>`
- [ ] `Rectangle<impl CoordFloat>`
- [ ] And if some of the limitations below are addressed
`Polygon`, `MultiPolygon`
- [ ] If some of the limitations discussed below can be addressed
- [ ] `Polygon`
- [ ] `MultiPolygon`
- [ ] `Geometry` & `GeometryCollection`

## Limitations

Expand All @@ -35,7 +37,7 @@ and potentially a better algorithm is needed:
will produce an elbow at infinity
- [ ] Only local cropping where the output is self-intersecting. Non-adjacent
line segments in the output may be self-intersecting.
- [ ] Does not handle closed shapes
- [ ] Does not handle closed shapes yet


## Algorithm
Expand Down

0 comments on commit 6f03cd0

Please sign in to comment.