making an infinite line type, to be less confusing

This commit is contained in:
Dustin Carlino 2019-01-28 18:09:16 -08:00
parent 191b87f148
commit 2e982ec827
7 changed files with 97 additions and 82 deletions

View File

@ -18,6 +18,7 @@
## Stability
- test results per git commit
- https://github.com/spotify/git-test
- way to view later
- also could be benchmarks; just arbitrary data over time
- also screenshots
@ -38,6 +39,8 @@
### Current major geometry problems
- return more info with intersection stuff.
- line intersection code is giving completely silly results for 23rd
- Line::new(
Pt2D::new(2220.510790392476, 17.372151672558502),
@ -46,5 +49,7 @@
Pt2D::new(2220.5127307848657, 16.63215421656464),
Pt2D::new(2220.514671177255, 15.892156760571009),
) intersect, but first line doesn't contain_pt(Pt2D(2220.495346552271, 22.98459165963943))
- use https://crates.io/crates/line_intersection or ncollide for some of this math
- actually, copy in code from line_intersection and use new geo... or maybe dont use geo at all
- bad shifted polylines on 45th st in 23rd map

View File

@ -134,7 +134,8 @@ fn calculate_corners(i: &Intersection, map: &Map) -> Vec<Polygon> {
let dst_line = l2.first_line().shift_left(LANE_THICKNESS / 2.0);
let pt_maybe_in_intersection = src_line
.intersection_two_infinite_lines(&dst_line)
.infinite()
.intersection(&dst_line.infinite())
.expect("SharedSidewalkCorner between parallel sidewalks");
// Now find all of the points on the intersection polygon between the two sidewalks.

View File

@ -9,7 +9,7 @@ mod pt;
pub use crate::angle::Angle;
pub use crate::circle::Circle;
pub use crate::gps::{GPSBounds, LonLat};
pub use crate::line::Line;
pub use crate::line::{InfiniteLine, Line};
pub use crate::polygon::{Polygon, Triangle};
pub use crate::polyline::PolyLine;
pub use crate::pt::{Bounds, HashablePt2D, Pt2D};
@ -21,26 +21,3 @@ pub const EPSILON_DIST: si::Meter<f64> = si::Meter {
value_unsafe: 0.01,
_marker: marker::PhantomData,
};
// NOT segment. Fails for parallel lines.
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
pub fn line_intersection(l1: &Line, l2: &Line) -> Option<Pt2D> {
let x1 = l1.pt1().x();
let y1 = l1.pt1().y();
let x2 = l1.pt2().x();
let y2 = l1.pt2().y();
let x3 = l2.pt1().x();
let y3 = l2.pt1().y();
let x4 = l2.pt2().x();
let y4 = l2.pt2().y();
let numer_x = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
let numer_y = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if denom == 0.0 {
None
} else {
Some(Pt2D::new(numer_x / denom, numer_y / denom))
}
}

View File

@ -1,9 +1,9 @@
use crate::{line_intersection, Angle, PolyLine, Polygon, Pt2D, EPSILON_DIST};
use crate::{Angle, PolyLine, Polygon, Pt2D, EPSILON_DIST};
use dimensioned::si;
use serde_derive::{Deserialize, Serialize};
use std::fmt;
// Segment, technically
// Segment, technically. Should rename.
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Line(Pt2D, Pt2D);
@ -13,6 +13,10 @@ impl Line {
Line(pt1, pt2)
}
pub fn infinite(&self) -> InfiniteLine {
InfiniteLine(self.0, self.1)
}
// TODO we call these frequently here; unnecessary copies?
pub fn pt1(&self) -> Pt2D {
self.0
@ -72,32 +76,38 @@ impl Line {
* si::M
}
// TODO Also return the distance along self
pub fn intersection(&self, other: &Line) -> Option<Pt2D> {
// TODO shoddy way of implementing this
// TODO doesn't handle nearly parallel lines
if !self.intersects(other) {
None
// From http://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
if is_counter_clockwise(self.pt1(), other.pt1(), other.pt2())
== is_counter_clockwise(self.pt2(), other.pt1(), other.pt2())
|| is_counter_clockwise(self.pt1(), self.pt2(), other.pt1())
== is_counter_clockwise(self.pt1(), self.pt2(), other.pt2())
{
return None;
}
let hit = self.infinite().intersection(&other.infinite())?;
if self.contains_pt(hit) {
Some(hit)
} else {
if let Some(hit) = line_intersection(self, other) {
if self.contains_pt(hit) {
Some(hit)
} else {
// TODO This shouldn't be possible! :D
println!(
"{} and {} intersect, but first line doesn't contain_pt({})",
self, other, hit
);
None
}
} else {
None
}
// TODO This shouldn't be possible! :D
println!(
"{} and {} intersect, but first line doesn't contain_pt({})",
self, other, hit
);
None
}
}
// TODO separate LineSegment and InfiniteLine types
pub fn intersection_two_infinite_lines(&self, other: &Line) -> Option<Pt2D> {
line_intersection(self, other)
// TODO Also return the distance along self
pub fn intersection_infinite(&self, other: &InfiniteLine) -> Option<Pt2D> {
let hit = self.infinite().intersection(other)?;
if self.contains_pt(hit) {
Some(hit)
} else {
None
}
}
pub fn shift_right(&self, width: f64) -> Line {
@ -130,14 +140,6 @@ impl Line {
Line(self.pt2(), self.pt1())
}
pub fn intersects(&self, other: &Line) -> bool {
// From http://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
is_counter_clockwise(self.pt1(), other.pt1(), other.pt2())
!= is_counter_clockwise(self.pt2(), other.pt1(), other.pt2())
&& is_counter_clockwise(self.pt1(), self.pt2(), other.pt1())
!= is_counter_clockwise(self.pt1(), self.pt2(), other.pt2())
}
pub fn angle(&self) -> Angle {
self.pt1().angle_to(self.pt2())
}
@ -238,3 +240,31 @@ impl fmt::Display for Line {
fn is_counter_clockwise(pt1: Pt2D, pt2: Pt2D, pt3: Pt2D) -> bool {
(pt3.y() - pt1.y()) * (pt2.x() - pt1.x()) > (pt2.y() - pt1.y()) * (pt3.x() - pt1.x())
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct InfiniteLine(Pt2D, Pt2D);
impl InfiniteLine {
// Fails for parallel lines.
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
pub fn intersection(&self, other: &InfiniteLine) -> Option<Pt2D> {
let x1 = self.0.x();
let y1 = self.0.y();
let x2 = self.1.x();
let y2 = self.1.y();
let x3 = other.0.x();
let y3 = other.0.y();
let x4 = other.1.x();
let y4 = other.1.y();
let numer_x = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
let numer_y = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if denom == 0.0 {
None
} else {
Some(Pt2D::new(numer_x / denom, numer_y / denom))
}
}
}

View File

@ -1,4 +1,4 @@
use crate::{line_intersection, Angle, Bounds, HashablePt2D, Line, Polygon, Pt2D, EPSILON_DIST};
use crate::{Angle, Bounds, HashablePt2D, InfiniteLine, Line, Polygon, Pt2D, EPSILON_DIST};
use dimensioned::si;
use ordered_float::NotNan;
use serde_derive::{Deserialize, Serialize};
@ -223,7 +223,10 @@ impl PolyLine {
let l2 = Line::new(pt2_raw, pt3_raw).shift_either_direction(width);
// When the lines are perfectly parallel, it means pt2_shift_1st == pt2_shift_2nd and the
// original geometry is redundant.
let pt2_shift = line_intersection(&l1, &l2).unwrap_or_else(|| l1.pt2());
let pt2_shift = l1
.infinite()
.intersection(&l2.infinite())
.unwrap_or_else(|| l1.pt2());
if pt3_idx == 2 {
result.push(l1.pt1());
@ -318,13 +321,11 @@ impl PolyLine {
None
}
pub fn intersection_infinite_line(&self, other: Line) -> Option<Pt2D> {
// TODO There must be better ways to do this. :)
// TODO Also distance along
pub fn intersection_infinite(&self, other: &InfiniteLine) -> Option<Pt2D> {
for l in self.lines() {
if let Some(hit) = line_intersection(&l, &other) {
if l.contains_pt(hit) {
return Some(hit);
}
if let Some(hit) = l.intersection_infinite(other) {
return Some(hit);
}
}
None

View File

@ -101,10 +101,11 @@ fn generalized_trim_back(
if let Some((hit, angle)) = pl1.intersection(pl2) {
// Find where the perpendicular hits the original road line
let perp = Line::new(hit, hit.project_away(1.0, angle.rotate_degs(90.0)));
let perp =
Line::new(hit, hit.project_away(1.0, angle.rotate_degs(90.0))).infinite();
// How could something perpendicular to a shifted polyline never hit the original
// polyline?
let trim_to = road_center.intersection_infinite_line(perp).unwrap();
let trim_to = road_center.intersection_infinite(&perp).unwrap();
let trimmed = road_center.get_slice_ending_at(trim_to).unwrap();
if trimmed.length() < shortest_center.length() {
shortest_center = trimmed;

View File

@ -1,5 +1,5 @@
use crate::runner::TestRunner;
use geom::{line_intersection, Line, PolyLine, Pt2D};
use geom::{Line, PolyLine, Pt2D};
use rand;
#[allow(clippy::unreadable_literal)]
@ -25,21 +25,21 @@ pub fn run(t: &mut TestRunner) {
let width = 50.0;
let pt1_s = Line::new(pt1, pt2).shift_right(width).pt1();
let pt2_s = line_intersection(
&Line::new(pt1, pt2).shift_right(width),
&Line::new(pt2, pt3).shift_right(width),
)
.unwrap();
let pt3_s = line_intersection(
&Line::new(pt2, pt3).shift_right(width),
&Line::new(pt3, pt4).shift_right(width),
)
.unwrap();
let pt4_s = line_intersection(
&Line::new(pt3, pt4).shift_right(width),
&Line::new(pt4, pt5).shift_right(width),
)
.unwrap();
let pt2_s = Line::new(pt1, pt2)
.shift_right(width)
.infinite()
.intersection(&Line::new(pt2, pt3).shift_right(width).infinite())
.unwrap();
let pt3_s = Line::new(pt2, pt3)
.shift_right(width)
.infinite()
.intersection(&Line::new(pt3, pt4).shift_right(width).infinite())
.unwrap();
let pt4_s = Line::new(pt3, pt4)
.shift_right(width)
.infinite()
.intersection(&Line::new(pt4, pt5).shift_right(width).infinite())
.unwrap();
let pt5_s = Line::new(pt4, pt5).shift_right(width).pt2();
assert_eq!(