Extend the parking thought bubbles to handle people climbing steep hills.

(And some clippy fixes)
This commit is contained in:
Dustin Carlino 2021-07-09 13:03:46 -07:00
parent 184593094e
commit da704b4546
16 changed files with 195 additions and 76 deletions

View File

@ -0,0 +1,4 @@
<svg width="34" height="29" viewBox="0 0 34 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.2747 28.9371C13.0191 28.9127 6.98232 28.8746 4.85956 28.8526L1 28.8125L8.99035 23.1594C29.2549 8.82266 33.426 5.89413 33.4262 6.00287C33.4264 6.05255 33.5553 10.976 33.7128 16.9438C33.8703 22.9116 33.9993 28.0656 33.9995 28.3972L34 29L30.9152 28.9908C29.2186 28.9857 23.5304 28.9616 18.2748 28.9371L18.2747 28.9371Z" fill="black"/>
<path d="M28 0L16.5619 1.58208L23.6511 10.6967L28 0ZM1.61394 21.7894C11.3131 14.2456 17.5281 9.4117 21.5104 6.31431L20.2825 4.73561C16.3002 7.833 10.0852 12.6669 0.386059 20.2106L1.61394 21.7894Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 660 B

View File

@ -131,7 +131,7 @@ fn prebake(
}
PrebakeSummary {
map: scenario.map_name.describe(),
scenario: scenario.scenario_name.clone(),
scenario: scenario.scenario_name,
finished_trips,
cancelled_trips,
total_trip_duration_seconds,

View File

@ -306,6 +306,7 @@ pub fn draw_occupants(details: &mut Details, app: &App, id: BuildingID, focus: O
pos,
facing: Angle::degrees(90.0),
waiting_for_turn: None,
intent: None,
preparing_bike: false,
// Both hands and feet!
waiting_for_bus: true,

View File

@ -125,16 +125,14 @@ impl Distance {
Distance::feet(10.0 * (ft / 10.0).ceil())
} else if miles < 0.1 {
Distance::feet(100.0 * (ft / 100.0).ceil())
} else if miles <= 1.0 {
Distance::miles((miles * 10.0).ceil() / 10.0)
} else if miles <= 10.0 {
Distance::miles(miles.ceil())
} else if miles <= 100.0 {
Distance::miles(10.0 * (miles / 10.0).ceil())
} else {
if miles <= 1.0 {
Distance::miles((miles * 10.0).ceil() / 10.0)
} else if miles <= 10.0 {
Distance::miles(miles.ceil())
} else if miles <= 100.0 {
Distance::miles(10.0 * (miles / 10.0).ceil())
} else {
self
}
self
}
}
}

View File

@ -1,6 +1,6 @@
use geom::{ArrowCap, Circle, Distance, Line, PolyLine, Polygon, Pt2D};
use map_model::{Map, SIDEWALK_THICKNESS};
use sim::{CarID, DrawCarInput, Sim};
use sim::{CarID, DrawCarInput, Intent, Sim};
use widgetry::{Drawable, GeomBatch, GfxCtx, Prerender};
use crate::colors::ColorScheme;
@ -99,6 +99,25 @@ impl DrawBike {
);
}
if input.intent == Some(Intent::SteepUphill) {
let bubble_z = -0.0001;
let mut bubble_batch =
GeomBatch::load_svg(prerender, "system/assets/map/thought_bubble.svg")
.scale(0.05)
.centered_on(input.body.middle())
.translate(2.0, -3.5)
.set_z_offset(bubble_z);
bubble_batch.append(
GeomBatch::load_svg(prerender, "system/assets/tools/uphill.svg")
.scale(0.05)
.centered_on(input.body.middle())
.translate(2.2, -4.2)
.set_z_offset(bubble_z),
);
draw_default.append(bubble_batch);
}
let zorder = input
.partly_on
.into_iter()

View File

