starting to model pedestrians. spawn and draw them.

This commit is contained in:
Dustin Carlino 2018-07-11 14:01:12 -07:00
parent 6f00507740
commit e5de791fa7
12 changed files with 300 additions and 64 deletions

View File

@ -28,6 +28,6 @@
## pedestrians
- pedestrians with different speeds, moving bidirectionally on everything
- pathfinding and stepping
- make them start and end at buildings
- trim the sidewalk path to the edge of a building

View File

@ -208,6 +208,15 @@ but the fact that sidewalks are oriented is actually convenient, it makes it cle
what if we just add a bit and make turns bidirectional? still express them in the directional way?
if we're looking at turns from a road that's a sidewalk, bake in some extra logic?
## Pedestrian modeling
- Is it useful to distinguish CarID and PedestrianID? What about when an agent has a multi-modal trip? Probably become AgentID later.
- Worth mentioning that I'm assuming pedestrians don't queue or collide. In
most reasonable sidewalk cases, this is true. Don't need to model more
detailed movement. As a consequence of this, crosswalk turns never conflict.
Assume people can weave.
## Stop signs
How to depict stop signs? Each driving lane has a priority... asap go or full

View File

@ -240,6 +240,12 @@
0.29857337,
1.0
],
"Pedestrian": [
0.2,
0.2,
1.0,
1.0
],
"TrafficSignalBox": [
0.0,
0.0,

View File

@ -57,6 +57,8 @@ pub enum Colors {
StuckCar,
ParkedCar,
Pedestrian,
TrafficSignalBox,
TrafficSignalGreen,
TrafficSignalYellow,

View File

@ -10,7 +10,7 @@ use map_model;
use map_model::{BuildingID, IntersectionID, Map, RoadID, TurnID};
use piston::input::{Button, Key, ReleaseEvent};
use render;
use sim::{CarID, Sim};
use sim::{CarID, PedestrianID, Sim};
use std::collections::HashSet;
// TODO only used for mouseover, which happens in order anyway...
@ -21,6 +21,7 @@ pub enum ID {
Turn(TurnID),
Building(BuildingID),
Car(CarID),
Pedestrian(PedestrianID),
//Parcel(ParcelID),
}
@ -34,6 +35,7 @@ pub enum SelectionState {
SelectedBuilding(BuildingID),
SelectedTurn(TurnID),
SelectedCar(CarID),
SelectedPedestrian(PedestrianID),
}
impl SelectionState {
@ -51,6 +53,7 @@ impl SelectionState {
Some(ID::Building(id)) => SelectionState::SelectedBuilding(id),
Some(ID::Turn(id)) => SelectionState::SelectedTurn(id),
Some(ID::Car(id)) => SelectionState::SelectedCar(id),
Some(ID::Pedestrian(id)) => SelectionState::SelectedPedestrian(id),
None => SelectionState::Empty,
}
}
@ -154,6 +157,9 @@ impl SelectionState {
SelectionState::SelectedCar(id) => {
canvas.draw_mouse_tooltip(g, &sim.car_tooltip(id));
}
SelectionState::SelectedPedestrian(id) => {
canvas.draw_mouse_tooltip(g, &sim.ped_tooltip(id));
}
}
}

View File

@ -29,7 +29,7 @@ use plugins::traffic_signal_editor::TrafficSignalEditor;
use plugins::turn_colors::TurnColors;
use plugins::warp::WarpState;
use render;
use sim::{CarID, CarState};
use sim::{CarID, CarState, PedestrianID};
use std::collections::HashMap;
use std::process;
@ -171,6 +171,11 @@ impl UI {
return Some(ID::Car(c.id));
}
}
for p in &self.sim_ctrl.sim.get_draw_peds_on_road(r.id, &self.map) {
if p.contains_pt(x, y) {
return Some(ID::Pedestrian(p.id));
}
}
}
if self.show_icons.is_enabled() {
@ -191,6 +196,11 @@ impl UI {
return Some(ID::Car(c.id));
}
}
for p in &self.sim_ctrl.sim.get_draw_peds_on_turn(*t, &self.map) {
if p.contains_pt(x, y) {
return Some(ID::Pedestrian(p.id));
}
}
}
if i.contains_pt(x, y) {
@ -206,6 +216,11 @@ impl UI {
return Some(ID::Car(c.id));
}
}
for p in &self.sim_ctrl.sim.get_draw_peds_on_road(r.id, &self.map) {
if p.contains_pt(x, y) {
return Some(ID::Pedestrian(p.id));
}
}
if r.road_contains_pt(x, y) {
return Some(ID::Road(r.id));
@ -322,6 +337,10 @@ impl UI {
CarState::Parked => self.cs.get(Colors::ParkedCar),
}
}
fn color_ped(&self, _id: PedestrianID) -> Color {
self.cs.get(Colors::Pedestrian)
}
}
impl gui::GUI for UI {
@ -416,8 +435,12 @@ impl gui::GUI for UI {
return gui::EventLoopMode::InputOnly;
}
if self.sim_ctrl.sim.total_cars() == 0 {
if input.unimportant_key_pressed(Key::S, "Seed the map with 50% parked cars") {
if input.unimportant_key_pressed(
Key::S,
"Seed the map with 50% parked cars and some pedestrians",
) {
self.sim_ctrl.sim.seed_parked_cars(0.5);
self.sim_ctrl.sim.seed_pedestrians(&self.map, 1000);
return gui::EventLoopMode::InputOnly;
}
} else {
@ -442,11 +465,9 @@ impl gui::GUI for UI {
return gui::EventLoopMode::InputOnly;
}
if self.map.get_r(id).lane_type == map_model::LaneType::Driving {
if input.key_pressed(Key::A, "Press A to start a parked car on this road") {
self.sim_ctrl.sim.start_parked_car(&self.map, id);
return gui::EventLoopMode::InputOnly;
}
if input.key_pressed(Key::A, "Press A to start something on this road") {
self.sim_ctrl.sim.start_agent(&self.map, id);
return gui::EventLoopMode::InputOnly;
}
}
SelectionState::SelectedIntersection(id) => {
@ -538,6 +559,9 @@ impl gui::GUI for UI {
for c in &self.sim_ctrl.sim.get_draw_cars_on_turn(t.id, &self.map) {
c.draw(g, self.color_car(c.id));
}
for p in &self.sim_ctrl.sim.get_draw_peds_on_turn(t.id, &self.map) {
p.draw(g, self.color_ped(p.id));
}
}
}
@ -545,6 +569,9 @@ impl gui::GUI for UI {
for c in &self.sim_ctrl.sim.get_draw_cars_on_road(r.id, &self.map) {
c.draw(g, self.color_car(c.id));
}
for p in &self.sim_ctrl.sim.get_draw_peds_on_road(r.id, &self.map) {
p.draw(g, self.color_ped(p.id));
}
}
if self.show_buildings.is_enabled() {

View File

@ -254,3 +254,5 @@ fn shift_short_polyline_equivalence() {
Some(PolyLine::new(vec![l.pt1(), l.pt2()]))
);
}
// TODO test that shifting lines and polylines is a reversible operation

31
sim/src/draw_ped.rs Normal file
View File

@ -0,0 +1,31 @@
use ezgui::GfxCtx;
use geom::Pt2D;
use graphics;
use map_model::geometry;
use PedestrianID;
const RADIUS: f64 = 1.0;
// TODO should this live in editor/render?
// TODO show turns waited for
pub struct DrawPedestrian {
pub id: PedestrianID,
circle: [f64; 4],
}
impl DrawPedestrian {
pub(crate) fn new(id: PedestrianID, pos: Pt2D) -> DrawPedestrian {
DrawPedestrian {
id,
circle: geometry::circle(pos.x(), pos.y(), RADIUS),
}
}
pub fn draw(&self, g: &mut GfxCtx, color: graphics::types::Color) {
g.draw_ellipse(color, self.circle);
}
pub fn contains_pt(&self, x: f64, y: f64) -> bool {
geometry::point_in_circle(x, y, [self.circle[0], self.circle[1]], RADIUS)
}
}

View File

@ -12,57 +12,13 @@ use rand::Rng;
use std;
use std::collections::{BTreeMap, HashSet, VecDeque};
use std::f64;
use {CarID, Tick, SPEED_LIMIT};
use {CarID, On, Tick, SPEED_LIMIT};
const FOLLOWING_DISTANCE: si::Meter<f64> = si::Meter {
value_unsafe: 8.0,
_marker: std::marker::PhantomData,
};
// TODO this name isn't quite right :)
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub(crate) enum On {
Road(RoadID),
Turn(TurnID),
}
impl On {
pub(crate) fn as_road(&self) -> RoadID {
match self {
&On::Road(id) => id,
&On::Turn(_) => panic!("not a road"),
}
}
pub(crate) fn as_turn(&self) -> TurnID {
match self {
&On::Turn(id) => id,
&On::Road(_) => panic!("not a turn"),
}
}
fn maybe_turn(&self) -> Option<TurnID> {
match self {
&On::Turn(id) => Some(id),
&On::Road(_) => None,
}
}
fn length(&self, map: &Map) -> si::Meter<f64> {
match self {
&On::Road(id) => map.get_r(id).length(),
&On::Turn(id) => map.get_t(id).length(),
}
}
fn dist_along(&self, dist: si::Meter<f64>, map: &Map) -> (Pt2D, Angle) {
match self {
&On::Road(id) => map.get_r(id).dist_along(dist),
&On::Turn(id) => map.get_t(id).dist_along(dist),
}
}
}
// This represents an actively driving car, not a parked one
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) struct Car {

View File

@ -15,12 +15,16 @@ extern crate serde;
extern crate serde_derive;
mod draw_car;
mod draw_ped;
mod driving;
mod intersections;
mod parking;
mod sim;
mod walking;
use dimensioned::si;
use geom::{Angle, Pt2D};
use map_model::{Map, RoadID, TurnID};
pub use sim::{Benchmark, CarState, Sim};
use std::fmt;
@ -33,6 +37,15 @@ impl fmt::Display for CarID {
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct PedestrianID(pub usize);
impl fmt::Display for PedestrianID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "PedestrianID({0})", self.0)
}
}
pub const TIMESTEP: si::Second<f64> = si::Second {
value_unsafe: 0.1,
_marker: std::marker::PhantomData,
@ -73,3 +86,47 @@ impl std::fmt::Display for Tick {
write!(f, "{0:.1}s", (self.0 as f64) * TIMESTEP.value_unsafe)
}
}
// TODO this name isn't quite right :)
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub(crate) enum On {
Road(RoadID),
Turn(TurnID),
}
impl On {
pub(crate) fn as_road(&self) -> RoadID {
match self {
&On::Road(id) => id,
&On::Turn(_) => panic!("not a road"),
}
}
pub(crate) fn as_turn(&self) -> TurnID {
match self {
&On::Turn(id) => id,
&On::Road(_) => panic!("not a turn"),
}
}
fn maybe_turn(&self) -> Option<TurnID> {
match self {
&On::Turn(id) => Some(id),
&On::Road(_) => None,
}
}
fn length(&self, map: &Map) -> si::Meter<f64> {
match self {
&On::Road(id) => map.get_r(id).length(),
&On::Turn(id) => map.get_t(id).length(),
}
}
fn dist_along(&self, dist: si::Meter<f64>, map: &Map) -> (Pt2D, Angle) {
match self {
&On::Road(id) => map.get_r(id).dist_along(dist),
&On::Turn(id) => map.get_t(id).dist_along(dist),
}
}
}

View File

@ -3,13 +3,15 @@
use control::ControlMap;
use dimensioned::si;
use draw_car::DrawCar;
use draw_ped::DrawPedestrian;
use driving::DrivingSimState;
use map_model::{LaneType, Map, RoadID, TurnID};
use parking::ParkingSimState;
use rand::{FromEntropy, Rng, SeedableRng, XorShiftRng};
use std::f64;
use std::time::{Duration, Instant};
use {CarID, Tick};
use walking::WalkingSimState;
use {CarID, PedestrianID, Tick};
pub enum CarState {
Moving,
@ -25,11 +27,12 @@ pub struct Sim {
#[derivative(PartialEq = "ignore")]
rng: XorShiftRng,
pub time: Tick,
id_counter: usize,
car_id_counter: usize,
debug: Option<CarID>,
driving_state: DrivingSimState,
parking_state: ParkingSimState,
walking_state: WalkingSimState,
}
impl Sim {
@ -43,19 +46,20 @@ impl Sim {
rng,
driving_state: DrivingSimState::new(map),
parking_state: ParkingSimState::new(map),
walking_state: WalkingSimState::new(),
time: Tick::zero(),
id_counter: 0,
car_id_counter: 0,
debug: None,
}
}
pub fn total_cars(&self) -> usize {
self.id_counter
self.car_id_counter
}
pub fn seed_parked_cars(&mut self, percent: f64) {
self.parking_state
.seed_random_cars(&mut self.rng, percent, &mut self.id_counter)
.seed_random_cars(&mut self.rng, percent, &mut self.car_id_counter)
}
pub fn start_many_parked_cars(&mut self, map: &Map, num_cars: usize) {
@ -77,18 +81,19 @@ impl Sim {
let n = num_cars.min(driving_lanes.len());
let mut actual = 0;
for i in 0..n {
if self.start_parked_car(map, driving_lanes[i]) {
if self.start_agent(map, driving_lanes[i]) {
actual += 1;
}
}
println!("Started {} parked cars of requested {}", actual, n);
}
pub fn start_parked_car(&mut self, map: &Map, id: RoadID) -> bool {
pub fn start_agent(&mut self, map: &Map, id: RoadID) -> bool {
let (driving_lane, parking_lane) = match map.get_r(id).lane_type {
LaneType::Sidewalk => {
println!("{} is a sidewalk, can't start a parked car here", id);
return false;
self.walking_state.seed_pedestrian(id);
println!("Spawned a pedestrian at {}", id);
return true;
}
LaneType::Driving => {
if let Some(parking) = map.find_parking_lane(id) {
@ -125,11 +130,17 @@ impl Sim {
}
}
pub fn seed_pedestrians(&mut self, map: &Map, num: usize) {
self.walking_state.seed_pedestrians(&mut self.rng, map, num);
println!("Spawned {} pedestrians", num);
}
pub fn step(&mut self, map: &Map, control_map: &ControlMap) {
self.time.increment();
// TODO Vanish action should become Park
self.driving_state.step(self.time, map, control_map);
self.walking_state.step(self.time, map, control_map);
}
pub fn get_car_state(&self, c: CarID) -> CarState {
@ -144,6 +155,7 @@ impl Sim {
}
}
// TODO maybe just DrawAgent instead? should caller care?
pub fn get_draw_cars_on_road(&self, r: RoadID, map: &Map) -> Vec<DrawCar> {
match map.get_r(r).lane_type {
LaneType::Driving => {
@ -158,8 +170,16 @@ impl Sim {
self.driving_state.turns[t.0].get_draw_cars(self.time, &self.driving_state, map)
}
pub fn get_draw_peds_on_road(&self, r: RoadID, map: &Map) -> Vec<DrawPedestrian> {
self.walking_state.get_draw_peds_on_road(map.get_r(r))
}
pub fn get_draw_peds_on_turn(&self, t: TurnID, map: &Map) -> Vec<DrawPedestrian> {
self.walking_state.get_draw_peds_on_turn(map.get_t(t))
}
pub fn summary(&self) -> String {
// TODO also report parking state
// TODO also report parking state and walking state
let waiting = self.driving_state
.cars
.values()
@ -173,6 +193,10 @@ impl Sim {
)
}
pub fn ped_tooltip(&self, p: PedestrianID) -> Vec<String> {
vec![format!("Hello to {}", p)]
}
pub fn car_tooltip(&self, car: CarID) -> Vec<String> {
if let Some(driving) = self.driving_state.cars.get(&car) {
driving.tooltip_lines()

116
sim/src/walking.rs Normal file
View File

@ -0,0 +1,116 @@
use control::ControlMap;
use dimensioned::si;
use draw_ped::DrawPedestrian;
use map_model::{LaneType, Map, Road, RoadID, Turn, TurnID};
use multimap::MultiMap;
use rand::Rng;
use std;
use std::collections::VecDeque;
use {On, PedestrianID, Tick};
// TODO tune these!
// TODO make it vary, after we can easily serialize these
const SPEED: si::MeterPerSecond<f64> = si::MeterPerSecond {
value_unsafe: 0.9,
_marker: std::marker::PhantomData,
};
#[derive(Serialize, Deserialize)]
struct Pedestrian {
id: PedestrianID,
on: On,
// TODO si::Meter<f64> after serde support lands
// TODO or since Tick is deliberately not f64, have a better type for Meters.
dist_along: f64,
// Traveling along the road/turn in its original direction or not?
contraflow: bool,
// Head is the next road
path: VecDeque<RoadID>,
}
// TODO this is used for verifying sim state determinism, so it should actually check everything.
// the f64 prevents this from being derived.
impl PartialEq for Pedestrian {
fn eq(&self, other: &Pedestrian) -> bool {
self.id == other.id
}
}
impl Eq for Pedestrian {}
#[derive(Serialize, Deserialize, Derivative, PartialEq, Eq)]
pub(crate) struct WalkingSimState {
// Trying a different style than driving for storing things
peds_per_sidewalk: MultiMap<RoadID, Pedestrian>,
peds_per_turn: MultiMap<TurnID, Pedestrian>,
id_counter: usize,
}
impl WalkingSimState {
pub fn new() -> WalkingSimState {
WalkingSimState {
peds_per_sidewalk: MultiMap::new(),
peds_per_turn: MultiMap::new(),
id_counter: 0,
}
}
pub fn step(&mut self, time: Tick, map: &Map, control_map: &ControlMap) {
// TODO implement
}
pub fn get_draw_peds_on_road(&self, r: &Road) -> Vec<DrawPedestrian> {
let mut result = Vec::new();
for p in self.peds_per_sidewalk.get_vec(&r.id).unwrap_or(&Vec::new()) {
result.push(DrawPedestrian::new(
p.id,
r.dist_along(p.dist_along * si::M).0,
));
}
result
}
pub fn get_draw_peds_on_turn(&self, t: &Turn) -> Vec<DrawPedestrian> {
let mut result = Vec::new();
for p in self.peds_per_turn.get_vec(&t.id).unwrap_or(&Vec::new()) {
result.push(DrawPedestrian::new(
p.id,
t.dist_along(p.dist_along * si::M).0,
));
}
result
}
pub fn seed_pedestrians<R: Rng + ?Sized>(&mut self, rng: &mut R, map: &Map, num_peds: usize) {
let mut sidewalks: Vec<RoadID> = Vec::new();
for r in map.all_roads() {
if r.lane_type == LaneType::Sidewalk {
sidewalks.push(r.id);
}
}
for _i in 0..num_peds {
self.seed_pedestrian(*rng.choose(&sidewalks).unwrap());
}
}
pub fn seed_pedestrian(&mut self, r: RoadID) {
let id = PedestrianID(self.id_counter);
self.id_counter += 1;
self.peds_per_sidewalk.insert(
r,
Pedestrian {
id,
on: On::Road(r),
// TODO start next to a building path, or at least some random position
dist_along: 0.0,
// TODO should be based on first step
contraflow: true,
// TODO compute a path
path: VecDeque::new(),
},
);
}
}