Skip to content

Commit

Permalink
Make two different APIs for thickening a PolyLine. Just return a
Browse files Browse the repository at this point in the history
Tessellation when thickening Rings for outlines, since the result isn't
a valid Ring. #951
  • Loading branch information
dabreegster committed Sep 5, 2022
1 parent 3a3d525 commit 274f1ef
Show file tree
Hide file tree
Showing 12 changed files with 78 additions and 44 deletions.
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
12 changes: 5 additions & 7 deletions apps/game/src/devtools/kml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions apps/santa/src/after_level.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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)
}
}
2 changes: 1 addition & 1 deletion geom/src/polygon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ impl From<Polygon> for geo::Polygon {
}
}

fn from_multi(multi: geo::MultiPolygon) -> Result<Vec<Polygon>> {
pub(crate) fn from_multi(multi: geo::MultiPolygon) -> Result<Vec<Polygon>> {
let mut result = Vec::new();
for polygon in multi {
result.push(Polygon::try_from(polygon)?);
Expand Down
20 changes: 14 additions & 6 deletions geom/src/polyline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ impl PolyLine {
&self,
self_width: Distance,
boundary_width: Distance,
) -> Option<Polygon> {
) -> Option<Tessellation> {
if self_width <= boundary_width || self.length() <= boundary_width + EPSILON_DIST {
return None;
}
Expand Down Expand Up @@ -519,22 +519,32 @@ 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,
Err(err) => {
// 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());
Expand All @@ -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
Expand Down
6 changes: 3 additions & 3 deletions geom/src/ring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down
40 changes: 38 additions & 2 deletions geom/src/tessellation.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
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.
#[derive(PartialEq, Serialize, Deserialize, Clone, Debug)]
pub struct Tessellation {
/// These points aren't in any meaningful order. It's not generally possible to reconstruct a
/// `Polygon` from this.
points: Vec<Pt2D>,
pub(crate) points: Vec<Pt2D>,
/// Groups of three indices make up the triangles
indices: Vec<u16>,
}
Expand Down Expand Up @@ -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<Vec<Polygon>> {
use geo::BooleanOps;

crate::polygon::from_multi(self.to_geo().difference(&other.to_geo()))
}
}

fn downsize(input: Vec<usize>) -> Vec<u16> {
Expand Down
8 changes: 3 additions & 5 deletions map_gui/src/render/car.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 3 additions & 5 deletions map_gui/src/render/lane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions map_gui/src/render/parking_lot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 3 additions & 5 deletions map_gui/src/render/pedestrian.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 274f1ef

Please sign in to comment.