From 2e982ec827cc3bfe80edecc14e7a7448e8bfe1cf Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Mon, 28 Jan 2019 18:09:16 -0800 Subject: [PATCH] making an infinite line type, to be less confusing --- docs/TODO_logistic.md | 5 ++ editor/src/render/intersection.rs | 3 +- geom/src/lib.rs | 25 +------ geom/src/line.rs | 92 +++++++++++++++++--------- geom/src/polyline.rs | 17 ++--- map_model/src/make/initial/geometry.rs | 5 +- tests/src/geom.rs | 32 ++++----- 7 files changed, 97 insertions(+), 82 deletions(-) diff --git a/docs/TODO_logistic.md b/docs/TODO_logistic.md index 4da11a490b..88cde1a9d1 100644 --- a/docs/TODO_logistic.md +++ b/docs/TODO_logistic.md @@ -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 diff --git a/editor/src/render/intersection.rs b/editor/src/render/intersection.rs index 036976d35e..8d61295726 100644 --- a/editor/src/render/intersection.rs +++ b/editor/src/render/intersection.rs @@ -134,7 +134,8 @@ fn calculate_corners(i: &Intersection, map: &Map) -> Vec { 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. diff --git a/geom/src/lib.rs b/geom/src/lib.rs index f6fe4358ae..a58c966b7d 100644 --- a/geom/src/lib.rs +++ b/geom/src/lib.rs @@ -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 = 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 { - 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)) - } -} diff --git a/geom/src/line.rs b/geom/src/line.rs index 2656bc318f..61ba624a00 100644 --- a/geom/src/line.rs +++ b/geom/src/line.rs @@ -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 { - // 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 { - line_intersection(self, other) + // TODO Also return the distance along self + pub fn intersection_infinite(&self, other: &InfiniteLine) -> Option { + 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 { + 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)) + } + } +} diff --git a/geom/src/polyline.rs b/geom/src/polyline.rs index 9c459fd186..77700b2d99 100644 --- a/geom/src/polyline.rs +++ b/geom/src/polyline.rs @@ -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 { - // TODO There must be better ways to do this. :) + // TODO Also distance along + pub fn intersection_infinite(&self, other: &InfiniteLine) -> Option { 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 diff --git a/map_model/src/make/initial/geometry.rs b/map_model/src/make/initial/geometry.rs index b67077da11..f37b633126 100644 --- a/map_model/src/make/initial/geometry.rs +++ b/map_model/src/make/initial/geometry.rs @@ -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; diff --git a/tests/src/geom.rs b/tests/src/geom.rs index d327c7b6e8..017c84ece0 100644 --- a/tests/src/geom.rs +++ b/tests/src/geom.rs @@ -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!(