From bdc72e88b8dbe4c05c375e92e94270816d03e9a7 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Fri, 15 Oct 2021 11:24:26 -0700 Subject: [PATCH 1/2] Drawing the route sketcher larger as we zoom out. I think it looks/feels OK. Ad-hoc code and no good caching yet. --- game/src/common/route_sketcher.rs | 55 ++++++++++++++++++++++++++----- widgetry/src/canvas.rs | 2 +- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/game/src/common/route_sketcher.rs b/game/src/common/route_sketcher.rs index 34852e7698..cbc7c807c0 100644 --- a/game/src/common/route_sketcher.rs +++ b/game/src/common/route_sketcher.rs @@ -4,12 +4,16 @@ use widgetry::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, TextExt, Widg use crate::app::App; +const RADIUS: Distance = Distance::const_meters(10.0); + // TODO Supercede RoadSelector, probably.. pub struct RouteSketcher { snap_to_intersections: FindClosest, route: Route, mode: Mode, preview: Drawable, + + thickness: f64, } impl RouteSketcher { @@ -24,6 +28,8 @@ impl RouteSketcher { route: Route::new(), mode: Mode::Neutral, preview: Drawable::empty(ctx), + + thickness: 0.0, } } @@ -99,18 +105,22 @@ impl RouteSketcher { fn update_preview(&mut self, ctx: &mut EventCtx, app: &App) { let map = &app.primary.map; + let thickness = zoom_to_thickness(ctx); let mut batch = GeomBatch::new(); // Draw the confirmed route for pair in self.route.full_path.windows(2) { // TODO Inefficient! - let r = map.find_road_between(pair[0], pair[1]).unwrap(); - batch.push(Color::RED.alpha(0.5), map.get_r(r).get_thick_polygon()); + let r = map.get_r(map.find_road_between(pair[0], pair[1]).unwrap()); + batch.push( + Color::RED.alpha(0.5), + r.center_pts.make_polygons(thickness * r.get_width()), + ); } for i in &self.route.full_path { batch.push( Color::BLUE.alpha(0.5), - Circle::new(map.get_i(*i).polygon.center(), Distance::meters(10.0)).to_polygon(), + Circle::new(map.get_i(*i).polygon.center(), thickness * RADIUS).to_polygon(), ); } @@ -121,8 +131,7 @@ impl RouteSketcher { cnt += 1; batch.push( Color::RED, - Circle::new(map.get_i(*i).polygon.center(), Distance::meters(10.0)) - .to_polygon(), + Circle::new(map.get_i(*i).polygon.center(), thickness * RADIUS).to_polygon(), ); batch.append( widgetry::Text::from(Line(format!("{}", cnt))) @@ -136,7 +145,7 @@ impl RouteSketcher { if let Mode::Hovering(i) = self.mode { batch.push( Color::BLUE, - Circle::new(map.get_i(i).polygon.center(), Distance::meters(10.0)).to_polygon(), + Circle::new(map.get_i(i).polygon.center(), thickness * RADIUS).to_polygon(), ); if self.route.waypoints.len() == 1 { if let Some((roads, intersections)) = @@ -154,11 +163,12 @@ impl RouteSketcher { if let Mode::Dragging { at, .. } = self.mode { batch.push( Color::BLUE, - Circle::new(map.get_i(at).polygon.center(), Distance::meters(10.0)).to_polygon(), + Circle::new(map.get_i(at).polygon.center(), thickness * RADIUS).to_polygon(), ); } self.preview = batch.upload(ctx); + self.thickness = thickness; } pub fn get_widget_to_describe(&self, ctx: &mut EventCtx) -> Widget { @@ -196,7 +206,16 @@ impl RouteSketcher { let orig_route = self.route.clone(); let orig_mode = self.mode.clone(); self.update_mode(ctx, app); - if self.route != orig_route || self.mode != orig_mode { + println!( + "Current thickness {}, zoom_to_thickness({}) now {}", + self.thickness, + ctx.canvas.cam_zoom, + zoom_to_thickness(ctx) + ); + if self.route != orig_route + || self.mode != orig_mode + || zoom_to_thickness(ctx) != self.thickness + { self.update_preview(ctx, app); true } else { @@ -221,7 +240,7 @@ impl RouteSketcher { if let Some(pt) = g.canvas.get_cursor_in_map_space() { g.draw_polygon( Color::BLUE.alpha(0.5), - Circle::new(pt, Distance::meters(10.0)).to_polygon(), + Circle::new(pt, self.thickness * RADIUS).to_polygon(), ); } } @@ -328,3 +347,21 @@ enum Mode { Hovering(IntersectionID), Dragging { idx: usize, at: IntersectionID }, } + +fn zoom_to_thickness(ctx: &EventCtx) -> f64 { + let zoom = ctx.canvas.cam_zoom; + let bucketed_zoom = if zoom >= 1.0 { + 1.0 + } else { + (zoom * 10.0).round() / 10.0 + }; + + // Thicker lines as we zoom out. Scale up to 5x. Never shrink past the road's actual width. + let thickness = (0.5 / bucketed_zoom).max(1.0); + // And on gigantic maps, zoom may approach 0, so avoid NaNs. + if thickness.is_finite() { + thickness + } else { + 5.0 + } +} diff --git a/widgetry/src/canvas.rs b/widgetry/src/canvas.rs index 1cd55ca766..773a6ba262 100644 --- a/widgetry/src/canvas.rs +++ b/widgetry/src/canvas.rs @@ -223,7 +223,7 @@ impl Canvas { // By popular request, some limits ;) self.cam_zoom = 1.1_f64 .powf(old_zoom.log(1.1) + delta * (self.settings.canvas_scroll_speed as f64 / 10.0)) - .max(self.min_zoom()) + //.max(self.min_zoom()) .min(self.max_zoom()); // Make screen_to_map of the focus point still point to the same thing after From 6ce725d0bba9c2b225005f17c77a32b0c3c87078 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Fri, 15 Oct 2021 12:26:37 -0700 Subject: [PATCH 2/2] A blunt attempt to recalculate all route stuff when discretized zoom changes. Again, looks decent... (except for drawing intersection polygons for some extra detail layers) --- game/src/common/waypoints.rs | 5 ++++- game/src/ungap/trip/mod.rs | 35 +++++++++++++++++++++++++++++++--- game/src/ungap/trip/results.rs | 25 ++++++++++++++++++------ widgetry/src/canvas.rs | 2 +- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/game/src/common/waypoints.rs b/game/src/common/waypoints.rs index 16e7110894..269afc7add 100644 --- a/game/src/common/waypoints.rs +++ b/game/src/common/waypoints.rs @@ -212,9 +212,11 @@ impl InputWaypoints { world: &mut World, wrap_id: F, zorder: usize, + thickness: f64, ) { for (idx, waypoint) in self.waypoints.iter().enumerate() { - let hitbox = Circle::new(waypoint.center, Distance::meters(30.0)).to_polygon(); + let hitbox = + Circle::new(waypoint.center, thickness * Distance::meters(30.0)).to_polygon(); let color = self.get_waypoint_color(idx); let mut draw_normal = GeomBatch::new(); @@ -222,6 +224,7 @@ impl InputWaypoints { draw_normal.append( Text::from(Line(get_waypoint_text(idx).to_string()).fg(Color::WHITE)) .render(ctx) + .scale(thickness) .centered_on(waypoint.center), ); diff --git a/game/src/ungap/trip/mod.rs b/game/src/ungap/trip/mod.rs index 9794cf6392..2804e54765 100644 --- a/game/src/ungap/trip/mod.rs +++ b/game/src/ungap/trip/mod.rs @@ -20,6 +20,8 @@ pub struct TripPlanner { // TODO We really only need to store preferences and stats, but... alt_routes: Vec, world: World, + + thickness: f64, } impl TakeLayers for TripPlanner { @@ -49,10 +51,11 @@ impl TripPlanner { input_panel: Panel::empty(ctx), waypoints: InputWaypoints::new(app), - main_route: RouteDetails::main_route(ctx, app, Vec::new()).details, + main_route: RouteDetails::main_route(ctx, app, Vec::new(), 0.0).details, files: files::TripManagement::new(app), alt_routes: Vec::new(), world: World::bounded(app.primary.map.get_bounds()), + thickness: 0.0, }; if let Some(current_name) = &app.session.ungap_current_trip_name { @@ -64,9 +67,11 @@ impl TripPlanner { // Use the current session settings to determine "main" and alts fn recalculate_routes(&mut self, ctx: &mut EventCtx, app: &mut App) { + self.thickness = zoom_to_thickness(ctx); let mut world = World::bounded(app.primary.map.get_bounds()); - let main_route = RouteDetails::main_route(ctx, app, self.waypoints.get_waypoints()); + let main_route = + RouteDetails::main_route(ctx, app, self.waypoints.get_waypoints(), self.thickness); self.main_route = main_route.details; world .add(ID::MainRoute) @@ -101,6 +106,7 @@ impl TripPlanner { self.waypoints.get_waypoints(), &self.main_route, preferences, + self.thickness, ); // Dedupe equivalent routes based on their stats, which is usually detailed enough if alt.details.stats != self.main_route.stats @@ -119,8 +125,9 @@ impl TripPlanner { } } + // TODO Oh no, even these have to scale with zoom!? self.waypoints - .rebuild_world(ctx, &mut world, |id| ID::Waypoint(id), 2); + .rebuild_world(ctx, &mut world, |id| ID::Waypoint(id), 2, self.thickness); world.initialize_hover(ctx); world.rebuilt_during_drag(&self.world); @@ -186,6 +193,10 @@ impl State for TripPlanner { }), }; + if zoom_to_thickness(ctx) != self.thickness { + self.recalculate_routes(ctx, app); + } + let panel_outcome = self.input_panel.event(ctx); if let Outcome::Clicked(ref x) = panel_outcome { if let Some(t) = Tab::Trip.handle_action::(ctx, app, x) { @@ -275,3 +286,21 @@ impl RoutingPreferences { } } } + +fn zoom_to_thickness(ctx: &EventCtx) -> f64 { + let zoom = ctx.canvas.cam_zoom; + let bucketed_zoom = if zoom >= 1.0 { + 1.0 + } else { + (zoom * 10.0).round() / 10.0 + }; + + // Thicker lines as we zoom out. Scale up to 5x. Never shrink past the road's actual width. + let thickness = (0.5 / bucketed_zoom).max(1.0); + // And on gigantic maps, zoom may approach 0, so avoid NaNs. + if thickness.is_finite() { + thickness + } else { + 5.0 + } +} diff --git a/game/src/ungap/trip/results.rs b/game/src/ungap/trip/results.rs index aa8685e758..01bd1e73f1 100644 --- a/game/src/ungap/trip/results.rs +++ b/game/src/ungap/trip/results.rs @@ -33,6 +33,7 @@ pub struct RouteDetails { hover_on_line_plot: Option<(Distance, Drawable)>, hover_on_route_tooltip: Option, + // TODO At least the first one here should also be zoom-responsive draw_high_stress: Drawable, draw_traffic_signals: Drawable, draw_unprotected_turns: Drawable, @@ -51,7 +52,12 @@ pub struct RouteStats { impl RouteDetails { /// "main" is determined by `app.session.routing_preferences` - pub fn main_route(ctx: &mut EventCtx, app: &App, waypoints: Vec) -> BuiltRoute { + pub fn main_route( + ctx: &mut EventCtx, + app: &App, + waypoints: Vec, + thickness: f64, + ) -> BuiltRoute { RouteDetails::new( ctx, app, @@ -59,6 +65,7 @@ impl RouteDetails { Color::RED, None, app.session.routing_preferences, + thickness, ) } @@ -68,6 +75,7 @@ impl RouteDetails { waypoints: Vec, main: &RouteDetails, preferences: RoutingPreferences, + thickness: f64, ) -> BuiltRoute { let mut built = RouteDetails::new( ctx, @@ -76,6 +84,7 @@ impl RouteDetails { Color::grey(0.3), Some(Color::RED), preferences, + thickness, ); built.tooltip_for_alt = Some(compare_routes( app, @@ -94,6 +103,7 @@ impl RouteDetails { // Only used for alts outline_color: Option, preferences: RoutingPreferences, + thickness: f64, ) -> BuiltRoute { let mut draw_route = ToggleZoomed::builder(); let mut hitbox_pieces = Vec::new(); @@ -135,7 +145,7 @@ impl RouteDetails { // that're stressful, and use trace draw_high_stress.push( Color::YELLOW, - this_pl.make_polygons(5.0 * NORMAL_LANE_THICKNESS), + this_pl.make_polygons(thickness * 5.0 * NORMAL_LANE_THICKNESS), ); } } @@ -144,6 +154,7 @@ impl RouteDetails { elevation_pts.push((current_dist, i.elevation)); if i.is_traffic_signal() { num_traffic_signals += 1; + // TODO scale up by thickness, but how for a general polygon? draw_traffic_signals.push(Color::YELLOW, i.polygon.clone()); } if map.is_unprotected_turn( @@ -152,6 +163,7 @@ impl RouteDetails { map.get_t(*t).turn_type, ) { num_unprotected_turns += 1; + // TODO scale up by thickness, but how for a general polygon? draw_unprotected_turns.push(Color::YELLOW, i.polygon.clone()); } } @@ -161,7 +173,7 @@ impl RouteDetails { let maybe_pl = path.trace(map); if let Some(ref pl) = maybe_pl { - let shape = pl.make_polygons(5.0 * NORMAL_LANE_THICKNESS); + let shape = pl.make_polygons(thickness * 5.0 * NORMAL_LANE_THICKNESS); draw_route .unzoomed .push(route_color.alpha(0.8), shape.clone()); @@ -172,9 +184,10 @@ impl RouteDetails { hitbox_pieces.push(shape); if let Some(color) = outline_color { - if let Some(outline) = - pl.to_thick_boundary(5.0 * NORMAL_LANE_THICKNESS, NORMAL_LANE_THICKNESS) - { + if let Some(outline) = pl.to_thick_boundary( + thickness * 5.0 * NORMAL_LANE_THICKNESS, + thickness * NORMAL_LANE_THICKNESS, + ) { draw_route.unzoomed.push(color, outline.clone()); draw_route.zoomed.push(color.alpha(0.5), outline); } diff --git a/widgetry/src/canvas.rs b/widgetry/src/canvas.rs index 773a6ba262..1cd55ca766 100644 --- a/widgetry/src/canvas.rs +++ b/widgetry/src/canvas.rs @@ -223,7 +223,7 @@ impl Canvas { // By popular request, some limits ;) self.cam_zoom = 1.1_f64 .powf(old_zoom.log(1.1) + delta * (self.settings.canvas_scroll_speed as f64 / 10.0)) - //.max(self.min_zoom()) + .max(self.min_zoom()) .min(self.max_zoom()); // Make screen_to_map of the focus point still point to the same thing after