From bffa469396e5fe8b824a9d34a1166250aafea421 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Wed, 29 Dec 2021 18:23:33 +0000 Subject: [PATCH] Print LTN pathfinding info in the panel, not a tooltip. The tooltips clash with clicking on roads to filter them, and probably weren't terribly discoverable anyway. --- game/src/ltn/pathfinding.rs | 141 +++++++++++-------------------- game/src/ltn/per_neighborhood.rs | 4 +- widgetry/src/mapspace/mod.rs | 6 ++ widgetry/src/text.rs | 8 ++ 4 files changed, 65 insertions(+), 94 deletions(-) diff --git a/game/src/ltn/pathfinding.rs b/game/src/ltn/pathfinding.rs index b155c2aa72..3337b20c87 100644 --- a/game/src/ltn/pathfinding.rs +++ b/game/src/ltn/pathfinding.rs @@ -1,4 +1,4 @@ -use geom::{Distance, Duration, Polygon}; +use geom::{Distance, Duration}; use map_model::NORMAL_LANE_THICKNESS; use sim::{TripEndpoint, TripMode}; use widgetry::mapspace::{ObjectID, ToggleZoomed, World}; @@ -15,6 +15,7 @@ pub struct RoutePlanner { panel: Panel, waypoints: InputWaypoints, world: World, + draw_routes: ToggleZoomed, neighborhood: Neighborhood, } @@ -27,8 +28,6 @@ impl TakeNeighborhood for RoutePlanner { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] enum Obj { - RouteAfterFilters, - RouteBeforeFilters, Waypoint(WaypointID), Filterable(FilterableObj), } @@ -44,9 +43,9 @@ impl RoutePlanner { panel: Panel::empty(ctx), waypoints: InputWaypoints::new(app), world: World::unbounded(), + draw_routes: ToggleZoomed::empty(ctx), neighborhood, }; - rp.update(ctx, app); Box::new(rp) } @@ -65,7 +64,7 @@ impl RoutePlanner { Line("Increase to see how vehicles may try to detour in heavy traffic").secondary(), ]) .into_widget(ctx), - Text::new().into_widget(ctx).named("note"), + Text::new().into_widget(ctx).named("results"), ]); let mut panel = Tab::Pathfinding .panel_builder(ctx, app, contents) @@ -75,31 +74,29 @@ impl RoutePlanner { panel.restore(ctx, &self.panel); self.panel = panel; - let mut world = self.calculate_paths(ctx, app); - self.waypoints - .rebuild_world(ctx, &mut world, Obj::Waypoint, 3); - world.initialize_hover(ctx); - world.rebuilt_during_drag(&self.world); - self.world = world; - } - - /// Also has the side effect of changing a note in the panel - fn calculate_paths(&mut self, ctx: &mut EventCtx, app: &App) -> World { - let map = &app.primary.map; - let mut world = World::bounded(map.get_bounds()); - - // TODO It's expensive to do this as we constantly drag the route! + let mut world = World::bounded(app.primary.map.get_bounds()); super::per_neighborhood::populate_world( ctx, app, &self.neighborhood, &mut world, Obj::Filterable, - // TODO Put these on top of the routes, so we can click and filter roads part of the - // route. We lose the tooltip though; probably should put that in the panel instead - // anyway. - 2, + 0, ); + self.waypoints + .rebuild_world(ctx, &mut world, Obj::Waypoint, 1); + world.initialize_hover(ctx); + world.rebuilt_during_drag(&self.world); + self.world = world; + + self.recalculate_paths(ctx, app); + } + + // Updates the panel and draw_routes + fn recalculate_paths(&mut self, ctx: &mut EventCtx, app: &App) { + let map = &app.primary.map; + let mut results = Text::new(); + let mut draw = ToggleZoomed::builder(); // First the route respecting the filters let (total_time_after, total_dist_after) = { @@ -108,8 +105,6 @@ impl RoutePlanner { params.main_road_penalty = self.panel.spinner::("main road penalty").0; let cache_custom = true; - let mut draw_route = ToggleZoomed::builder(); - let mut hitbox_pieces = Vec::new(); let mut total_time = Duration::ZERO; let mut total_dist = Distance::ZERO; for pair in self.waypoints.get_waypoints().windows(2) { @@ -119,11 +114,8 @@ impl RoutePlanner { .and_then(|path| path.trace(map).map(|pl| (path, pl))) { let shape = pl.make_polygons(5.0 * NORMAL_LANE_THICKNESS); - draw_route - .unzoomed - .push(Color::RED.alpha(0.8), shape.clone()); - draw_route.zoomed.push(Color::RED.alpha(0.5), shape.clone()); - hitbox_pieces.push(shape); + draw.unzoomed.push(Color::RED.alpha(0.8), shape.clone()); + draw.zoomed.push(Color::RED.alpha(0.5), shape); // Use estimate_duration and not the original cost from pathfinding, since that // includes huge penalties when the route is forced to cross a filter @@ -131,20 +123,10 @@ impl RoutePlanner { total_dist += path.total_length(); } } - if !hitbox_pieces.is_empty() { - let mut txt = Text::new(); - txt.add_line(Line("Route respecting the new modal filters")); - txt.add_line(Line(format!("Time: {}", total_time))); - txt.add_line(Line(format!("Distance: {}", total_dist))); - - world - .add(Obj::RouteAfterFilters) - .hitbox(Polygon::union_all(hitbox_pieces)) - .zorder(0) - .draw(draw_route) - .hover_outline(Color::BLACK, Distance::meters(2.0)) - .tooltip(txt) - .build(ctx); + if total_dist != Distance::ZERO { + results.add_line(Line("Route respecting the new modal filters").fg(Color::BLUE)); + results.add_line(Line(format!("Time: {}", total_time))); + results.add_line(Line(format!("Distance: {}", total_dist))); } (total_time, total_dist) @@ -152,8 +134,7 @@ impl RoutePlanner { // Then the one ignoring filters { - let mut draw_route = ToggleZoomed::builder(); - let mut hitbox_pieces = Vec::new(); + let mut draw_old_route = ToggleZoomed::builder(); let mut total_time = Duration::ZERO; let mut total_dist = Distance::ZERO; let mut params = map.routing_params().clone(); @@ -166,74 +147,53 @@ impl RoutePlanner { .and_then(|path| path.trace(map).map(|pl| (path, pl))) { let shape = pl.make_polygons(5.0 * NORMAL_LANE_THICKNESS); - draw_route + draw_old_route .unzoomed .push(Color::BLUE.alpha(0.8), shape.clone()); - draw_route - .zoomed - .push(Color::BLUE.alpha(0.5), shape.clone()); - hitbox_pieces.push(shape); + draw_old_route.zoomed.push(Color::BLUE.alpha(0.5), shape); total_time += path.estimate_duration(map, None); total_dist += path.total_length(); } } - if !hitbox_pieces.is_empty() { - let mut txt = Text::new(); + if total_dist != Distance::ZERO { // If these two stats are the same, assume the two paths are equivalent if total_time == total_time_after && total_dist == total_dist_after { - world.delete(Obj::RouteAfterFilters); - txt.add_line(Line( + draw = draw_old_route; + results = Text::new(); + results.add_line(Line( "The route is the same before/after the new modal filters", )); - txt.add_line(Line(format!("Time: {}", total_time))); - txt.add_line(Line(format!("Distance: {}", total_dist))); - - let label = Text::new().into_widget(ctx); - self.panel.replace(ctx, "note", label); + results.add_line(Line(format!("Time: {}", total_time))); + results.add_line(Line(format!("Distance: {}", total_dist))); } else { - txt.add_line(Line("Route before the new modal filters")); - txt.add_line(Line(format!("Time: {}", total_time))); - txt.add_line(Line(format!("Distance: {}", total_dist))); + draw.append(draw_old_route); + results.add_line(Line("Route before the new modal filters").fg(Color::RED)); cmp_duration( - &mut txt, + &mut results, app, total_time - total_time_after, "shorter", "longer", ); + // Remove formatting -- red/green gets confusing with the blue/red of the two + // routes + results.remove_colors_from_last_line(); cmp_dist( - &mut txt, + &mut results, app, total_dist - total_dist_after, "shorter", "longer", ); - - let label = Text::from_all(vec![ - Line("Blue path").fg(Color::BLUE), - Line(" before adding filters, "), - Line("red path").fg(Color::RED), - Line(" after new filters"), - ]) - .into_widget(ctx); - self.panel.replace(ctx, "note", label); + results.remove_colors_from_last_line(); } - - world - .add(Obj::RouteBeforeFilters) - .hitbox(Polygon::union_all(hitbox_pieces)) - // If the two routes partly overlap, put the "before" on top, since it has - // the comparison stats. - .zorder(1) - .draw(draw_route) - .hover_outline(Color::BLACK, Distance::meters(2.0)) - .tooltip(txt) - .build(ctx); } } - world + self.draw_routes = draw.build(ctx); + let label = results.into_widget(ctx); + self.panel.replace(ctx, "results", label); } } @@ -270,13 +230,7 @@ impl State for RoutePlanner { if let Outcome::Changed(ref x) = panel_outcome { if x == "main road penalty" { - // Recompute paths - let mut world = self.calculate_paths(ctx, app); - self.waypoints - .rebuild_world(ctx, &mut world, Obj::Waypoint, 2); - world.initialize_hover(ctx); - world.rebuilt_during_drag(&self.world); - self.world = world; + self.recalculate_paths(ctx, app); } } @@ -294,6 +248,7 @@ impl State for RoutePlanner { self.panel.draw(g); g.redraw(&self.neighborhood.fade_irrelevant); + self.draw_routes.draw(g); self.neighborhood.draw_filters.draw(g); if g.canvas.is_unzoomed() { self.neighborhood.labels.draw(g, app); diff --git a/game/src/ltn/per_neighborhood.rs b/game/src/ltn/per_neighborhood.rs index 320851def1..4e24086e6c 100644 --- a/game/src/ltn/per_neighborhood.rs +++ b/game/src/ltn/per_neighborhood.rs @@ -195,7 +195,9 @@ pub fn handle_world_outcome( } WorldOutcome::ClickedObject(FilterableObj::InteriorIntersection(i)) => { if app.primary.map.get_i(i).roads.len() != 4 { - return false; + // Misleading. Nothing changes, but we'll "fall through" to other cases without + // this + return true; } // Toggle through all possible filters diff --git a/widgetry/src/mapspace/mod.rs b/widgetry/src/mapspace/mod.rs index 7a54862f00..4b50943378 100644 --- a/widgetry/src/mapspace/mod.rs +++ b/widgetry/src/mapspace/mod.rs @@ -87,6 +87,12 @@ impl ToggleZoomedBuilder { self } + pub fn append(&mut self, other: ToggleZoomedBuilder) { + assert_eq!(self.always_draw_unzoomed, other.always_draw_unzoomed); + self.unzoomed.append(other.unzoomed); + self.zoomed.append(other.zoomed); + } + pub fn build(self, ctx: &EventCtx) -> ToggleZoomed { if self.always_draw_unzoomed { assert!(self.zoomed.is_empty()); diff --git a/widgetry/src/text.rs b/widgetry/src/text.rs index 1fe618b0d3..f868c55fc0 100644 --- a/widgetry/src/text.rs +++ b/widgetry/src/text.rs @@ -291,6 +291,14 @@ impl Text { } } + pub fn remove_colors_from_last_line(&mut self) { + let (_, spans) = self.lines.last_mut().unwrap(); + for span in spans { + span.fg_color = None; + span.outline_color = None; + } + } + pub fn is_empty(&self) -> bool { self.lines.is_empty() }