From 4c1e9b41c103b1e5485c5897131461aed1cd2b2a Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Fri, 17 May 2019 13:27:30 -0700 Subject: [PATCH] experimentally try finding intersection polygon by naively intersecting thick road polygons. --- editor/Cargo.toml | 1 + editor/src/debug/mod.rs | 113 ++++++++++++++++++++++++++++++++++++-- editor/src/helpers.rs | 5 +- editor/src/render/road.rs | 2 +- geom/src/polygon.rs | 22 ++++++++ geom/src/polyline.rs | 9 +++ map_model/src/road.rs | 15 +++-- 7 files changed, 151 insertions(+), 16 deletions(-) diff --git a/editor/Cargo.toml b/editor/Cargo.toml index ea1fbd3657..8c4321206c 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] aabb-quadtree = "0.1.0" abstutil = { path = "../abstutil" } +clipping = "0.1.1" counter = "0.4.3" derive-new = "0.5.6" ezgui = { path = "../ezgui" } diff --git a/editor/src/debug/mod.rs b/editor/src/debug/mod.rs index 8f3d990442..abf4988fc7 100644 --- a/editor/src/debug/mod.rs +++ b/editor/src/debug/mod.rs @@ -11,11 +11,13 @@ use crate::helpers::ID; use crate::render::DrawOptions; use crate::ui::{ShowLayers, ShowObject, UI}; use abstutil::Timer; +use clipping::CPolygon; use ezgui::{ Color, EventCtx, EventLoopMode, GfxCtx, InputResult, Key, ModalMenu, ScrollingMenu, Text, TextBox, Wizard, }; -use map_model::RoadID; +use geom::{Polygon, Pt2D}; +use map_model::{IntersectionID, Map, RoadID}; use std::collections::HashSet; pub struct DebugMode { @@ -23,6 +25,7 @@ pub struct DebugMode { common: CommonState, chokepoints: Option, show_original_roads: HashSet, + intersection_geom: HashSet, connected_roads: connected_roads::ShowConnectedRoads, objects: objects::ObjectDebugger, hidden: HashSet, @@ -45,6 +48,7 @@ impl DebugMode { common: CommonState::new(), chokepoints: None, show_original_roads: HashSet::new(), + intersection_geom: HashSet::new(), connected_roads: connected_roads::ShowConnectedRoads::new(), objects: objects::ObjectDebugger::new(), hidden: HashSet::new(), @@ -67,6 +71,7 @@ impl DebugMode { (Some(Key::Escape), "quit"), (Some(Key::C), "show/hide chokepoints"), (Some(Key::O), "clear original roads shown"), + (Some(Key::G), "clear intersection geometry"), (Some(Key::H), "unhide everything"), (Some(Key::Num1), "show/hide buildings"), (Some(Key::Num2), "show/hide intersections"), @@ -111,6 +116,12 @@ impl DebugMode { mode.show_original_roads.len() )); } + if !mode.intersection_geom.is_empty() { + txt.add_line(format!( + "Showing {} attempts at intersection geometry", + mode.intersection_geom.len() + )); + } if !mode.hidden.is_empty() { txt.add_line(format!("Hiding {} things", mode.hidden.len())); } @@ -151,6 +162,13 @@ impl DebugMode { mode.show_original_roads.clear(); } } + if !mode.intersection_geom.is_empty() + && state.ui.primary.current_selection.is_none() + { + if menu.action("clear intersection geometry") { + mode.intersection_geom.clear(); + } + } match state.ui.primary.current_selection { Some(ID::Lane(_)) | Some(ID::Intersection(_)) @@ -177,13 +195,25 @@ impl DebugMode { if let Some(ID::Lane(l)) = state.ui.primary.current_selection { let id = state.ui.primary.map.get_l(l).parent; - if ctx.input.contextual_action( - Key::V, - &format!("show original geometry of {:?}", id), - ) { + if !mode.show_original_roads.contains(&id) + && ctx.input.contextual_action( + Key::V, + &format!("show original geometry of {}", id), + ) + { mode.show_original_roads.insert(id); } } + if let Some(ID::Intersection(i)) = state.ui.primary.current_selection { + if !mode.intersection_geom.contains(&i) + && ctx.input.contextual_action( + Key::G, + &format!("recalculate intersection geometry of {}", i), + ) + { + mode.intersection_geom.insert(i); + } + } mode.connected_roads.event(ctx, &state.ui); mode.objects.event(ctx, &state.ui); mode.neighborhood_summary.event(&state.ui, menu); @@ -344,6 +374,9 @@ impl DebugMode { ); } } + for id in &mode.intersection_geom { + recalc_intersection_geom(*id, &state.ui.primary.map, g); + } mode.objects.draw(g, &state.ui); mode.neighborhood_summary.draw(g); @@ -396,3 +429,73 @@ impl ShowObject for DebugMode { &self.layers } } + +fn recalc_intersection_geom(id: IntersectionID, map: &Map, g: &mut GfxCtx) { + let i = map.get_i(id); + let mut all_polys = Vec::new(); + for r in &i.roads { + let (pl, width) = map.get_r(*r).get_thick_polyline(true).unwrap(); + // This is different than pl.make_polygons(width) because of the order of the points!!! + let poly = Polygon::new(&pl.to_thick_boundary_pts(width)); + g.draw_polygon(Color::RED.alpha(0.4), &poly); + all_polys.push(poly); + } + + let mut all_pieces = Vec::new(); + for (idx1, p1) in all_polys.iter().enumerate() { + for (idx2, p2) in all_polys.iter().enumerate() { + if idx1 != idx2 { + all_pieces.extend(intersection(p1, p2)); + } + } + } + for p in &all_pieces { + g.draw_polygon(Color::BLUE.alpha(0.4), &p); + } + if false { + if let Some(final_poly) = union(&all_pieces) { + g.draw_polygon(Color::GREEN.alpha(0.4), &final_poly); + } + } +} + +fn poly_to_cpoly(poly: &Polygon) -> CPolygon { + let mut pts: Vec<[f64; 2]> = poly.points().iter().map(|pt| [pt.x(), pt.y()]).collect(); + if pts[0] == *pts.last().unwrap() { + pts.pop(); + } + CPolygon::from_vec(&pts) +} + +fn cpoly_to_poly(raw_pts: Vec<[f64; 2]>) -> Polygon { + let mut pts: Vec = raw_pts + .into_iter() + .map(|pt| Pt2D::new(pt[0], pt[1])) + .collect(); + if pts[0] != *pts.last().unwrap() { + pts.push(pts[0]); + } + Polygon::new(&pts) +} + +fn intersection(p1: &Polygon, p2: &Polygon) -> Vec { + let mut cp1 = poly_to_cpoly(p1); + let mut cp2 = poly_to_cpoly(p2); + cp1.intersection(&mut cp2) + .into_iter() + .map(|pts| cpoly_to_poly(pts)) + .collect() +} + +fn union(polys: &Vec) -> Option { + let mut result = poly_to_cpoly(&polys[0]); + for p in polys.iter().skip(1) { + let output = result.union(&mut poly_to_cpoly(p)); + if output.len() != 1 { + println!("Argh, got {} pieces from union", output.len()); + return None; + } + result = CPolygon::from_vec(&output[0]); + } + Some(cpoly_to_poly(result.points())) +} diff --git a/editor/src/helpers.rs b/editor/src/helpers.rs index 7636df8563..f4e6e7d765 100644 --- a/editor/src/helpers.rs +++ b/editor/src/helpers.rs @@ -198,10 +198,7 @@ impl ID { pub fn canonical_point(&self, primary: &PerMapUI) -> Option { match *self { - ID::Road(id) => primary - .map - .maybe_get_r(id) - .map(|r| r.original_center_pts.first_pt()), + ID::Road(id) => primary.map.maybe_get_r(id).map(|r| r.center_pts.first_pt()), ID::Lane(id) => primary.map.maybe_get_l(id).map(|l| l.first_pt()), ID::Intersection(id) => primary.map.maybe_get_i(id).map(|i| i.point), ID::Turn(id) => primary.map.maybe_get_i(id.parent).map(|i| i.point), diff --git a/editor/src/render/road.rs b/editor/src/render/road.rs index a0cbb937cd..1f60068f64 100644 --- a/editor/src/render/road.rs +++ b/editor/src/render/road.rs @@ -36,7 +36,7 @@ impl Renderable for DrawRoad { } fn get_outline(&self, map: &Map) -> Polygon { - let (pl, width) = map.get_r(self.id).get_thick_polyline().unwrap(); + let (pl, width) = map.get_r(self.id).get_thick_polyline(false).unwrap(); pl.to_thick_boundary(width, OUTLINE_THICKNESS) .unwrap_or_else(|| map.get_r(self.id).get_thick_polygon().unwrap()) } diff --git a/geom/src/polygon.rs b/geom/src/polygon.rs index c967b88980..eb98420299 100644 --- a/geom/src/polygon.rs +++ b/geom/src/polygon.rs @@ -1,5 +1,6 @@ use crate::{Bounds, Distance, HashablePt2D, Pt2D}; use serde_derive::{Deserialize, Serialize}; +use std::fmt; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Polygon { @@ -120,6 +121,8 @@ impl Polygon { } } + // The order of these points depends on the constructor! The first and last point may or may + // not match. Polygons constructed from PolyLines will have a very weird order. pub fn points(&self) -> &Vec { &self.points } @@ -149,6 +152,25 @@ impl Polygon { } } +impl fmt::Display for Polygon { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!( + f, + "Polygon with {} points and {} indices", + self.points.len(), + self.indices.len() + )?; + for (idx, pt) in self.points.iter().enumerate() { + writeln!(f, " {}: {}", idx, pt)?; + } + write!(f, "Indices: [")?; + for slice in self.indices.chunks_exact(3) { + write!(f, "({}, {}, {}), ", slice[0], slice[1], slice[2])?; + } + writeln!(f, "]") + } +} + #[derive(Clone, Debug)] pub struct Triangle { pub pt1: Pt2D, diff --git a/geom/src/polyline.rs b/geom/src/polyline.rs index 3b86755d00..7c5d6803ff 100644 --- a/geom/src/polyline.rs +++ b/geom/src/polyline.rs @@ -59,6 +59,15 @@ impl PolyLine { pl.make_polygons(thickness) } + pub fn to_thick_boundary_pts(&self, width: Distance) -> Vec { + let mut side1 = self.shift_with_sharp_angles(width / 2.0); + let mut side2 = self.shift_with_sharp_angles(-width / 2.0); + side2.reverse(); + side1.extend(side2); + side1.push(side1[0]); + side1 + } + pub fn to_thick_boundary( &self, self_width: Distance, diff --git a/map_model/src/road.rs b/map_model/src/road.rs index c114733563..0dc681736a 100644 --- a/map_model/src/road.rs +++ b/map_model/src/road.rs @@ -291,23 +291,26 @@ impl Road { search.iter().find(|(_, t)| lt == *t).map(|(id, _)| *id) } - pub fn get_thick_polyline(&self) -> Warn<(PolyLine, Distance)> { + pub fn get_thick_polyline(&self, orig_pts: bool) -> Warn<(PolyLine, Distance)> { let width_right = (self.children_forwards.len() as f64) * LANE_THICKNESS; let width_left = (self.children_backwards.len() as f64) * LANE_THICKNESS; let total_width = width_right + width_left; + let pts = if orig_pts { + &self.original_center_pts + } else { + &self.center_pts + }; if width_right >= width_left { - self.center_pts - .shift_right((width_right - width_left) / 2.0) + pts.shift_right((width_right - width_left) / 2.0) .map(|pl| (pl, total_width)) } else { - self.center_pts - .shift_left((width_left - width_right) / 2.0) + pts.shift_left((width_left - width_right) / 2.0) .map(|pl| (pl, total_width)) } } pub fn get_thick_polygon(&self) -> Warn { - self.get_thick_polyline() + self.get_thick_polyline(false) .map(|(pl, width)| pl.make_polygons(width)) }