From c4d38ca5913498367f2e94c0751cbc8ff038b79f Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Mon, 27 Sep 2021 13:13:11 -0700 Subject: [PATCH] A humble start to showing an alternate, low-stress route. #743 --- abstutil/src/collections.rs | 2 +- game/src/ungap/route.rs | 114 +++++++++++++++++++++++++++++----- geom/src/lib.rs | 2 +- map_model/src/edits/mod.rs | 2 +- map_model/src/pathfind/mod.rs | 6 +- santa/src/music.rs | 2 +- sim/src/sim/mod.rs | 4 +- widgetry/src/widgets/mod.rs | 2 +- 8 files changed, 109 insertions(+), 25 deletions(-) diff --git a/abstutil/src/collections.rs b/abstutil/src/collections.rs index 8b4446a39f..4c221440d3 100644 --- a/abstutil/src/collections.rs +++ b/abstutil/src/collections.rs @@ -73,7 +73,7 @@ where self.map } } -impl std::default::Default for MultiMap +impl Default for MultiMap where K: Ord + PartialEq + Clone, V: Ord + PartialEq + Clone, diff --git a/game/src/ungap/route.rs b/game/src/ungap/route.rs index 4ecf7cd8dd..6c438557a0 100644 --- a/game/src/ungap/route.rs +++ b/game/src/ungap/route.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use abstutil::Timer; use geom::{Circle, Distance, Duration, FindClosest, PolyLine}; use map_gui::tools::{grey_out_map, ChooseSomething, PopupMsg}; -use map_model::{Path, PathStep, NORMAL_LANE_THICKNESS}; +use map_model::{Path, PathStep, RoutingParams, NORMAL_LANE_THICKNESS}; use sim::{TripEndpoint, TripMode}; use widgetry::{ Choice, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, LinePlot, Outcome, Panel, @@ -26,6 +26,8 @@ pub struct RoutePlanner { waypoints: InputWaypoints, results: RouteResults, files: RouteManagement, + + alt_low_stress_route: AltRouteResults, } impl TakeLayers for RoutePlanner { @@ -42,16 +44,21 @@ impl RoutePlanner { input_panel: Panel::empty(ctx), waypoints: InputWaypoints::new(ctx, app), - results: RouteResults::new(ctx, app, Vec::new()).0, + results: RouteResults::main_route(ctx, app, Vec::new()).0, files: RouteManagement::new(app), + + alt_low_stress_route: AltRouteResults::low_stress(ctx, app, Vec::new()), }; rp.update_input_panel(ctx, app); Box::new(rp) } fn update_input_panel(&mut self, ctx: &mut EventCtx, app: &App) { - let (results, results_widget) = RouteResults::new(ctx, app, self.waypoints.get_waypoints()); + let (results, results_widget) = + RouteResults::main_route(ctx, app, self.waypoints.get_waypoints()); self.results = results; + self.alt_low_stress_route = + AltRouteResults::low_stress(ctx, app, self.waypoints.get_waypoints()); let params = &app.session.routing_params; let col = Widget::col(vec![ @@ -152,6 +159,8 @@ impl State for RoutePlanner { return t; } + self.alt_low_stress_route.event(ctx); + Transition::Keep } @@ -160,6 +169,7 @@ impl State for RoutePlanner { self.input_panel.draw(g); self.waypoints.draw(g); self.results.draw(g, app, &self.input_panel); + self.alt_low_stress_route.draw(g, app); } } @@ -181,7 +191,27 @@ struct RouteResults { } impl RouteResults { - fn new(ctx: &mut EventCtx, app: &App, waypoints: Vec) -> (RouteResults, Widget) { + fn main_route( + ctx: &mut EventCtx, + app: &App, + waypoints: Vec, + ) -> (RouteResults, Widget) { + RouteResults::new( + ctx, + app, + waypoints, + Color::CYAN, + &app.session.routing_params, + ) + } + + fn new( + ctx: &mut EventCtx, + app: &App, + waypoints: Vec, + route_color: Color, + params: &RoutingParams, + ) -> (RouteResults, Widget) { let mut unzoomed_batch = GeomBatch::new(); let mut zoomed_batch = GeomBatch::new(); let mut draw_high_stress = GeomBatch::new(); @@ -204,10 +234,7 @@ impl RouteResults { for pair in waypoints.windows(2) { if let Some(path) = TripEndpoint::path_req(pair[0], pair[1], TripMode::Bike, map) - .and_then(|req| { - map.pathfind_with_params(req, &app.session.routing_params) - .ok() - }) + .and_then(|req| map.pathfind_with_params(req, params).ok()) { total_distance += path.total_length(); total_time += path.estimate_duration(map, Some(map_model::MAX_BIKE_SPEED)); @@ -250,8 +277,8 @@ impl RouteResults { let maybe_pl = path.trace(map); if let Some(ref pl) = maybe_pl { let shape = pl.make_polygons(5.0 * NORMAL_LANE_THICKNESS); - unzoomed_batch.push(Color::CYAN.alpha(0.8), shape.clone()); - zoomed_batch.push(Color::CYAN.alpha(0.5), shape); + unzoomed_batch.push(route_color.alpha(0.8), shape.clone()); + zoomed_batch.push(route_color.alpha(0.5), shape); closest_path_segment.add(paths.len(), pl.points()); } paths.push((path, maybe_pl)); @@ -502,6 +529,63 @@ impl RouteResults { } } +struct AltRouteResults { + closest_path_segment: FindClosest, + hovering: bool, + + draw_route_unzoomed: Drawable, + draw_route_zoomed: Drawable, +} + +impl AltRouteResults { + fn low_stress(ctx: &mut EventCtx, app: &App, waypoints: Vec) -> AltRouteResults { + let (results, _) = RouteResults::new( + ctx, + app, + waypoints, + Color::grey(0.3), + &RoutingParams { + avoid_high_stress: 2.0, + ..Default::default() + }, + ); + AltRouteResults { + closest_path_segment: results.closest_path_segment, + hovering: false, + + draw_route_unzoomed: results.draw_route_unzoomed, + draw_route_zoomed: results.draw_route_zoomed, + } + } + + fn event(&mut self, ctx: &mut EventCtx) { + if ctx.redo_mouseover() { + self.hovering = false; + if let Some(pt) = ctx.canvas.get_cursor_in_map_space() { + if self + .closest_path_segment + .closest_pt(pt, 10.0 * NORMAL_LANE_THICKNESS) + .is_some() + { + self.hovering = true; + } + } + } + } + + fn draw(&self, g: &mut GfxCtx, app: &App) { + if g.canvas.cam_zoom >= app.opts.min_zoom_for_detail { + g.redraw(&self.draw_route_zoomed); + } else { + g.redraw(&self.draw_route_unzoomed); + } + + if self.hovering { + g.draw_mouse_tooltip(Text::from(Line("Click to try this alternate route"))); + } + } +} + /// Save sequences of waypoints as named routes. Basic file management -- save, load, browse. This /// is useful to define "test cases," then edit the bike network and "run the tests" to compare /// results. @@ -674,7 +758,7 @@ impl RouteManagement { self.current = self.all.next(&self.current.name).unwrap().clone(); Some(Transition::Keep) } - "rename route" => Some(Transition::Push(RenameEdits::new_state( + "rename route" => Some(Transition::Push(RenameRoute::new_state( ctx, &self.current, &self.all, @@ -684,12 +768,12 @@ impl RouteManagement { } } -struct RenameEdits { +struct RenameRoute { current_name: String, all_names: HashSet, } -impl RenameEdits { +impl RenameRoute { fn new_state( ctx: &mut EventCtx, current: &NamedRoute, @@ -714,7 +798,7 @@ impl RenameEdits { .build(ctx); >::new_state( panel, - Box::new(RenameEdits { + Box::new(RenameRoute { current_name: current.name.clone(), all_names: all.routes.keys().cloned().collect(), }), @@ -722,7 +806,7 @@ impl RenameEdits { } } -impl SimpleState for RenameEdits { +impl SimpleState for RenameRoute { fn on_click(&mut self, _: &mut EventCtx, _: &mut App, x: &str, panel: &Panel) -> Transition { match x { "close" => Transition::Pop, diff --git a/geom/src/lib.rs b/geom/src/lib.rs index ee32bdf5cb..d2378e9b34 100644 --- a/geom/src/lib.rs +++ b/geom/src/lib.rs @@ -101,7 +101,7 @@ impl std::convert::From for CornerRadii { } } -impl std::default::Default for CornerRadii { +impl Default for CornerRadii { fn default() -> Self { Self::zero() } diff --git a/map_model/src/edits/mod.rs b/map_model/src/edits/mod.rs index e11ca0cbaf..d603185eed 100644 --- a/map_model/src/edits/mod.rs +++ b/map_model/src/edits/mod.rs @@ -349,7 +349,7 @@ impl MapEdits { } } -impl std::default::Default for MapEdits { +impl Default for MapEdits { fn default() -> MapEdits { MapEdits::new() } diff --git a/map_model/src/pathfind/mod.rs b/map_model/src/pathfind/mod.rs index d8346be585..d550d6f033 100644 --- a/map_model/src/pathfind/mod.rs +++ b/map_model/src/pathfind/mod.rs @@ -177,9 +177,9 @@ pub struct RoutingParams { pub avoid_high_stress: f64, } -impl RoutingParams { - pub const fn default() -> RoutingParams { - RoutingParams { +impl Default for RoutingParams { + fn default() -> Self { + Self { // This is a total guess -- it really depends on the traffic patterns of the particular // road at the time we're routing. unprotected_turn_penalty: Duration::const_seconds(30.0), diff --git a/santa/src/music.rs b/santa/src/music.rs index e7ef4ec34e..03eed49041 100644 --- a/santa/src/music.rs +++ b/santa/src/music.rs @@ -15,7 +15,7 @@ pub struct Music { inner: Option, } -impl std::default::Default for Music { +impl Default for Music { fn default() -> Music { Music::empty() } diff --git a/sim/src/sim/mod.rs b/sim/src/sim/mod.rs index 593fc78c0d..107968d598 100644 --- a/sim/src/sim/mod.rs +++ b/sim/src/sim/mod.rs @@ -113,7 +113,7 @@ pub struct SimOptions { pub skip_analytics: bool, } -impl std::default::Default for SimOptions { +impl Default for SimOptions { fn default() -> SimOptions { SimOptions::new("tmp") } @@ -161,7 +161,7 @@ pub enum AlertHandler { Silence, } -impl std::default::Default for AlertHandler { +impl Default for AlertHandler { fn default() -> AlertHandler { AlertHandler::Print } diff --git a/widgetry/src/widgets/mod.rs b/widgetry/src/widgets/mod.rs index c44ba2719b..8664b3fc66 100644 --- a/widgetry/src/widgets/mod.rs +++ b/widgetry/src/widgets/mod.rs @@ -138,7 +138,7 @@ impl std::convert::From for CornerRounding { } } -impl std::default::Default for CornerRounding { +impl Default for CornerRounding { fn default() -> Self { CornerRounding::CornerRadii(CornerRadii::default()) }