From 3dfeae8b2c211554427de454308c22a35cecd477 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Fri, 25 Oct 2019 13:14:05 -0700 Subject: [PATCH] introduce a proper closed polyline abstraction, sub it in --- game/src/render/building.rs | 7 ++--- game/src/render/extra_shape.rs | 6 ++-- game/src/render/intersection.rs | 7 ++--- geom/src/lib.rs | 2 ++ geom/src/polyline.rs | 37 ++++++++-------------- geom/src/ring.rs | 54 +++++++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 37 deletions(-) create mode 100644 geom/src/ring.rs diff --git a/game/src/render/building.rs b/game/src/render/building.rs index 3a9544ddce..5bfea13bad 100644 --- a/game/src/render/building.rs +++ b/game/src/render/building.rs @@ -1,7 +1,7 @@ use crate::helpers::{ColorScheme, ID}; use crate::render::{DrawCtx, DrawOptions, Renderable, OUTLINE_THICKNESS}; use ezgui::{Color, GeomBatch, GfxCtx, Line, Text}; -use geom::{Circle, Distance, Line, PolyLine, Polygon, Pt2D}; +use geom::{Circle, Distance, Line, Polygon, Pt2D, Ring}; use map_model::{Building, BuildingID, Map, LANE_THICKNESS}; pub struct DrawBuilding { @@ -111,10 +111,7 @@ impl Renderable for DrawBuilding { } fn get_outline(&self, map: &Map) -> Polygon { - PolyLine::make_polygons_for_boundary( - map.get_b(self.id).polygon.points().clone(), - OUTLINE_THICKNESS, - ) + Ring::new(map.get_b(self.id).polygon.points().clone()).make_polygons(OUTLINE_THICKNESS) } fn contains_pt(&self, pt: Pt2D, map: &Map) -> bool { diff --git a/game/src/render/extra_shape.rs b/game/src/render/extra_shape.rs index 2eeb83f3d5..88df918741 100644 --- a/game/src/render/extra_shape.rs +++ b/game/src/render/extra_shape.rs @@ -3,7 +3,7 @@ use crate::render::{ DrawCtx, DrawOptions, Renderable, EXTRA_SHAPE_POINT_RADIUS, EXTRA_SHAPE_THICKNESS, }; use ezgui::{Color, Drawable, GeomBatch, GfxCtx, Prerender}; -use geom::{Circle, FindClosest, GPSBounds, PolyLine, Polygon, Pt2D}; +use geom::{Circle, FindClosest, GPSBounds, PolyLine, Polygon, Pt2D, Ring}; use kml::ExtraShape; use map_model::{DirectedRoadID, Map, LANE_THICKNESS}; use std::collections::BTreeMap; @@ -54,9 +54,9 @@ impl DrawExtraShape { } else if pts[0] == *pts.last().unwrap() { // TODO Toggle between these better //Polygon::new(&pts) - PolyLine::make_polygons_for_boundary(pts, EXTRA_SHAPE_THICKNESS) + Ring::new(pts).make_polygons(EXTRA_SHAPE_THICKNESS) } else { - PolyLine::make_polygons_for_boundary(pts, EXTRA_SHAPE_THICKNESS) + PolyLine::new(pts).make_polygons(EXTRA_SHAPE_THICKNESS) }; let mut batch = GeomBatch::new(); batch.push( diff --git a/game/src/render/intersection.rs b/game/src/render/intersection.rs index 9c7786eae6..cd638d182d 100644 --- a/game/src/render/intersection.rs +++ b/game/src/render/intersection.rs @@ -5,7 +5,7 @@ use crate::render::{ }; use abstutil::Timer; use ezgui::{Color, Drawable, GeomBatch, GfxCtx, Prerender}; -use geom::{Angle, Distance, Duration, Line, PolyLine, Polygon, Pt2D, EPSILON_DIST}; +use geom::{Angle, Distance, Duration, Line, PolyLine, Polygon, Pt2D, Ring, EPSILON_DIST}; use map_model::{ Intersection, IntersectionID, IntersectionType, Map, Road, RoadWithStopSign, Turn, TurnID, TurnType, LANE_THICKNESS, @@ -170,10 +170,7 @@ impl Renderable for DrawIntersection { } fn get_outline(&self, map: &Map) -> Polygon { - PolyLine::make_polygons_for_boundary( - map.get_i(self.id).polygon.points().clone(), - OUTLINE_THICKNESS, - ) + Ring::new(map.get_i(self.id).polygon.points().clone()).make_polygons(OUTLINE_THICKNESS) } fn contains_pt(&self, pt: Pt2D, map: &Map) -> bool { diff --git a/geom/src/lib.rs b/geom/src/lib.rs index 7242f15580..96ab0b5c63 100644 --- a/geom/src/lib.rs +++ b/geom/src/lib.rs @@ -9,6 +9,7 @@ mod line; mod polygon; mod polyline; mod pt; +mod ring; mod speed; pub use crate::angle::Angle; @@ -22,6 +23,7 @@ pub use crate::line::{InfiniteLine, Line}; pub use crate::polygon::{Polygon, Triangle}; pub use crate::polyline::PolyLine; pub use crate::pt::{HashablePt2D, Pt2D}; +pub use crate::ring::Ring; pub use crate::speed::Speed; // About 0.4 inches... which is quite tiny on the scale of things. :) diff --git a/geom/src/polyline.rs b/geom/src/polyline.rs index 0b0057f60c..f38613f3db 100644 --- a/geom/src/polyline.rs +++ b/geom/src/polyline.rs @@ -1,5 +1,5 @@ use crate::{ - Angle, Bounds, Distance, HashablePt2D, InfiniteLine, Line, Polygon, Pt2D, EPSILON_DIST, + Angle, Bounds, Distance, HashablePt2D, InfiniteLine, Line, Polygon, Pt2D, Ring, EPSILON_DIST, }; use abstutil::Warn; use serde_derive::{Deserialize, Serialize}; @@ -27,7 +27,7 @@ impl PolyLine { // needs to be squished down, why, and how. if pts.windows(2).any(|pair| pair[0] == pair[1]) { panic!( - "PL with total length {} and {} pts has ~dupe pts: {:?}", + "PL with total length {} and {} pts has ~dupe adjacent pts: {:?}", length, pts.len(), pts @@ -68,7 +68,7 @@ impl PolyLine { Some(result) } - pub fn make_polygons_for_boundary(pts: Vec, thickness: Distance) -> Polygon { + pub(crate) fn make_polygons_for_boundary(pts: Vec, thickness: Distance) -> Polygon { // Points WILL repeat -- fast-path some stuff. let pl = PolyLine { pts, @@ -77,15 +77,6 @@ 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, @@ -102,7 +93,7 @@ impl PolyLine { side1.extend(side2); side1.push(side1[0]); side1.dedup(); - Some(PolyLine::make_polygons_for_boundary(side1, boundary_width)) + Some(Ring::new(side1).make_polygons(boundary_width)) } pub fn reversed(&self) -> PolyLine { @@ -534,17 +525,15 @@ impl PolyLine { let angle = slice.last_pt().angle_to(self.last_pt()); Warn::ok(vec![ p, - PolyLine::make_polygons_for_boundary( - vec![ - self.last_pt(), - self.last_pt() - .project_away(head_size, angle.rotate_degs(-135.0)), - self.last_pt() - .project_away(head_size, angle.rotate_degs(135.0)), - self.last_pt(), - ], - outline_thickness, - ), + Ring::new(vec![ + self.last_pt(), + self.last_pt() + .project_away(head_size, angle.rotate_degs(-135.0)), + self.last_pt() + .project_away(head_size, angle.rotate_degs(135.0)), + self.last_pt(), + ]) + .make_polygons(outline_thickness), ]) } else { Warn::warn( diff --git a/geom/src/ring.rs b/geom/src/ring.rs new file mode 100644 index 0000000000..1aeebf9a99 --- /dev/null +++ b/geom/src/ring.rs @@ -0,0 +1,54 @@ +use crate::{Distance, PolyLine, Polygon, Pt2D}; +use serde_derive::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::fmt; + +// Maybe a misnomer, but like a PolyLine, but closed. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Ring { + // first equals last + pts: Vec, +} + +impl Ring { + pub fn new(pts: Vec) -> Ring { + assert!(pts.len() >= 3); + assert_eq!(pts[0], *pts.last().unwrap()); + + // This checks no lines are too small. Could take the other approach and automatically + // squish down points here and make sure the final result is at least EPSILON_DIST. + // But probably better for the callers to do this -- they have better understanding of what + // needs to be squished down, why, and how. + if pts.windows(2).any(|pair| pair[0] == pair[1]) { + panic!("Ring has ~dupe adjacent pts: {:?}", pts); + } + + let result = Ring { pts }; + + let mut seen_pts = HashSet::new(); + for pt in result.pts.iter().skip(1) { + seen_pts.insert(pt.to_hashable()); + } + if seen_pts.len() != result.pts.len() - 1 { + panic!("Ring has repeat points: {}", result); + } + + result + } + + pub fn make_polygons(&self, thickness: Distance) -> Polygon { + // TODO Has a weird corner. Use the polygon offset thing instead? And move the + // implementation here, ideally. + PolyLine::make_polygons_for_boundary(self.pts.clone(), thickness) + } +} + +impl fmt::Display for Ring { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "Ring::new(vec![")?; + for pt in &self.pts { + writeln!(f, " Pt2D::new({}, {}),", pt.x(), pt.y())?; + } + write!(f, "])") + } +}