properly merging nearby pedestrians by intervals of dist_along

This commit is contained in:
Dustin Carlino 2019-08-16 22:18:19 -07:00
parent d0d7bb79e3
commit 9d58ee12a4
8 changed files with 212 additions and 110 deletions

View File

@ -1,9 +1,9 @@
use crate::helpers::{ColorScheme, ID};
use crate::render::{DrawCtx, DrawOptions, Renderable};
use crate::render::{DrawCtx, DrawOptions, Renderable, OUTLINE_THICKNESS};
use ezgui::{Color, Drawable, GeomBatch, GfxCtx, Prerender, Text};
use geom::{Circle, Distance, PolyLine, Polygon, Pt2D};
use map_model::{Map, Traversable, LANE_THICKNESS};
use sim::{DrawPedestrianInput, PedestrianID};
use geom::{Circle, Distance, PolyLine, Polygon};
use map_model::{Map, LANE_THICKNESS};
use sim::{DrawPedCrowdInput, DrawPedestrianInput, PedestrianID};
pub struct DrawPedestrian {
pub id: PedestrianID,
@ -110,61 +110,6 @@ impl DrawPedestrian {
draw_default: prerender.upload(draw_default),
}
}
pub fn aggregate_peds(
mut peds: Vec<DrawPedestrianInput>,
) -> (Vec<DrawPedestrianInput>, Vec<PedCrowd>) {
if peds.is_empty() {
return (Vec::new(), Vec::new());
}
// This'd be much cheaper to do by distance along the traversable, but it's a bit
// memory-expensive to plumb that out for this experiment. It also doesn't handle front
// paths as well.
// TODO But average position will wind up off the sidewalk. :P
let radius = LANE_THICKNESS / 4.0;
let mut loners: Vec<PedestrianID> = Vec::new();
let mut crowds: Vec<PedCrowd> = Vec::new();
let mut remaining: Vec<(PedestrianID, Pt2D)> = peds.iter().map(|p| (p.id, p.pos)).collect();
// We know everyone's on the same traversable.
let on = peds[0].on;
// TODO Incorrect and inefficient clustering
while !remaining.is_empty() {
let mut keep: Vec<(PedestrianID, Pt2D)> = Vec::new();
let mut combine: Vec<(PedestrianID, Pt2D)> = Vec::new();
let (base_id, base_pos) = remaining.pop().unwrap();
for (id, pos) in &remaining {
if base_pos.dist_to(*pos) <= 2.0 * radius {
combine.push((*id, *pos));
} else {
keep.push((*id, *pos));
}
}
if combine.is_empty() {
loners.push(base_id);
} else {
let mut positions = vec![base_pos];
let mut members = vec![base_id];
peds.retain(|p| p.id != base_id);
for (id, pos) in combine {
positions.push(pos);
members.push(id);
remaining.retain(|(rem_id, _)| *rem_id != id);
peds.retain(|p| p.id != id);
}
crowds.push(PedCrowd {
members,
avg_pos: Pt2D::center(&positions),
on,
});
}
}
(peds, crowds)
}
}
impl Renderable for DrawPedestrian {
@ -190,15 +135,10 @@ impl Renderable for DrawPedestrian {
}
}
pub struct PedCrowd {
avg_pos: Pt2D,
members: Vec<PedestrianID>,
on: Traversable,
}
pub struct DrawPedCrowd {
members: Vec<PedestrianID>,
body_circle: Circle,
blob: Polygon,
blob_pl: PolyLine,
zorder: isize,
draw_default: Drawable,
@ -206,14 +146,26 @@ pub struct DrawPedCrowd {
}
impl DrawPedCrowd {
pub fn new(input: PedCrowd, map: &Map, prerender: &Prerender, _: &ColorScheme) -> DrawPedCrowd {
let radius = LANE_THICKNESS / 4.0;
let body_circle = Circle::new(input.avg_pos, radius);
let mut batch = GeomBatch::new();
batch.push(Color::GREEN, body_circle.to_polygon());
pub fn new(
input: DrawPedCrowdInput,
map: &Map,
prerender: &Prerender,
cs: &ColorScheme,
) -> DrawPedCrowd {
// TODO front path
let pl_slice = input.on.exact_slice(input.low, input.high, map);
let pl_shifted = if input.contraflow {
pl_slice.shift_left(LANE_THICKNESS / 4.0).unwrap()
} else {
pl_slice.shift_right(LANE_THICKNESS / 4.0).unwrap()
};
let blob = pl_shifted.make_polygons(LANE_THICKNESS / 2.0);
let draw_default = prerender.upload_borrowed(vec![(cs.get("pedestrian"), &blob)]);
let mut label = Text::with_bg_color(None);
label.add_styled_line(
format!("{}", input.members.len()),
// Ideally "pedestrian head", but it looks really faded...
Some(Color::BLACK),
None,
Some(15),
@ -221,9 +173,10 @@ impl DrawPedCrowd {
DrawPedCrowd {
members: input.members,
body_circle,
blob_pl: pl_shifted,
blob,
zorder: input.on.get_zorder(map),
draw_default: prerender.upload(batch),
draw_default,
label,
}
}
@ -237,16 +190,17 @@ impl Renderable for DrawPedCrowd {
fn draw(&self, g: &mut GfxCtx, opts: &DrawOptions, _: &DrawCtx) {
if let Some(color) = opts.color(self.get_id()) {
g.draw_circle(color, &self.body_circle);
g.draw_polygon(color, &self.blob);
} else {
g.redraw(&self.draw_default);
}
g.draw_text_at_mapspace(&self.label, self.body_circle.center);
g.draw_text_at_mapspace(&self.label, self.blob.center());
}
fn get_outline(&self, _: &Map) -> Polygon {
// TODO thin ring
self.body_circle.to_polygon()
self.blob_pl
.to_thick_boundary(LANE_THICKNESS / 2.0, OUTLINE_THICKNESS)
.unwrap_or_else(|| self.blob.clone())
}
fn get_zorder(&self) -> isize {

View File

@ -6,7 +6,9 @@ use abstutil::MultiMap;
use ezgui::{hotkey, EventCtx, GfxCtx, ItemSlider, Key, Text};
use geom::Duration;
use map_model::{Map, Traversable};
use sim::{CarID, DrawCarInput, DrawPedestrianInput, GetDrawAgents, PedestrianID};
use sim::{
CarID, DrawCarInput, DrawPedCrowdInput, DrawPedestrianInput, GetDrawAgents, PedestrianID,
};
use std::collections::BTreeMap;
pub struct InactiveTimeTravel {
@ -151,14 +153,22 @@ impl GetDrawAgents for TimeTraveler {
.collect()
}
fn get_draw_peds(&self, on: Traversable, _map: &Map) -> Vec<DrawPedestrianInput> {
// TODO This cheats and doesn't handle crowds. :\
fn get_draw_peds(
&self,
on: Traversable,
_map: &Map,
) -> (Vec<DrawPedestrianInput>, Vec<DrawPedCrowdInput>) {
let state = self.get_current_state();
state
.peds_per_traversable
.get(on)
.iter()
.map(|id| state.peds[id].clone())
.collect()
(
state
.peds_per_traversable
.get(on)
.iter()
.map(|id| state.peds[id].clone())
.collect(),
Vec::new(),
)
}
fn get_all_draw_cars(&self, _map: &Map) -> Vec<DrawCarInput> {

View File

@ -346,8 +346,7 @@ impl UI {
for c in source.get_draw_cars(*on, map).into_iter() {
list.push(draw_vehicle(c, map, prerender, &self.cs));
}
let (loners, crowds) =
DrawPedestrian::aggregate_peds(source.get_draw_peds(*on, map));
let (loners, crowds) = source.get_draw_peds(*on, map);
for p in loners {
list.push(Box::new(DrawPedestrian::new(
p, step_count, map, prerender, &self.cs,

View File

@ -192,6 +192,7 @@ impl GUI for UI {
true
} else if menu.action("reset hints") {
self.hints.hints.clear();
self.hints.parking_overrides.clear();
true
} else {
false

View File

@ -23,7 +23,7 @@ pub(crate) use self::transit::TransitSimState;
pub use self::trips::{FinishedTrips, TripEnd, TripMode, TripStart, TripStatus};
pub(crate) use self::trips::{TripLeg, TripManager};
pub use crate::render::{
CarStatus, DrawCarInput, DrawPedestrianInput, GetDrawAgents, UnzoomedAgent,
CarStatus, DrawCarInput, DrawPedCrowdInput, DrawPedestrianInput, GetDrawAgents, UnzoomedAgent,
};
use abstutil::Cloneable;
use geom::{Distance, Duration, Pt2D, Speed};

View File

@ -1,5 +1,5 @@
use crate::{
AgentID, Command, CreatePedestrian, DistanceInterval, DrawPedestrianInput,
AgentID, Command, CreatePedestrian, DistanceInterval, DrawPedCrowdInput, DrawPedestrianInput,
IntersectionSimState, ParkingSimState, PedestrianID, Scheduler, SidewalkPOI, SidewalkSpot,
TimeInterval, TransitSimState, TripID, TripManager, TripPositions, UnzoomedAgent,
};
@ -86,19 +86,6 @@ impl WalkingSimState {
.collect()
}
pub fn get_draw_peds(
&self,
time: Duration,
on: Traversable,
map: &Map,
) -> Vec<DrawPedestrianInput> {
self.peds_per_traversable
.get(on)
.iter()
.map(|id| self.peds[id].get_draw_ped(time, map))
.collect()
}
pub fn update_ped(
&mut self,
id: PedestrianID,
@ -306,6 +293,94 @@ impl WalkingSimState {
.insert(ped.trip, ped.get_draw_ped(trip_positions.time, map).pos);
}
}
pub fn get_draw_peds_on(
&self,
time: Duration,
on: Traversable,
map: &Map,
) -> (Vec<DrawPedestrianInput>, Vec<DrawPedCrowdInput>) {
// Classify into direction-based groups or by building front path.
let mut forwards: Vec<(PedestrianID, Distance)> = Vec::new();
let mut backwards: Vec<(PedestrianID, Distance)> = Vec::new();
let mut front_path: MultiMap<BuildingID, (PedestrianID, Distance)> = MultiMap::new();
let mut loners: Vec<DrawPedestrianInput> = Vec::new();
for id in self.peds_per_traversable.get(on) {
let ped = &self.peds[id];
let dist = ped.get_dist_along(time, map);
match ped.state {
PedState::Crossing(ref dist_int, _) => {
if dist_int.start < dist_int.end {
forwards.push((*id, dist));
} else {
backwards.push((*id, dist));
}
}
PedState::WaitingToTurn(dist) => {
if dist == Distance::ZERO {
backwards.push((*id, dist));
} else {
forwards.push((*id, dist));
}
}
PedState::LeavingBuilding(b, _) | PedState::EnteringBuilding(b, _) => {
// TODO Group on front paths too.
if false {
// TODO Distance along the front path
front_path.insert(b, (*id, dist));
} else {
loners.push(ped.get_draw_ped(time, map));
}
}
PedState::StartingToBike(_, _, _)
| PedState::FinishingBiking(_, _, _)
| PedState::WaitingForBus => {
// The backwards half of the sidewalk is closer to the road.
backwards.push((*id, dist));
}
}
}
let mut crowds: Vec<DrawPedCrowdInput> = Vec::new();
let on_len = on.length(map);
// For each group, sort by distance along. Attempt to bundle into intervals.
for (idx, mut group) in vec![forwards, backwards]
.into_iter()
.chain(
front_path
.consume()
.values()
.map(|set| set.into_iter().cloned().collect::<Vec<_>>()),
)
.enumerate()
{
if group.is_empty() {
continue;
}
group.sort_by_key(|(_, dist)| *dist);
let (individs, these_crowds) = find_crowds(group, on);
for id in individs {
loners.push(self.peds[&id].get_draw_ped(time, map));
}
for mut crowd in these_crowds {
crowd.contraflow = idx == 1;
// Clamp the distance intervals.
if crowd.low < Distance::ZERO {
crowd.low = Distance::ZERO;
}
if crowd.high > on_len {
crowd.high = on_len;
}
crowds.push(crowd);
}
}
(loners, crowds)
}
}
#[derive(Serialize, Deserialize, PartialEq)]
@ -498,3 +573,50 @@ impl PedState {
}
}
}
// The crowds returned here may have low/high values extending up to radius past the real geometry.
fn find_crowds(
input: Vec<(PedestrianID, Distance)>,
on: Traversable,
) -> (Vec<PedestrianID>, Vec<DrawPedCrowdInput>) {
let mut loners = Vec::new();
let mut crowds = Vec::new();
let radius = LANE_THICKNESS / 4.0;
let mut current_crowd = DrawPedCrowdInput {
low: input[0].1 - radius,
high: input[0].1 + radius,
contraflow: false,
members: vec![input[0].0],
on,
};
for (id, dist) in input.into_iter().skip(1) {
// If the pedestrian circles would overlap at all,
if dist - radius <= current_crowd.high {
current_crowd.members.push(id);
current_crowd.high = dist + radius;
} else {
if current_crowd.members.len() == 1 {
loners.push(current_crowd.members[0]);
} else {
crowds.push(current_crowd);
}
// Reset current_crowd
current_crowd = DrawPedCrowdInput {
low: dist - radius,
high: dist + radius,
contraflow: false,
members: vec![id],
on,
};
}
}
// Handle the last bit
if current_crowd.members.len() == 1 {
loners.push(current_crowd.members[0]);
} else {
crowds.push(current_crowd);
}
(loners, crowds)
}

View File

@ -1,5 +1,5 @@
use crate::{CarID, PedestrianID};
use geom::{Angle, Duration, PolyLine, Pt2D};
use geom::{Angle, Distance, Duration, PolyLine, Pt2D};
use map_model::{Map, Traversable, TurnID};
// Intermediate structures so that sim and editor crates don't have a cyclic dependency.
@ -13,6 +13,14 @@ pub struct DrawPedestrianInput {
pub on: Traversable,
}
pub struct DrawPedCrowdInput {
pub low: Distance,
pub high: Distance,
pub contraflow: bool,
pub members: Vec<PedestrianID>,
pub on: Traversable,
}
#[derive(Clone)]
pub struct DrawCarInput {
pub id: CarID,
@ -49,7 +57,11 @@ pub trait GetDrawAgents {
fn get_draw_car(&self, id: CarID, map: &Map) -> Option<DrawCarInput>;
fn get_draw_ped(&self, id: PedestrianID, map: &Map) -> Option<DrawPedestrianInput>;
fn get_draw_cars(&self, on: Traversable, map: &Map) -> Vec<DrawCarInput>;
fn get_draw_peds(&self, on: Traversable, map: &Map) -> Vec<DrawPedestrianInput>;
fn get_draw_peds(
&self,
on: Traversable,
map: &Map,
) -> (Vec<DrawPedestrianInput>, Vec<DrawPedCrowdInput>);
fn get_all_draw_cars(&self, map: &Map) -> Vec<DrawCarInput>;
fn get_all_draw_peds(&self, map: &Map) -> Vec<DrawPedestrianInput>;
}

View File

@ -1,8 +1,8 @@
use crate::{
AgentID, CarID, Command, CreateCar, DrawCarInput, DrawPedestrianInput, DrivingGoal,
DrivingSimState, Event, FinishedTrips, GetDrawAgents, IntersectionSimState, ParkedCar,
ParkingSimState, ParkingSpot, PedestrianID, Router, Scheduler, TransitSimState, TripID,
TripLeg, TripManager, TripPositions, TripSpawner, TripSpec, TripStatus, UnzoomedAgent,
AgentID, CarID, Command, CreateCar, DrawCarInput, DrawPedCrowdInput, DrawPedestrianInput,
DrivingGoal, DrivingSimState, Event, FinishedTrips, GetDrawAgents, IntersectionSimState,
ParkedCar, ParkingSimState, ParkingSpot, PedestrianID, Router, Scheduler, TransitSimState,
TripID, TripLeg, TripManager, TripPositions, TripSpawner, TripSpec, TripStatus, UnzoomedAgent,
VehicleSpec, VehicleType, WalkingSimState, BUS_LENGTH,
};
use abstutil::{elapsed_seconds, Timer};
@ -287,8 +287,12 @@ impl GetDrawAgents for Sim {
.get_draw_cars_on(self.time, on, map, &self.transit)
}
fn get_draw_peds(&self, on: Traversable, map: &Map) -> Vec<DrawPedestrianInput> {
self.walking.get_draw_peds(self.time, on, map)
fn get_draw_peds(
&self,
on: Traversable,
map: &Map,
) -> (Vec<DrawPedestrianInput>, Vec<DrawPedCrowdInput>) {
self.walking.get_draw_peds_on(self.time, on, map)
}
fn get_all_draw_cars(&self, map: &Map) -> Vec<DrawCarInput> {