diff --git a/game/src/debug/routes.rs b/game/src/debug/routes.rs index a0125bdd2c..64d5f1d037 100644 --- a/game/src/debug/routes.rs +++ b/game/src/debug/routes.rs @@ -207,7 +207,7 @@ fn params_to_controls(ctx: &mut EventCtx, mode: TripMode, params: &RoutingParams .margin_right(20), Spinner::widget( ctx, - "unprotected turn penalty", + "unprotected_turn_penalty", (Duration::seconds(1.0), Duration::seconds(100.0)), params.unprotected_turn_penalty, Duration::seconds(1.0), @@ -219,7 +219,7 @@ fn params_to_controls(ctx: &mut EventCtx, mode: TripMode, params: &RoutingParams "Bike lane penalty:".text_widget(ctx).margin_right(20), Spinner::widget( ctx, - "bike lane penalty", + "bike_lane_penalty", (0.0, 2.0), params.bike_lane_penalty, 0.1, @@ -229,7 +229,7 @@ fn params_to_controls(ctx: &mut EventCtx, mode: TripMode, params: &RoutingParams "Bus lane penalty:".text_widget(ctx).margin_right(20), Spinner::widget( ctx, - "bus lane penalty", + "bus_lane_penalty", (0.0, 2.0), params.bus_lane_penalty, 0.1, @@ -239,12 +239,34 @@ fn params_to_controls(ctx: &mut EventCtx, mode: TripMode, params: &RoutingParams "Driving lane penalty:".text_widget(ctx).margin_right(20), Spinner::widget( ctx, - "driving lane penalty", + "driving_lane_penalty", (0.0, 2.0), params.driving_lane_penalty, 0.1, ), ])); + rows.push(Widget::row(vec![ + "Avoid steep inclines (>= 8%):" + .text_widget(ctx) + .margin_right(20), + Spinner::widget( + ctx, + "avoid_steep_incline_penalty", + (0.0, 2.0), + params.avoid_steep_incline_penalty, + 0.1, + ), + ])); + rows.push(Widget::row(vec![ + "Avoid high-stress roads:".text_widget(ctx).margin_right(20), + Spinner::widget( + ctx, + "avoid_high_stress", + (0.0, 2.0), + params.avoid_high_stress, + 0.1, + ), + ])); } Widget::col(rows) } @@ -252,16 +274,18 @@ fn params_to_controls(ctx: &mut EventCtx, mode: TripMode, params: &RoutingParams fn controls_to_params(panel: &Panel) -> (TripMode, RoutingParams) { let mut params = RoutingParams::default(); if !panel.is_button_enabled("cars") { - params.unprotected_turn_penalty = panel.spinner("unprotected turn penalty"); + params.unprotected_turn_penalty = panel.spinner("unprotected_turn_penalty"); return (TripMode::Drive, params); } if !panel.is_button_enabled("pedestrians") { return (TripMode::Walk, params); } - params.unprotected_turn_penalty = panel.spinner("unprotected turn penalty"); - params.bike_lane_penalty = panel.spinner("bike lane penalty"); - params.bus_lane_penalty = panel.spinner("bus lane penalty"); - params.driving_lane_penalty = panel.spinner("driving lane penalty"); + params.unprotected_turn_penalty = panel.spinner("unprotected_turn_penalty"); + params.bike_lane_penalty = panel.spinner("bike_lane_penalty"); + params.bus_lane_penalty = panel.spinner("bus_lane_penalty"); + params.driving_lane_penalty = panel.spinner("driving_lane_penalty"); + params.avoid_steep_incline_penalty = panel.spinner("avoid_steep_incline_penalty"); + params.avoid_high_stress = panel.spinner("avoid_high_stress"); (TripMode::Bike, params) } @@ -400,7 +424,7 @@ impl State for AllRoutesExplorer { ((*after as isize) - (*before as isize)).abs() as usize }) .max() - .unwrap() as f64; + .unwrap_or(0) as f64; for (r, before, after) in comparisons { match after.cmp(&before) { std::cmp::Ordering::Less => { diff --git a/map_model/src/pathfind/mod.rs b/map_model/src/pathfind/mod.rs index 651f61dc3f..e2b33fd38b 100644 --- a/map_model/src/pathfind/mod.rs +++ b/map_model/src/pathfind/mod.rs @@ -159,11 +159,28 @@ pub fn zone_cost(mvmnt: MovementID, constraints: PathConstraints, map: &Map) -> pub struct RoutingParams { // For all vehicles. This is added to the cost of a movement as an additional delay. pub unprotected_turn_penalty: Duration, + // For bike routing. Multiplied by the base cost, since spending more time on the wrong lane // type matters. pub bike_lane_penalty: f64, pub bus_lane_penalty: f64, pub driving_lane_penalty: f64, + + // For bike routing. + // "Steep" is a fixed threshold of 8% incline, uphill only. Multiply by the base cost. (Note + // that cost already includes a reduction of speed to account for the incline -- this is a + // further "delay" on top of that!) + // TODO But even steeper roads matter more! + // TODO Serialize as usual. Requires regenerating all maps, not ready to do that yet. + #[serde(skip_serializing, skip_deserializing, default = "one")] + pub avoid_steep_incline_penalty: f64, + // If the road is `high_stress_for_bikes`, multiply by the base cost. + #[serde(skip_serializing, skip_deserializing, default = "one")] + pub avoid_high_stress: f64, +} + +fn one() -> f64 { + 1.0 } impl RoutingParams { @@ -172,9 +189,13 @@ impl RoutingParams { // 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), + bike_lane_penalty: 1.0, bus_lane_penalty: 1.1, driving_lane_penalty: 1.5, + + avoid_steep_incline_penalty: 1.0, + avoid_high_stress: 1.0, } } } diff --git a/map_model/src/pathfind/vehicles.rs b/map_model/src/pathfind/vehicles.rs index e2fb0a07e1..3b682b574c 100644 --- a/map_model/src/pathfind/vehicles.rs +++ b/map_model/src/pathfind/vehicles.rs @@ -317,10 +317,31 @@ pub fn vehicle_cost( PathConstraints::Pedestrian => unreachable!(), }; + let mut multiplier = 1.0; + if constraints == PathConstraints::Bike && params.avoid_steep_incline_penalty != 1.0 { + let road = map.get_r(dr.id); + let percent_incline = if dr.dir == Direction::Fwd { + road.percent_incline + } else { + -road.percent_incline + }; + if percent_incline >= 0.08 { + multiplier *= params.avoid_steep_incline_penalty; + } + } + + if constraints == PathConstraints::Bike && params.avoid_high_stress != 1.0 { + let road = map.get_r(dr.id); + if road.high_stress_for_bikes(map) { + multiplier *= params.avoid_high_stress; + } + } + + let mut extra = Duration::ZERO; // Penalize unprotected turns at a stop sign from smaller to larger roads. if map.is_unprotected_turn(dr.id, mvmnt.to.id, mvmnt_turn_type) { - base + params.unprotected_turn_penalty - } else { - base + extra += params.unprotected_turn_penalty } + + multiplier * base + extra }