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 { PrebakeSummary {
map: scenario.map_name.describe(), map: scenario.map_name.describe(),
scenario: scenario.scenario_name.clone(), scenario: scenario.scenario_name,
finished_trips, finished_trips,
cancelled_trips, cancelled_trips,
total_trip_duration_seconds, total_trip_duration_seconds,

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use geom::{ArrowCap, Circle, Distance, Line, PolyLine, Polygon, Pt2D}; use geom::{ArrowCap, Circle, Distance, Line, PolyLine, Polygon, Pt2D};
use map_model::{Map, SIDEWALK_THICKNESS}; use map_model::{Map, SIDEWALK_THICKNESS};
use sim::{CarID, DrawCarInput, Sim}; use sim::{CarID, DrawCarInput, Intent, Sim};
use widgetry::{Drawable, GeomBatch, GfxCtx, Prerender}; use widgetry::{Drawable, GeomBatch, GfxCtx, Prerender};
use crate::colors::ColorScheme; 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 let zorder = input
.partly_on .partly_on
.into_iter() .into_iter()

View File

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

View File

@ -1,6 +1,6 @@
use geom::{ArrowCap, Circle, Distance, PolyLine, Polygon, Pt2D}; use geom::{ArrowCap, Circle, Distance, PolyLine, Polygon, Pt2D};
use map_model::{DrivingSide, Map, SIDEWALK_THICKNESS}; 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 widgetry::{Color, Drawable, GeomBatch, GfxCtx, Line, Prerender, Text};
use crate::colors::ColorScheme; 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 { DrawPedestrian {
id: input.id, id: input.id,
body_circle, body_circle,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -228,7 +228,11 @@ impl Queue {
} }
self.geom_len 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 // 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. // calculate this before moving this car from Crossing to another state.
dist_int.lerp(time_int.percent_clamp_end(now)).min(bound) 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); println!("- {:?} @ {}..{}", entry.member, entry.front, entry.back);
match entry.member { match entry.member {
Queued::Vehicle(id) => match cars[&id].state { Queued::Vehicle(id) => match cars[&id].state {
CarState::Crossing(ref time_int, ref dist_int) => { CarState::Crossing {
ref time_int,
ref dist_int,
..
} => {
println!( println!(
" Going {} .. {} during {} .. {}", " Going {} .. {} during {} .. {}",
dist_int.start, dist_int.end, time_int.start, time_int.end 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::sim::Ctx;
use crate::{ use crate::{
AgentID, AgentProperties, Command, CommutersVehiclesCounts, CreatePedestrian, DistanceInterval, AgentID, AgentProperties, Command, CommutersVehiclesCounts, CreatePedestrian, DistanceInterval,
DrawPedCrowdInput, DrawPedestrianInput, Event, IntersectionSimState, ParkedCar, ParkingSpot, DrawPedCrowdInput, DrawPedestrianInput, Event, Intent, IntersectionSimState, ParkedCar,
PedCrowdLocation, PedestrianID, PersonID, Scheduler, SidewalkPOI, SidewalkSpot, TimeInterval, ParkingSpot, PedCrowdLocation, PedestrianID, PersonID, Scheduler, SidewalkPOI, SidewalkSpot,
TransitSimState, TripID, TripManager, UnzoomedAgent, TimeInterval, TransitSimState, TripID, TripManager, UnzoomedAgent,
}; };
const TIME_TO_START_BIKING: Duration = Duration::const_seconds(30.0); const TIME_TO_START_BIKING: Duration = Duration::const_seconds(30.0);
@ -60,13 +60,14 @@ impl WalkingSimState {
let mut ped = Pedestrian { let mut ped = Pedestrian {
id: params.id, id: params.id,
// Temporary bogus thing // Temporary bogus thing
state: PedState::Crossing( state: PedState::Crossing {
DistanceInterval::new_walking(Distance::ZERO, Distance::meters(1.0)), dist_int: DistanceInterval::new_walking(Distance::ZERO, Distance::meters(1.0)),
TimeInterval::new( time_int: TimeInterval::new(
Time::START_OF_DAY, Time::START_OF_DAY,
Time::START_OF_DAY + Duration::seconds(1.0), Time::START_OF_DAY + Duration::seconds(1.0),
), ),
), steep_uphill: false,
},
speed: params.speed, speed: params.speed,
total_blocked_time: Duration::ZERO, total_blocked_time: Duration::ZERO,
started_at: now, started_at: now,
@ -129,7 +130,7 @@ impl WalkingSimState {
) { ) {
let mut ped = self.peds.get_mut(&id).unwrap(); let mut ped = self.peds.get_mut(&id).unwrap();
match ped.state { match ped.state {
PedState::Crossing(ref dist_int, _) => { PedState::Crossing { ref dist_int, .. } => {
if ped.path.is_last_step() { if ped.path.is_last_step() {
match ped.goal.connection { match ped.goal.connection {
SidewalkPOI::ParkingSpot(spot) => { SidewalkPOI::ParkingSpot(spot) => {
@ -371,9 +372,11 @@ impl WalkingSimState {
}*/ }*/
let current_state_dist = match p.state { let current_state_dist = match p.state {
PedState::Crossing(ref dist_int, ref time_int) => { PedState::Crossing {
time_int.percent(now) * dist_int.length() 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 // We're at the beginning of our trip and are only walking along a driveway or biking
// connection // connection
PedState::LeavingBuilding(_, _) PedState::LeavingBuilding(_, _)
@ -448,7 +451,7 @@ impl WalkingSimState {
let dist = ped.get_dist_along(now, map); let dist = ped.get_dist_along(now, map);
match ped.state { match ped.state {
PedState::Crossing(ref dist_int, _) => { PedState::Crossing { ref dist_int, .. } => {
if dist_int.start < dist_int.end { if dist_int.start < dist_int.end {
forwards.push((*id, dist)); forwards.push((*id, dist));
} else { } else {
@ -628,18 +631,26 @@ impl Pedestrian {
} }
}; };
let dist_int = DistanceInterval::new_walking(start_dist, end_dist); let dist_int = DistanceInterval::new_walking(start_dist, end_dist);
let speed = self.path.current_step().as_traversable().max_speed_along( let (speed, percent_incline) = self
Some(self.speed), .path
PathConstraints::Pedestrian, .current_step()
map, .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); 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 { fn get_dist_along(&self, now: Time, map: &Map) -> Distance {
match self.state { 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::WaitingToTurn(dist, _) => dist,
PedState::LeavingBuilding(b, _) | PedState::EnteringBuilding(b, _) => { PedState::LeavingBuilding(b, _) | PedState::EnteringBuilding(b, _) => {
map.get_b(b).sidewalk_pos.dist_along() map.get_b(b).sidewalk_pos.dist_along()
@ -661,8 +672,13 @@ impl Pedestrian {
} else { } else {
270.0 270.0
}; };
let mut intent = None;
let (pos, facing) = match self.state { 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 { let percent = if now > time_int.end {
1.0 1.0
} else { } else {
@ -677,6 +693,9 @@ impl Pedestrian {
} else { } else {
orig_angle.opposite() orig_angle.opposite()
}; };
if steep_uphill {
intent = Some(Intent::SteepUphill);
}
( (
pos.project_away(SIDEWALK_THICKNESS / 4.0, facing.rotate_degs(angle_offset)), pos.project_away(SIDEWALK_THICKNESS / 4.0, facing.rotate_degs(angle_offset)),
facing, facing,
@ -758,6 +777,7 @@ impl Pedestrian {
PedState::WaitingToTurn(_, _) => Some(self.path.next_step().as_turn()), PedState::WaitingToTurn(_, _) => Some(self.path.next_step().as_turn()),
_ => None, _ => None,
}, },
intent,
preparing_bike: matches!( preparing_bike: matches!(
self.state, self.state,
PedState::StartingToBike(_, _, _) | PedState::FinishingBiking(_, _, _) PedState::StartingToBike(_, _, _) | PedState::FinishingBiking(_, _, _)
@ -817,7 +837,11 @@ impl Pedestrian {
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
enum PedState { 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. /// The Distance is either 0 or the current traversable's length. The Time is blocked_since.
WaitingToTurn(Distance, Time), WaitingToTurn(Distance, Time),
LeavingBuilding(BuildingID, TimeInterval), LeavingBuilding(BuildingID, TimeInterval),
@ -832,7 +856,7 @@ enum PedState {
impl PedState { impl PedState {
fn get_end_time(&self) -> Time { fn get_end_time(&self) -> Time {
match self { match self {
PedState::Crossing(_, ref time_int) => time_int.end, PedState::Crossing { ref time_int, .. } => time_int.end,
PedState::WaitingToTurn(_, _) => unreachable!(), PedState::WaitingToTurn(_, _) => unreachable!(),
PedState::LeavingBuilding(_, ref time_int) => time_int.end, PedState::LeavingBuilding(_, ref time_int) => time_int.end,
PedState::EnteringBuilding(_, 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 pos: Pt2D,
pub facing: Angle, pub facing: Angle,
pub waiting_for_turn: Option<TurnID>, pub waiting_for_turn: Option<TurnID>,
pub intent: Option<Intent>,
pub preparing_bike: bool, pub preparing_bike: bool,
pub waiting_for_bus: bool, pub waiting_for_bus: bool,
pub on: Traversable, pub on: Traversable,
@ -37,7 +38,7 @@ pub struct DrawCarInput {
pub id: CarID, pub id: CarID,
pub waiting_for_turn: Option<TurnID>, pub waiting_for_turn: Option<TurnID>,
pub status: CarStatus, pub status: CarStatus,
pub show_parking_intent: bool, pub intent: Option<Intent>,
/// Front of the car /// Front of the car
pub on: Traversable, pub on: Traversable,
/// Possibly the rest /// Possibly the rest
@ -56,6 +57,13 @@ pub enum CarStatus {
Parked, Parked,
} }
/// Shows an agent's current inner intention or thoughts.
#[derive(Clone, PartialEq)]
pub enum Intent {
Parking,
SteepUphill,
}
pub struct UnzoomedAgent { pub struct UnzoomedAgent {
pub id: AgentID, pub id: AgentID,
pub pos: Pt2D, pub pos: Pt2D,