From 9d58ee12a4caa8d185cd0898f39d00db00853208 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Fri, 16 Aug 2019 22:18:19 -0700 Subject: [PATCH] properly merging nearby pedestrians by intervals of dist_along --- editor/src/render/pedestrian.rs | 108 ++++++--------------- editor/src/sandbox/time_travel.rs | 26 ++++-- editor/src/ui.rs | 3 +- fix_map_geom/src/main.rs | 1 + sim/src/lib.rs | 2 +- sim/src/mechanics/walking.rs | 150 +++++++++++++++++++++++++++--- sim/src/render.rs | 16 +++- sim/src/sim.rs | 16 ++-- 8 files changed, 212 insertions(+), 110 deletions(-) diff --git a/editor/src/render/pedestrian.rs b/editor/src/render/pedestrian.rs index 1622117f26..5f3d03ec3d 100644 --- a/editor/src/render/pedestrian.rs +++ b/editor/src/render/pedestrian.rs @@ -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, - ) -> (Vec, Vec) { - 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 = Vec::new(); - let mut crowds: Vec = 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, - on: Traversable, -} - pub struct DrawPedCrowd { members: Vec, - 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 { diff --git a/editor/src/sandbox/time_travel.rs b/editor/src/sandbox/time_travel.rs index 2c25e695de..425b0b86d2 100644 --- a/editor/src/sandbox/time_travel.rs +++ b/editor/src/sandbox/time_travel.rs @@ -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 { + // TODO This cheats and doesn't handle crowds. :\ + fn get_draw_peds( + &self, + on: Traversable, + _map: &Map, + ) -> (Vec, Vec) { 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 { diff --git a/editor/src/ui.rs b/editor/src/ui.rs index efae334e32..9364e8a668 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -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, diff --git a/fix_map_geom/src/main.rs b/fix_map_geom/src/main.rs index c093d3ae37..9ccef28840 100644 --- a/fix_map_geom/src/main.rs +++ b/fix_map_geom/src/main.rs @@ -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 diff --git a/sim/src/lib.rs b/sim/src/lib.rs index 4343b26c0f..0a5b0a2a54 100644 --- a/sim/src/lib.rs +++ b/sim/src/lib.rs @@ -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}; diff --git a/sim/src/mechanics/walking.rs b/sim/src/mechanics/walking.rs index e26a270013..f0987d529f 100644 --- a/sim/src/mechanics/walking.rs +++ b/sim/src/mechanics/walking.rs @@ -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 { - 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, Vec) { + // 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 = MultiMap::new(); + + let mut loners: Vec = 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 = 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::>()), + ) + .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, Vec) { + 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) +} diff --git a/sim/src/render.rs b/sim/src/render.rs index d37d21ee2b..c5d8ee0a22 100644 --- a/sim/src/render.rs +++ b/sim/src/render.rs @@ -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, + 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; fn get_draw_ped(&self, id: PedestrianID, map: &Map) -> Option; fn get_draw_cars(&self, on: Traversable, map: &Map) -> Vec; - fn get_draw_peds(&self, on: Traversable, map: &Map) -> Vec; + fn get_draw_peds( + &self, + on: Traversable, + map: &Map, + ) -> (Vec, Vec); fn get_all_draw_cars(&self, map: &Map) -> Vec; fn get_all_draw_peds(&self, map: &Map) -> Vec; } diff --git a/sim/src/sim.rs b/sim/src/sim.rs index 003492a14c..7ef9da0715 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -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 { - self.walking.get_draw_peds(self.time, on, map) + fn get_draw_peds( + &self, + on: Traversable, + map: &Map, + ) -> (Vec, Vec) { + self.walking.get_draw_peds_on(self.time, on, map) } fn get_all_draw_cars(&self, map: &Map) -> Vec {