Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exploring designs: thicker lines unzoomed #782

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 46 additions & 9 deletions game/src/common/route_sketcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this a radius of? The waypoint markers?


// TODO Supercede RoadSelector, probably..
pub struct RouteSketcher {
snap_to_intersections: FindClosest<IntersectionID>,
route: Route,
mode: Mode,
preview: Drawable,

thickness: f64,
}

impl RouteSketcher {
Expand All @@ -24,6 +28,8 @@ impl RouteSketcher {
route: Route::new(),
mode: Mode::Neutral,
preview: Drawable::empty(ctx),

thickness: 0.0,
}
}

Expand Down Expand Up @@ -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(),
);
}

Expand All @@ -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)))
Expand All @@ -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)) =
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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(),
);
}
}
Expand Down Expand Up @@ -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
}
}
5 changes: 4 additions & 1 deletion game/src/common/waypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,19 @@ impl InputWaypoints {
world: &mut World<T>,
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();
draw_normal.push(color, hitbox.clone());
draw_normal.append(
Text::from(Line(get_waypoint_text(idx).to_string()).fg(Color::WHITE))
.render(ctx)
.scale(thickness)
.centered_on(waypoint.center),
);

Expand Down
35 changes: 32 additions & 3 deletions game/src/ungap/trip/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub struct TripPlanner {
// TODO We really only need to store preferences and stats, but...
alt_routes: Vec<RouteDetails>,
world: World<ID>,

thickness: f64,
}

impl TakeLayers for TripPlanner {
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -186,6 +193,10 @@ impl State<App> 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::<TripPlanner>(ctx, app, x) {
Expand Down Expand Up @@ -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
}
}
25 changes: 19 additions & 6 deletions game/src/ungap/trip/results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub struct RouteDetails {
hover_on_line_plot: Option<(Distance, Drawable)>,
hover_on_route_tooltip: Option<Text>,

// TODO At least the first one here should also be zoom-responsive
draw_high_stress: Drawable,
draw_traffic_signals: Drawable,
draw_unprotected_turns: Drawable,
Expand All @@ -51,14 +52,20 @@ pub struct RouteStats {

impl RouteDetails {
/// "main" is determined by `app.session.routing_preferences`
pub fn main_route(ctx: &mut EventCtx, app: &App, waypoints: Vec<TripEndpoint>) -> BuiltRoute {
pub fn main_route(
ctx: &mut EventCtx,
app: &App,
waypoints: Vec<TripEndpoint>,
thickness: f64,
) -> BuiltRoute {
RouteDetails::new(
ctx,
app,
waypoints,
Color::RED,
None,
app.session.routing_preferences,
thickness,
)
}

Expand All @@ -68,6 +75,7 @@ impl RouteDetails {
waypoints: Vec<TripEndpoint>,
main: &RouteDetails,
preferences: RoutingPreferences,
thickness: f64,
) -> BuiltRoute {
let mut built = RouteDetails::new(
ctx,
Expand All @@ -76,6 +84,7 @@ impl RouteDetails {
Color::grey(0.3),
Some(Color::RED),
preferences,
thickness,
);
built.tooltip_for_alt = Some(compare_routes(
app,
Expand All @@ -94,6 +103,7 @@ impl RouteDetails {
// Only used for alts
outline_color: Option<Color>,
preferences: RoutingPreferences,
thickness: f64,
) -> BuiltRoute {
let mut draw_route = ToggleZoomed::builder();
let mut hitbox_pieces = Vec::new();
Expand Down Expand Up @@ -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),
);
}
}
Expand All @@ -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(
Expand All @@ -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());
}
}
Expand All @@ -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());
Expand All @@ -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);
}
Expand Down