From cedcc5ebe68e3d40465863d14cb6a2056e8d160b Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Sat, 3 Sep 2022 17:02:03 +0100 Subject: [PATCH] Make two different APIs for thickening a PolyLine. Just return a Tessellation when thickening Rings for outlines, since the result isn't a valid Ring. #951 --- Cargo.lock | 2 -- Cargo.toml | 6 ++--- apps/game/src/devtools/kml.rs | 12 ++++------ apps/santa/src/after_level.rs | 6 ++--- geom/src/polygon.rs | 2 +- geom/src/polyline.rs | 20 +++++++++++----- geom/src/ring.rs | 6 ++--- geom/src/tessellation.rs | 40 +++++++++++++++++++++++++++++-- map_gui/src/render/car.rs | 8 +++---- map_gui/src/render/lane.rs | 8 +++---- map_gui/src/render/parking_lot.rs | 4 ++-- map_gui/src/render/pedestrian.rs | 8 +++---- 12 files changed, 78 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41e3852bec..d7cc179b24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2004,7 +2004,6 @@ dependencies = [ [[package]] name = "import_streets" version = "0.1.0" -source = "git+https://github.com/a-b-street/osm2streets#4fb6b5aaa6a1297e81dd37240f15e3942a44aff4" dependencies = [ "abstutil", "anyhow", @@ -3953,7 +3952,6 @@ checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" [[package]] name = "street_network" version = "0.1.0" -source = "git+https://github.com/a-b-street/osm2streets#4fb6b5aaa6a1297e81dd37240f15e3942a44aff4" dependencies = [ "aabb-quadtree", "abstutil", diff --git a/Cargo.toml b/Cargo.toml index 8789038c18..550b72e802 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,6 @@ abstutil = { path = "abstutil" } polylabel = { git = "https://github.com/urschrei/polylabel-rs", rev = "b919b8587b491b9a952a6d4c0670558bfd38e034" } # To temporarily work on dependencies locally, uncomment this -#[patch."https://github.com/a-b-street/osm2streets"] -#import_streets = { path = "/home/dabreegster/osm2streets/import_streets" } -#street_network = { path = "/home/dabreegster/osm2streets/street_network" } +[patch."https://github.com/a-b-street/osm2streets"] +import_streets = { path = "/home/dabreegster/osm2streets/import_streets" } +street_network = { path = "/home/dabreegster/osm2streets/street_network" } diff --git a/apps/game/src/devtools/kml.rs b/apps/game/src/devtools/kml.rs index 3900b5695c..4cb5185555 100644 --- a/apps/game/src/devtools/kml.rs +++ b/apps/game/src/devtools/kml.rs @@ -314,13 +314,11 @@ fn make_object( let polygon = if pts.len() == 1 { Circle::new(pts[0], RADIUS).to_polygon() } else if let Ok(ring) = Ring::new(pts.clone()) { - // TODO Ideally we could choose this in the UI - if attribs.get("spatial_type") == Some(&"Polygon".to_string()) { - color = cs.rotating_color_plot(obj_idx).alpha(0.8); - ring.into_polygon() - } else { - ring.to_outline(THICKNESS) - } + // TODO If the below isn't true, show it as an outline instead? Can't make that a Polygon, + // though + // if attribs.get("spatial_type") == Some(&"Polygon".to_string()) { + color = cs.rotating_color_plot(obj_idx).alpha(0.8); + ring.into_polygon() } else { let backup = pts[0]; match PolyLine::new(pts) { diff --git a/apps/santa/src/after_level.rs b/apps/santa/src/after_level.rs index 8fefdb244f..d224361340 100644 --- a/apps/santa/src/after_level.rs +++ b/apps/santa/src/after_level.rs @@ -1,5 +1,5 @@ use abstutil::prettyprint_usize; -use geom::{Distance, PolyLine, Polygon, Pt2D}; +use geom::{Distance, PolyLine, Pt2D, Tessellation}; use widgetry::tools::{ColorLegend, PopupMsg}; use widgetry::{ Color, Drawable, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Key, Line, Panel, @@ -257,8 +257,8 @@ impl RecordPath { self.pts.push(pt); } - pub fn render(mut self, thickness: Distance) -> Polygon { + pub fn render(mut self, thickness: Distance) -> Tessellation { self.pts.dedup(); - PolyLine::unchecked_new(self.pts).make_polygons(thickness) + PolyLine::unchecked_new(self.pts).thicken_tessellation(thickness) } } diff --git a/geom/src/polygon.rs b/geom/src/polygon.rs index 5c338033c4..546f1cda90 100644 --- a/geom/src/polygon.rs +++ b/geom/src/polygon.rs @@ -523,7 +523,7 @@ impl From for geo::Polygon { } } -fn from_multi(multi: geo::MultiPolygon) -> Result> { +pub(crate) fn from_multi(multi: geo::MultiPolygon) -> Result> { let mut result = Vec::new(); for polygon in multi { result.push(Polygon::try_from(polygon)?); diff --git a/geom/src/polyline.rs b/geom/src/polyline.rs index 92b1839df6..c3e963876f 100644 --- a/geom/src/polyline.rs +++ b/geom/src/polyline.rs @@ -101,7 +101,7 @@ impl PolyLine { &self, self_width: Distance, boundary_width: Distance, - ) -> Option { + ) -> Option { if self_width <= boundary_width || self.length() <= boundary_width + EPSILON_DIST { return None; } @@ -519,7 +519,17 @@ impl PolyLine { Ok(result) } + /// This produces a `Polygon` with a valid `Ring`. It may crash if this polyline was made with + /// `unchecked_new` pub fn make_polygons(&self, width: Distance) -> Polygon { + let tessellation = self.thicken_tessellation(width); + let ring = Ring::deduping_new(tessellation.points.clone()) + .expect("PolyLine::make_polygons() failed"); + Polygon::pretessellated(vec![ring], tessellation) + } + + /// Just produces a Tessellation + pub fn thicken_tessellation(&self, width: Distance) -> Tessellation { // TODO Don't use the angle corrections yet -- they seem to do weird things. let side1 = match self.shift_with_sharp_angles(width / 2.0, MITER_THRESHOLD) { Ok(pl) => pl, @@ -527,14 +537,14 @@ impl PolyLine { // TODO Circles will look extremely bizarre, but it emphasizes there's a bug // without just crashing println!("make_polygons({}) of {:?} failed: {}", width, self, err); - return Circle::new(self.first_pt(), width).to_polygon(); + return Tessellation::from(Circle::new(self.first_pt(), width).to_polygon()); } }; let mut side2 = match self.shift_with_sharp_angles(-width / 2.0, MITER_THRESHOLD) { Ok(pl) => pl, Err(err) => { println!("make_polygons({}) of {:?} failed: {}", width, self, err); - return Circle::new(self.first_pt(), width).to_polygon(); + return Tessellation::from(Circle::new(self.first_pt(), width).to_polygon()); } }; assert_eq!(side1.len(), side2.len()); @@ -554,9 +564,7 @@ impl PolyLine { indices.extend(vec![len - high_idx, len - high_idx - 1, high_idx]); } - let tessellation = Tessellation::new(points.clone(), indices); - let ring = Ring::deduping_new(points).expect("PolyLine::make_polygons() failed"); - Polygon::pretessellated(vec![ring], tessellation) + Tessellation::new(points.clone(), indices) } /// This does the equivalent of make_polygons, returning the (start left, start right, end diff --git a/geom/src/ring.rs b/geom/src/ring.rs index e7b6d6fb4d..2c515cbdb6 100644 --- a/geom/src/ring.rs +++ b/geom/src/ring.rs @@ -5,7 +5,7 @@ use std::fmt::Write; use anyhow::Result; use serde::{Deserialize, Serialize}; -use crate::{Distance, GPSBounds, Line, PolyLine, Polygon, Pt2D}; +use crate::{Distance, GPSBounds, Line, PolyLine, Polygon, Pt2D, Tessellation}; /// Maybe a misnomer, but like a PolyLine, but closed. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -52,9 +52,9 @@ impl Ring { /// Draws the ring with some thickness, with half of it straddling the interor of the ring, and /// half on the outside. - pub fn to_outline(&self, thickness: Distance) -> Polygon { + pub fn to_outline(&self, thickness: Distance) -> Tessellation { // TODO Has a weird corner. Use the polygon offset thing instead? - PolyLine::unchecked_new(self.pts.clone()).make_polygons(thickness) + PolyLine::unchecked_new(self.pts.clone()).thicken_tessellation(thickness) } pub fn into_polygon(self) -> Polygon { diff --git a/geom/src/tessellation.rs b/geom/src/tessellation.rs index b1c575fc18..836bce1a77 100644 --- a/geom/src/tessellation.rs +++ b/geom/src/tessellation.rs @@ -1,6 +1,7 @@ +use anyhow::Result; use serde::{Deserialize, Serialize}; -use crate::{Angle, Bounds, Polygon, Pt2D}; +use crate::{Angle, Bounds, GPSBounds, Polygon, Pt2D}; // Only serializable for Polygons that precompute a tessellation /// A tessellated polygon, ready for rendering. @@ -8,7 +9,7 @@ use crate::{Angle, Bounds, Polygon, Pt2D}; pub struct Tessellation { /// These points aren't in any meaningful order. It's not generally possible to reconstruct a /// `Polygon` from this. - points: Vec, + pub(crate) points: Vec, /// Groups of three indices make up the triangles indices: Vec, } @@ -204,6 +205,41 @@ impl Tessellation { } result } + + /// Produces a GeoJSON multipolygon consisting of individual triangles. Optionally map the + /// world-space points back to GPS. + pub fn to_geojson(&self, gps: Option<&GPSBounds>) -> geojson::Geometry { + let mut polygons = Vec::new(); + for triangle in self.triangles() { + let raw_pts = vec![triangle.pt1, triangle.pt2, triangle.pt3, triangle.pt1]; + let mut pts = Vec::new(); + if let Some(gps) = gps { + for pt in gps.convert_back(&raw_pts) { + pts.push(vec![pt.x(), pt.y()]); + } + } else { + for pt in raw_pts { + pts.push(vec![pt.x(), pt.y()]); + } + } + polygons.push(vec![pts]); + } + + geojson::Geometry::new(geojson::Value::MultiPolygon(polygons)) + } + + // TODO This only makes sense for something vaguely Ring-like + fn to_geo(&self) -> geo::Polygon { + let exterior = crate::conversions::pts_to_line_string(&self.points); + geo::Polygon::new(exterior, Vec::new()) + } + + // TODO After making to_outline return a real Polygon, get rid of this + pub fn difference(&self, other: &Tessellation) -> Result> { + use geo::BooleanOps; + + crate::polygon::from_multi(self.to_geo().difference(&other.to_geo())) + } } fn downsize(input: Vec) -> Vec { diff --git a/map_gui/src/render/car.rs b/map_gui/src/render/car.rs index 30b44e3343..744552c6f5 100644 --- a/map_gui/src/render/car.rs +++ b/map_gui/src/render/car.rs @@ -221,11 +221,9 @@ impl Renderable for DrawCar { } fn get_outline(&self, _: &Map) -> Tessellation { - Tessellation::from( - self.body - .to_thick_boundary(CAR_WIDTH, OUTLINE_THICKNESS) - .unwrap_or_else(|| self.body_polygon.clone()), - ) + self.body + .to_thick_boundary(CAR_WIDTH, OUTLINE_THICKNESS) + .unwrap_or_else(|| Tessellation::from(self.body_polygon.clone())) } fn contains_pt(&self, pt: Pt2D, _: &Map) -> bool { diff --git a/map_gui/src/render/lane.rs b/map_gui/src/render/lane.rs index bf2f786840..a459249032 100644 --- a/map_gui/src/render/lane.rs +++ b/map_gui/src/render/lane.rs @@ -211,11 +211,9 @@ impl Renderable for DrawLane { fn get_outline(&self, map: &Map) -> Tessellation { let lane = map.get_l(self.id); - Tessellation::from( - lane.lane_center_pts - .to_thick_boundary(lane.width, OUTLINE_THICKNESS) - .unwrap_or_else(|| self.polygon.clone()), - ) + lane.lane_center_pts + .to_thick_boundary(lane.width, OUTLINE_THICKNESS) + .unwrap_or_else(|| Tessellation::from(self.polygon.clone())) } fn contains_pt(&self, pt: Pt2D, _: &Map) -> bool { diff --git a/map_gui/src/render/parking_lot.rs b/map_gui/src/render/parking_lot.rs index e4455fdb21..32c4914590 100644 --- a/map_gui/src/render/parking_lot.rs +++ b/map_gui/src/render/parking_lot.rs @@ -29,7 +29,7 @@ impl DrawParkingLot { let aisle_thickness = NORMAL_LANE_THICKNESS / 2.0; unzoomed_batch.push( cs.unzoomed_road_surface(osm::RoadRank::Local), - PolyLine::unchecked_new(aisle.clone()).make_polygons(aisle_thickness), + PolyLine::unchecked_new(aisle.clone()).thicken_tessellation(aisle_thickness), ); } unzoomed_batch.append( @@ -76,7 +76,7 @@ impl DrawParkingLot { batch.push( app.cs() .zoomed_road_surface(LaneType::Driving, osm::RoadRank::Local), - PolyLine::unchecked_new(aisle.clone()).make_polygons(aisle_thickness), + PolyLine::unchecked_new(aisle.clone()).thicken_tessellation(aisle_thickness), ); } let width = NORMAL_LANE_THICKNESS; diff --git a/map_gui/src/render/pedestrian.rs b/map_gui/src/render/pedestrian.rs index e921416468..afe3e19a96 100644 --- a/map_gui/src/render/pedestrian.rs +++ b/map_gui/src/render/pedestrian.rs @@ -291,11 +291,9 @@ impl Renderable for DrawPedCrowd { } fn get_outline(&self, _: &Map) -> Tessellation { - Tessellation::from( - self.blob_pl - .to_thick_boundary(sim::pedestrian_body_radius() * 2.0, OUTLINE_THICKNESS) - .unwrap_or_else(|| self.blob.clone()), - ) + self.blob_pl + .to_thick_boundary(sim::pedestrian_body_radius() * 2.0, OUTLINE_THICKNESS) + .unwrap_or_else(|| Tessellation::from(self.blob.clone())) } fn get_zorder(&self) -> isize {