mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-23 08:54:30 +03:00
Make Polygon::scale fallible. #951
Now there's no possible way to construct an invalid Polygon; every one has Rings!
This commit is contained in:
parent
7bba08113f
commit
081bd769fa
@ -1,7 +1,7 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use abstutil::prettyprint_usize;
|
||||
use geom::{ArrowCap, Distance, Duration, PolyLine, Polygon, Time};
|
||||
use geom::{ArrowCap, Distance, Duration, PolyLine, Polygon, Tessellation, Time};
|
||||
use map_gui::options::TrafficSignalStyle;
|
||||
use map_gui::render::traffic_signal::draw_signal_stage;
|
||||
use map_model::{IntersectionID, IntersectionType, StageType};
|
||||
@ -193,28 +193,29 @@ fn current_demand_body(ctx: &mut EventCtx, app: &App, id: IntersectionID) -> Wid
|
||||
}
|
||||
|
||||
let mut batch = GeomBatch::new();
|
||||
let polygon = app.primary.map.get_i(id).polygon.clone();
|
||||
let mut polygon = Tessellation::from(app.primary.map.get_i(id).polygon.clone());
|
||||
let bounds = polygon.get_bounds();
|
||||
// Pick a zoom so that we fit a fixed width in pixels
|
||||
let zoom = (0.25 * ctx.canvas.window_width) / bounds.width();
|
||||
batch.push(
|
||||
app.cs.normal_intersection,
|
||||
polygon.translate(-bounds.min_x, -bounds.min_y).scale(zoom),
|
||||
);
|
||||
polygon.translate(-bounds.min_x, -bounds.min_y);
|
||||
polygon.scale(zoom);
|
||||
batch.push(app.cs.normal_intersection, polygon);
|
||||
|
||||
let mut tooltips = Vec::new();
|
||||
let mut outlines = Vec::new();
|
||||
for (pl, demand) in demand_per_movement {
|
||||
let percent = (demand as f64) / (total_demand as f64);
|
||||
let arrow = pl
|
||||
if let Ok(arrow) = pl
|
||||
.make_arrow(percent * Distance::meters(3.0), ArrowCap::Triangle)
|
||||
.translate(-bounds.min_x, -bounds.min_y)
|
||||
.scale(zoom);
|
||||
if let Ok(p) = arrow.to_outline(Distance::meters(1.0)) {
|
||||
outlines.push(p);
|
||||
.scale(zoom)
|
||||
{
|
||||
if let Ok(p) = arrow.to_outline(Distance::meters(1.0)) {
|
||||
outlines.push(p);
|
||||
}
|
||||
batch.push(Color::hex("#A3A3A3"), arrow.clone());
|
||||
tooltips.push((arrow, Text::from(prettyprint_usize(demand)), None));
|
||||
}
|
||||
batch.push(Color::hex("#A3A3A3"), arrow.clone());
|
||||
tooltips.push((arrow, Text::from(prettyprint_usize(demand)), None));
|
||||
}
|
||||
batch.extend(Color::WHITE, outlines);
|
||||
|
||||
|
@ -144,7 +144,7 @@ impl SteepStreets {
|
||||
pt.project_away(arrow_len, Angle::degrees(135.0)),
|
||||
])
|
||||
.make_polygons(thickness)
|
||||
.scale(5.0);
|
||||
.must_scale(5.0);
|
||||
let uphill_legend = Widget::row(vec![
|
||||
GeomBatch::from(vec![(ctx.style().text_primary_color, panel_arrow)])
|
||||
.autocrop()
|
||||
@ -305,7 +305,7 @@ impl ElevationContours {
|
||||
geojson::Value::MultiPolygon(polygons) => {
|
||||
for p in polygons {
|
||||
if let Ok(p) = Polygon::from_geojson(&p) {
|
||||
let poly = p.scale(resolution_m);
|
||||
let poly = p.must_scale(resolution_m);
|
||||
if let Ok(x) = poly.to_outline(Distance::meters(5.0)) {
|
||||
draw.unzoomed.push(Color::BLACK.alpha(0.5), x);
|
||||
}
|
||||
|
@ -214,7 +214,9 @@ impl TimePanel {
|
||||
// on the right, except at the very end (for the last 'radius' pixels). And
|
||||
// when the width is too small for the radius, this messes up.
|
||||
progress_bar.push(bar_bg, Polygon::rectangle(bar_width, bar_height));
|
||||
progress_bar.push(bar_fg, Polygon::rectangle(finished_width, bar_height));
|
||||
if let Ok(p) = Polygon::maybe_rectangle(finished_width, bar_height) {
|
||||
progress_bar.push(bar_fg, p);
|
||||
}
|
||||
|
||||
if let Some(baseline_finished_width) = baseline_finished_width {
|
||||
if baseline_finished_width > 0.0 {
|
||||
|
@ -233,7 +233,7 @@ impl RenderCellsBuilder {
|
||||
for p in polygons {
|
||||
if let Ok(poly) = Polygon::from_geojson(&p) {
|
||||
cell_polygons.push(
|
||||
poly.scale(RESOLUTION_M)
|
||||
poly.must_scale(RESOLUTION_M)
|
||||
.translate(self.bounds.min_x, self.bounds.min_y),
|
||||
);
|
||||
}
|
||||
|
@ -92,31 +92,40 @@ impl Polygon {
|
||||
Bounds::from(&self.points)
|
||||
}
|
||||
|
||||
fn transform<F: Fn(&Pt2D) -> Pt2D>(&self, f: F) -> Self {
|
||||
Self {
|
||||
/// Transformations must preserve Rings.
|
||||
fn transform<F: Fn(&Pt2D) -> Pt2D>(&self, f: F) -> Result<Self> {
|
||||
let mut rings = None;
|
||||
if let Some(ref existing_rings) = self.rings {
|
||||
let mut transformed = Vec::new();
|
||||
for ring in existing_rings {
|
||||
transformed.push(Ring::new(ring.points().iter().map(&f).collect())?);
|
||||
}
|
||||
rings = Some(transformed);
|
||||
}
|
||||
Ok(Self {
|
||||
points: self.points.iter().map(&f).collect(),
|
||||
indices: self.indices.clone(),
|
||||
rings: self.rings.as_ref().map(|rings| {
|
||||
rings
|
||||
.iter()
|
||||
// When scaling, rings may collapse entirely; just give up on preserving in
|
||||
// that case.
|
||||
.filter_map(|ring| Ring::new(ring.points().iter().map(&f).collect()).ok())
|
||||
.collect()
|
||||
}),
|
||||
}
|
||||
rings,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn translate(&self, dx: f64, dy: f64) -> Self {
|
||||
self.transform(|pt| pt.offset(dx, dy))
|
||||
.expect("translate shouldn't collapse Rings")
|
||||
}
|
||||
|
||||
pub fn scale(&self, factor: f64) -> Self {
|
||||
/// When `factor` is small, this may collapse Rings and thus fail.
|
||||
pub fn scale(&self, factor: f64) -> Result<Self> {
|
||||
self.transform(|pt| Pt2D::new(pt.x() * factor, pt.y() * factor))
|
||||
}
|
||||
|
||||
pub fn scale_xy(&self, x_factor: f64, y_factor: f64) -> Self {
|
||||
self.transform(|pt| Pt2D::new(pt.x() * x_factor, pt.y() * y_factor))
|
||||
/// When `factor` is known to be over 1, then scaling can't fail.
|
||||
pub fn must_scale(&self, factor: f64) -> Self {
|
||||
if factor < 1.0 {
|
||||
panic!("must_scale({factor}) might collapse Rings. Use scale()");
|
||||
}
|
||||
self.transform(|pt| Pt2D::new(pt.x() * factor, pt.y() * factor))
|
||||
.expect("must_scale collapsed a Ring")
|
||||
}
|
||||
|
||||
pub fn rotate(&self, angle: Angle) -> Self {
|
||||
@ -132,6 +141,7 @@ impl Polygon {
|
||||
pivot.y() + origin_pt.y() * cos + origin_pt.x() * sin,
|
||||
)
|
||||
})
|
||||
.expect("rotate_around shouldn't collapse Rings")
|
||||
}
|
||||
|
||||
pub fn centered_on(&self, center: Pt2D) -> Self {
|
||||
@ -180,15 +190,21 @@ impl Polygon {
|
||||
}
|
||||
|
||||
/// Top-left at the origin. Doesn't take Distance, because this is usually pixels, actually.
|
||||
pub fn rectangle(width: f64, height: f64) -> Self {
|
||||
Ring::must_new(vec![
|
||||
pub fn maybe_rectangle(width: f64, height: f64) -> Result<Self> {
|
||||
Ring::new(vec![
|
||||
Pt2D::new(0.0, 0.0),
|
||||
Pt2D::new(width, 0.0),
|
||||
Pt2D::new(width, height),
|
||||
Pt2D::new(0.0, height),
|
||||
Pt2D::new(0.0, 0.0),
|
||||
])
|
||||
.into_polygon()
|
||||
.map(|ring| ring.into_polygon())
|
||||
}
|
||||
|
||||
/// Top-left at the origin. Doesn't take Distance, because this is usually pixels, actually.
|
||||
/// Note this will panic if `width` or `height` is 0.
|
||||
pub fn rectangle(width: f64, height: f64) -> Self {
|
||||
Self::maybe_rectangle(width, height).unwrap()
|
||||
}
|
||||
|
||||
pub fn rectangle_centered(center: Pt2D, width: Distance, height: Distance) -> Self {
|
||||
|
@ -64,16 +64,17 @@ impl<A: AppLike + 'static> CityPicker<A> {
|
||||
let mut tooltips = Vec::new();
|
||||
for (name, polygon) in city.districts {
|
||||
if &name != app.map().get_name() {
|
||||
batch.push(
|
||||
outline_color,
|
||||
polygon.to_outline(Distance::meters(200.0)).unwrap(),
|
||||
);
|
||||
let polygon = polygon.scale(zoom);
|
||||
tooltips.push((
|
||||
polygon.clone(),
|
||||
Text::from(nice_map_name(&name)),
|
||||
Some(ClickOutcome::Custom(Box::new(name))),
|
||||
));
|
||||
if let Ok(zoomed_polygon) = polygon.scale(zoom) {
|
||||
batch.push(
|
||||
outline_color,
|
||||
polygon.to_outline(Distance::meters(200.0)).unwrap(),
|
||||
);
|
||||
tooltips.push((
|
||||
zoomed_polygon,
|
||||
Text::from(nice_map_name(&name)),
|
||||
Some(ClickOutcome::Custom(Box::new(name))),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
DrawWithTooltips::new_widget(
|
||||
|
@ -212,7 +212,7 @@ pub fn make_heatmap(
|
||||
let color = Color::rgb(c.r as usize, c.g as usize, c.b as usize).alpha(0.6);
|
||||
for p in polygons {
|
||||
if let Ok(poly) = Polygon::from_geojson(&p) {
|
||||
batch.push(color, poly.scale(opts.resolution));
|
||||
batch.push(color, poly.must_scale(opts.resolution));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -348,7 +348,7 @@ pub fn draw_isochrone(
|
||||
geojson::Value::MultiPolygon(polygons) => {
|
||||
for p in polygons {
|
||||
if let Ok(poly) = Polygon::from_geojson(&p) {
|
||||
batch.push(*color, poly.scale(resolution_m));
|
||||
batch.push(*color, poly.must_scale(resolution_m));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user