@ -1,6 +1,6 @@
use geom::{Angle, ArrowCap, Distance, PolyLine, Polygon, Pt2D, Ring};
use map_model::{Map, TurnType};
use sim::{CarID, CarStatus, DrawCarInput, Sim, VehicleType};
use sim::{CarID, CarStatus, DrawCarInput, Intent, Sim, VehicleType};
use widgetry::{Color, Drawable, GeomBatch, GfxCtx, Line, Prerender, Text};
use crate::colors::ColorScheme;
@ -90,7 +90,7 @@ impl DrawCar {
);
}
if input.show_parking_intent {
if input.intent == Some(Intent::Parking) {
// draw intent bubble
let bubble_z = -0.0001;
let mut bubble_batch =

View File

@ -1,6 +1,6 @@
use geom::{ArrowCap, Circle, Distance, PolyLine, Polygon, Pt2D};
use map_model::{DrivingSide, Map, SIDEWALK_THICKNESS};
use sim::{DrawPedCrowdInput, DrawPedestrianInput, PedCrowdLocation, PedestrianID, Sim};
use sim::{DrawPedCrowdInput, DrawPedestrianInput, Intent, PedCrowdLocation, PedestrianID, Sim};
use widgetry::{Color, Drawable, GeomBatch, GfxCtx, Line, Prerender, Text};
use crate::colors::ColorScheme;
@ -43,6 +43,25 @@ impl DrawPedestrian {
);
}
if input.intent == Some(Intent::SteepUphill) {
let bubble_z = -0.0001;
let mut bubble_batch =
GeomBatch::load_svg(prerender, "system/assets/map/thought_bubble.svg")
.scale(0.05)
.centered_on(input.pos)
.translate(2.0, -3.5)
.set_z_offset(bubble_z);
bubble_batch.append(
GeomBatch::load_svg(prerender, "system/assets/tools/uphill.svg")
.scale(0.05)
.centered_on(input.pos)
.translate(2.2, -4.2)
.set_z_offset(bubble_z),
);
draw_default.append(bubble_batch);
}
DrawPedestrian {
id: input.id,
body_circle,

View File

@ -260,7 +260,7 @@ pub fn vehicle_cost(
PathConstraints::Pedestrian => unreachable!(),
};
let t1 = map.get_r(dr.id).center_pts.length()
/ Traversable::max_speed_along_road(dr, max_speed, constraints, map);
/ Traversable::max_speed_along_road(dr, max_speed, constraints, map).0;
let t2 =
mvmnt_length / Traversable::max_speed_along_movement(mvmnt, max_speed, constraints, map);

View File

@ -202,6 +202,18 @@ impl Traversable {
constraints: PathConstraints,
map: &Map,
) -> Speed {
self.max_speed_and_incline_along(max_speed_on_flat_ground, constraints, map)
.0
}
/// The single definitive place to determine how fast somebody could go along a single road or
/// turn. This should be used for pathfinding and simulation. Returns (speed, percent incline).
pub fn max_speed_and_incline_along(
&self,
max_speed_on_flat_ground: Option<Speed>,
constraints: PathConstraints,
map: &Map,
) -> (Speed, f64) {
match self {
Traversable::Lane(l) => Traversable::max_speed_along_road(
map.get_l(*l).get_directed_parent(),
@ -209,23 +221,26 @@ impl Traversable {
constraints,
map,
),
Traversable::Turn(t) => Traversable::max_speed_along_movement(
t.to_movement(map),
max_speed_on_flat_ground,
constraints,
map,
Traversable::Turn(t) => (
Traversable::max_speed_along_movement(
t.to_movement(map),
max_speed_on_flat_ground,
constraints,
map,
),
0.0,
),
}
}
/// The single definitive place to determine how fast somebody could go along a single road.
/// This should be used for pathfinding and simulation.
pub fn max_speed_along_road(
/// This should be used for pathfinding and simulation. Returns (speed, percent incline).
pub(crate) fn max_speed_along_road(
dr: DirectedRoadID,
max_speed_on_flat_ground: Option<Speed>,
constraints: PathConstraints,
map: &Map,
) -> Speed {
) -> (Speed, f64) {
let road = map.get_r(dr.id);
let percent_incline = if dr.dir == Direction::Fwd {
road.percent_incline
@ -245,16 +260,17 @@ impl Traversable {
road.speed_limit
};
if let Some(s) = max_speed_on_flat_ground {
let speed = if let Some(s) = max_speed_on_flat_ground {
base.min(s)
} else {
base
}
};
(speed, percent_incline)
}
/// The single definitive place to determine how fast somebody could go along a single
/// movement. This should be used for pathfinding and simulation.
pub fn max_speed_along_movement(
/// movement. This should be used for pathfinding and simulation. Ignores elevation.
pub(crate) fn max_speed_along_movement(
mvmnt: MovementID,
max_speed_on_flat_ground: Option<Speed>,
_: PathConstraints,

View File

@ -29,7 +29,7 @@ use map_model::{
};
pub use crate::render::{
CarStatus, DrawCarInput, DrawPedCrowdInput, DrawPedestrianInput, PedCrowdLocation,
CarStatus, DrawCarInput, DrawPedCrowdInput, DrawPedestrianInput, Intent, PedCrowdLocation,
UnzoomedAgent,
};

View File

@ -6,8 +6,8 @@ use geom::{Distance, Duration, PolyLine, Time, EPSILON_DIST};
use map_model::{Direction, LaneID, Map, Traversable};
use crate::{
CarID, CarStatus, DistanceInterval, DrawCarInput, ParkingSpot, PersonID, Router, TimeInterval,
TransitSimState, TripID, Vehicle, VehicleType,
CarID, CarStatus, DistanceInterval, DrawCarInput, Intent, ParkingSpot, PersonID, Router,
TimeInterval, TransitSimState, TripID, Vehicle, VehicleType,
};
/// Represents a single vehicle. Note "car" is a misnomer; it could also be a bus or bike.
@ -51,13 +51,17 @@ impl Car {
start_time: Time,
map: &Map,
) -> CarState {
let speed = self.router.head().max_speed_along(
let (speed, percent_incline) = self.router.head().max_speed_and_incline_along(
self.vehicle.max_speed,
self.vehicle.vehicle_type.to_constraints(),
map,
);
let dt = (dist_int.end - dist_int.start) / speed;
CarState::Crossing(TimeInterval::new(start_time, start_time + dt), dist_int)
CarState::Crossing {
time_int: TimeInterval::new(start_time, start_time + dt),
dist_int,
steep_uphill: percent_incline >= 0.08,
}
}
pub fn get_draw_car(
@ -262,17 +266,23 @@ impl Car {
status: match self.state {
CarState::Queued { .. } => CarStatus::Moving,
CarState::WaitingToAdvance { .. } => CarStatus::Moving,
CarState::Crossing(_, _) => CarStatus::Moving,
CarState::Crossing { .. } => CarStatus::Moving,
CarState::ChangingLanes { .. } => CarStatus::Moving,
CarState::Unparking { .. } => CarStatus::Moving,
CarState::Parking(_, _, _) => CarStatus::Moving,
// Changing color for idling buses is helpful
CarState::IdlingAtStop(_, _) => CarStatus::Parked,
},
show_parking_intent: matches!(
(self.is_parking(), &self.state),
(true, _) | (_, CarState::Unparking { .. })
),
intent: if self.is_parking() || matches!(self.state, CarState::Unparking { .. }) {
Some(Intent::Parking)
} else {
match self.state {
CarState::Crossing { steep_uphill, .. } if steep_uphill => {
Some(Intent::SteepUphill)
}
_ => None,
}
},
on: self.router.head(),
partly_on,
label: if self.vehicle.vehicle_type == VehicleType::Bus
@ -303,7 +313,11 @@ impl Car {
/// state machine encoded here.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub(crate) enum CarState {
Crossing(TimeInterval, DistanceInterval),
Crossing {
time_int: TimeInterval,
dist_int: DistanceInterval,
steep_uphill: bool,
},
ChangingLanes {
from: LaneID,
to: LaneID,
@ -334,7 +348,7 @@ pub(crate) enum CarState {
impl CarState {
pub fn get_end_time(&self) -> Time {
match self {
CarState::Crossing(ref time_int, _) => time_int.end,
CarState::Crossing { ref time_int, .. } => time_int.end,
CarState::Queued { .. } => unreachable!(),
CarState::WaitingToAdvance { .. } => unreachable!(),
// Note this state lasts for lc_time, NOT for new_time.

View File

@ -368,7 +368,7 @@ impl DrivingSimState {
transit: &mut TransitSimState,
) -> bool {
match car.state {
CarState::Crossing(_, _) => {
CarState::Crossing { .. } => {
car.state = CarState::Queued {
blocked_since: now,
want_to_change_lanes: None,
@ -554,7 +554,11 @@ impl DrivingSimState {
} => {
// The car is already in the target queue. Just set them in the crossing state; we
// already calculated the intervals for it.
car.state = CarState::Crossing(new_time, new_dist);
car.state = CarState::Crossing {
time_int: new_time,
dist_int: new_dist,
steep_uphill: false,
};
ctx.scheduler
.push(car.state.get_end_time(), Command::UpdateCar(car.vehicle.id));
@ -595,7 +599,7 @@ impl DrivingSimState {
let our_dist = dists[idx].front;
match car.state {
CarState::Crossing(_, _)
CarState::Crossing { .. }
| CarState::Unparking { .. }
| CarState::WaitingToAdvance { .. }
| CarState::ChangingLanes { .. } => unreachable!(),
@ -709,7 +713,7 @@ impl DrivingSimState {
/*
// If this car wasn't blocked at all, when would it reach its goal?
let ideal_end_time = match car.crossing_state(our_dist, now, map) {
CarState::Crossing(time_int, _) => time_int.end,
CarState::Crossing { time_int, .. } => time_int.end,
_ => unreachable!(),
};
if ideal_end_time == now {
@ -876,7 +880,7 @@ impl DrivingSimState {
Command::UpdateCar(follower_id),
);
}
CarState::Crossing(_, _) => {
CarState::Crossing { .. } => {
// If the follower was still Crossing, they might not've been blocked by the
// leader yet. But recalculating their Crossing state isn't necessarily a no-op
// -- this could prevent them from suddenly warping past a blockage.
@ -901,7 +905,9 @@ impl DrivingSimState {
now,
ctx.map,
) {
CarState::Crossing(time, dist) => (time, dist),
CarState::Crossing {
time_int, dist_int, ..
} => (time_int, dist_int),
_ => unreachable!(),
};
assert!(new_time.end >= lc_time.end);
@ -1051,7 +1057,7 @@ impl DrivingSimState {
CarState::WaitingToAdvance { .. } => unreachable!(),
// They weren't blocked. Note that there's no way the Crossing state could
// jump forwards here; the leader vanished from the end of the traversable.
CarState::Crossing(_, _)
CarState::Crossing { .. }
| CarState::ChangingLanes { .. }
| CarState::Unparking { .. }
| CarState::Parking(_, _, _)
@ -1151,7 +1157,9 @@ impl DrivingSimState {
now,
ctx.map,
) {
CarState::Crossing(time, dist) => (time, dist),
CarState::Crossing {
time_int, dist_int, ..
} => (time_int, dist_int),
_ => unreachable!(),
};
@ -1366,7 +1374,7 @@ impl DrivingSimState {
id: cause,
waiting_for_turn: None,
status: CarStatus::Parked,
show_parking_intent: false,
intent: None,
on,
partly_on: Vec::new(),
label: Some("block".to_string()),
@ -1383,7 +1391,7 @@ impl DrivingSimState {
id: cause,
waiting_for_turn: None,
status: CarStatus::Parked,
show_parking_intent: false,
intent: None,
on,
partly_on: Vec::new(),
label: Some("block".to_string()),

View File

@ -370,7 +370,7 @@ impl ParkingSim for NormalParkingSimState {
id: p.vehicle.id,
waiting_for_turn: None,
status: CarStatus::Parked,
show_parking_intent: false,
intent: None,
on: Traversable::Lane(lane),
partly_on: Vec::new(),
label: None,
@ -392,7 +392,7 @@ impl ParkingSim for NormalParkingSimState {
id: p.vehicle.id,
waiting_for_turn: None,
status: CarStatus::Parked,
show_parking_intent: false,
intent: None,
// Just used for z-order
on: Traversable::Lane(pl.driving_pos.lane()),
partly_on: Vec::new(),

View File

@ -228,7 +228,11 @@ impl Queue {
}
self.geom_len
}
CarState::Crossing(ref time_int, ref dist_int) => {
CarState::Crossing {
ref time_int,
ref dist_int,
..
} => {
// TODO Why percent_clamp_end? We process car updates in any order, so we might
// calculate this before moving this car from Crossing to another state.
dist_int.lerp(time_int.percent_clamp_end(now)).min(bound)
@ -575,7 +579,11 @@ fn dump_cars(dists: &[QueueEntry], cars: &FixedMap<CarID, Car>, id: Traversable,
println!("- {:?} @ {}..{}", entry.member, entry.front, entry.back);
match entry.member {
Queued::Vehicle(id) => match cars[&id].state {
CarState::Crossing(ref time_int, ref dist_int) => {
CarState::Crossing {
ref time_int,
ref dist_int,
..
} => {
println!(
" Going {} .. {} during {} .. {}",
dist_int.start, dist_int.end, time_int.start, time_int.end

View File

@ -12,9 +12,9 @@ use map_model::{
use crate::sim::Ctx;
use crate::{
AgentID, AgentProperties, Command, CommutersVehiclesCounts, CreatePedestrian, DistanceInterval,
DrawPedCrowdInput, DrawPedestrianInput, Event, IntersectionSimState, ParkedCar, ParkingSpot,
PedCrowdLocation, PedestrianID, PersonID, Scheduler, SidewalkPOI, SidewalkSpot, TimeInterval,
TransitSimState, TripID, TripManager, UnzoomedAgent,
DrawPedCrowdInput, DrawPedestrianInput, Event, Intent, IntersectionSimState, ParkedCar,
ParkingSpot, PedCrowdLocation, PedestrianID, PersonID, Scheduler, SidewalkPOI, SidewalkSpot,
TimeInterval, TransitSimState, TripID, TripManager, UnzoomedAgent,
};
const TIME_TO_START_BIKING: Duration = Duration::const_seconds(30.0);
@ -60,13 +60,14 @@ impl WalkingSimState {
let mut ped = Pedestrian {
id: params.id,
// Temporary bogus thing
state: PedState::Crossing(
DistanceInterval::new_walking(Distance::ZERO, Distance::meters(1.0)),
TimeInterval::new(
state: PedState::Crossing {
dist_int: DistanceInterval::new_walking(Distance::ZERO, Distance::meters(1.0)),
time_int: TimeInterval::new(
Time::START_OF_DAY,
Time::START_OF_DAY + Duration::seconds(1.0),
),
),
steep_uphill: false,
},
speed: params.speed,
total_blocked_time: Duration::ZERO,
started_at: now,
@ -129,7 +130,7 @@ impl WalkingSimState {
) {
let mut ped = self.peds.get_mut(&id).unwrap();
match ped.state {
PedState::Crossing(ref dist_int, _) => {
PedState::Crossing { ref dist_int, .. } => {
if ped.path.is_last_step() {
match ped.goal.connection {
SidewalkPOI::ParkingSpot(spot) => {
@ -371,9 +372,11 @@ impl WalkingSimState {
}*/
let current_state_dist = match p.state {
PedState::Crossing(ref dist_int, ref time_int) => {
time_int.percent(now) * dist_int.length()
}
PedState::Crossing {
ref dist_int,
ref time_int,
..
} => time_int.percent(now) * dist_int.length(),
// We're at the beginning of our trip and are only walking along a driveway or biking
// connection
PedState::LeavingBuilding(_, _)
@ -448,7 +451,7 @@ impl WalkingSimState {
let dist = ped.get_dist_along(now, map);
match ped.state {
PedState::Crossing(ref dist_int, _) => {
PedState::Crossing { ref dist_int, .. } => {
if dist_int.start < dist_int.end {
forwards.push((*id, dist));
} else {
@ -628,18 +631,26 @@ impl Pedestrian {
}
};
let dist_int = DistanceInterval::new_walking(start_dist, end_dist);
let speed = self.path.current_step().as_traversable().max_speed_along(
Some(self.speed),
PathConstraints::Pedestrian,
map,
);
let (speed, percent_incline) = self
.path
.current_step()
.as_traversable()
.max_speed_and_incline_along(Some(self.speed), PathConstraints::Pedestrian, map);
let time_int = TimeInterval::new(start_time, start_time + dist_int.length() / speed);
PedState::Crossing(dist_int, time_int)
PedState::Crossing {
dist_int,
time_int,
steep_uphill: percent_incline >= 0.08,
}
}
fn get_dist_along(&self, now: Time, map: &Map) -> Distance {
match self.state {
PedState::Crossing(ref dist_int, ref time_int) => dist_int.lerp(time_int.percent(now)),
PedState::Crossing {
ref dist_int,
ref time_int,
..
} => dist_int.lerp(time_int.percent(now)),
PedState::WaitingToTurn(dist, _) => dist,
PedState::LeavingBuilding(b, _) | PedState::EnteringBuilding(b, _) => {
map.get_b(b).sidewalk_pos.dist_along()
@ -661,8 +672,13 @@ impl Pedestrian {
} else {
270.0
};
let mut intent = None;
let (pos, facing) = match self.state {
PedState::Crossing(ref dist_int, ref time_int) => {
PedState::Crossing {
ref dist_int,
ref time_int,
steep_uphill,
} => {
let percent = if now > time_int.end {
1.0
} else {
@ -677,6 +693,9 @@ impl Pedestrian {
} else {
orig_angle.opposite()
};
if steep_uphill {
intent = Some(Intent::SteepUphill);
}
(
pos.project_away(SIDEWALK_THICKNESS / 4.0, facing.rotate_degs(angle_offset)),
facing,
@ -758,6 +777,7 @@ impl Pedestrian {
PedState::WaitingToTurn(_, _) => Some(self.path.next_step().as_turn()),
_ => None,
},
intent,
preparing_bike: matches!(
self.state,
PedState::StartingToBike(_, _, _) | PedState::FinishingBiking(_, _, _)
@ -817,7 +837,11 @@ impl Pedestrian {
#[derive(Serialize, Deserialize, Debug, Clone)]
enum PedState {
Crossing(DistanceInterval, TimeInterval),
Crossing {
dist_int: DistanceInterval,
time_int: TimeInterval,
steep_uphill: bool,
},
/// The Distance is either 0 or the current traversable's length. The Time is blocked_since.
WaitingToTurn(Distance, Time),
LeavingBuilding(BuildingID, TimeInterval),
@ -832,7 +856,7 @@ enum PedState {
impl PedState {
fn get_end_time(&self) -> Time {
match self {
PedState::Crossing(_, ref time_int) => time_int.end,
PedState::Crossing { ref time_int, .. } => time_int.end,
PedState::WaitingToTurn(_, _) => unreachable!(),
PedState::LeavingBuilding(_, ref time_int) => time_int.end,
PedState::EnteringBuilding(_, ref time_int) => time_int.end,

View File

@ -11,6 +11,7 @@ pub struct DrawPedestrianInput {
pub pos: Pt2D,
pub facing: Angle,
pub waiting_for_turn: Option<TurnID>,
pub intent: Option<Intent>,
pub preparing_bike: bool,
pub waiting_for_bus: bool,
pub on: Traversable,
@ -37,7 +38,7 @@ pub struct DrawCarInput {
pub id: CarID,
pub waiting_for_turn: Option<TurnID>,
pub status: CarStatus,
pub show_parking_intent: bool,
pub intent: Option<Intent>,
/// Front of the car
pub on: Traversable,
/// Possibly the rest
@ -56,6 +57,13 @@ pub enum CarStatus {
Parked,
}
/// Shows an agent's current inner intention or thoughts.
#[derive(Clone, PartialEq)]
pub enum Intent {
Parking,
SteepUphill,
}
pub struct UnzoomedAgent {
pub id: AgentID,
pub pos: Pt2D,