From d22aa87139ec5f54d5d29fd8e9714c24bd746f7f Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Fri, 31 Jul 2020 12:49:33 -0700 Subject: [PATCH] rearrange Building internals for #176. edits can affect building connections to driving/biking. just store the immutable stuff -- whether there's parking in the building, the connection to the sidewalk, and the physical driveway line. compute all the rest dynamically, so it responds to edits without effort. shouldn't be major behavior changes yet (besides maybe fixing some bugs involving edits) --- data/MANIFEST.txt | 26 ++++----- docs/history/history.md | 31 +++++++---- game/src/devtools/mapping.rs | 34 +++++++----- game/src/info/building.rs | 14 +++-- game/src/layer/parking.rs | 13 ++--- game/src/render/building.rs | 27 ++++------ game/src/render/pedestrian.rs | 12 ++--- game/src/sandbox/dashboards/commuter.rs | 7 ++- game/src/sandbox/gameplay/freeform.rs | 2 +- game/src/sandbox/gameplay/tutorial.rs | 2 +- geom/src/polyline.rs | 4 ++ importer/src/soundcast/trips.rs | 14 +++-- map_model/src/connectivity.rs | 4 +- map_model/src/lib.rs | 4 +- map_model/src/make/buildings.rs | 70 ++++++------------------ map_model/src/make/mod.rs | 10 ---- map_model/src/map.rs | 16 +++--- map_model/src/objects/building.rs | 71 +++++++++++++++---------- map_model/src/objects/lane.rs | 5 +- sim/src/lib.rs | 7 ++- sim/src/make/activity_model.rs | 4 +- sim/src/make/scenario.rs | 17 ++---- sim/src/make/spawner.rs | 2 +- sim/src/mechanics/car.rs | 4 +- sim/src/mechanics/parking.rs | 35 ++++++------ sim/src/mechanics/walking.rs | 67 ++++++++++++----------- sim/src/render.rs | 4 +- sim/src/router.rs | 29 +++++----- 28 files changed, 252 insertions(+), 283 deletions(-) diff --git a/data/MANIFEST.txt b/data/MANIFEST.txt index 140504ca8a..b932a167bf 100644 --- a/data/MANIFEST.txt +++ b/data/MANIFEST.txt @@ -16,9 +16,9 @@ data/input/raw_maps/south_seattle.bin,39f2484a46af741b8cc6d7f6e4965c3b,https://w data/input/raw_maps/udistrict.bin,01a9b18687030019c7d922752b3f1819,https://www.dropbox.com/s/omhrxg9y02oejx2/udistrict.bin.zip?dl=0 data/input/raw_maps/west_seattle.bin,39116eb1a0a07356f736ea5bee552cc9,https://www.dropbox.com/s/k0or6wmygcj5h1l/west_seattle.bin.zip?dl=0 data/input/screenshots/downtown.zip,3e34c19d20f8c6c7def49374bc32dcf1,https://www.dropbox.com/s/qawd35wz62m2acl/downtown.zip.zip?dl=0 -data/input/screenshots/huge_krakow.zip,c634e6ac360da746e1a9f4808ce83e8c,https://www.dropbox.com/s/dbzon7k5ukndtza/huge_krakow.zip.zip?dl=0 +data/input/screenshots/huge_krakow.zip,a0ba93beabbc214257d3b1de3bc5a57f,https://www.dropbox.com/s/dbzon7k5ukndtza/huge_krakow.zip.zip?dl=0 data/input/screenshots/lakeslice.zip,003047ad84f970daa936f2e572c2459b,https://www.dropbox.com/s/z0z96lsn7bunqfy/lakeslice.zip.zip?dl=0 -data/input/screenshots/montlake.zip,ef453f3a7d087ce0dc750103f8d55217,https://www.dropbox.com/s/ku5x06hdt0omway/montlake.zip.zip?dl=0 +data/input/screenshots/montlake.zip,e4432e5afecd317f0e5a6b17ff83c07d,https://www.dropbox.com/s/ku5x06hdt0omway/montlake.zip.zip?dl=0 data/input/screenshots/udistrict.zip,dabdbe5bded617635d94072e2963725f,https://www.dropbox.com/s/ecnt1tyza48y9o2/udistrict.zip.zip?dl=0 data/input/seattle/N47W122.hgt,0db4e23e51f7680538b0bbbc72208e07,https://www.dropbox.com/s/mmb4mgutwotijdw/N47W122.hgt.zip?dl=0 data/input/seattle/blockface.bin,add872bab9040ae911366328a230f8b5,https://www.dropbox.com/s/rxd2care60tbe75/blockface.bin.zip?dl=0 @@ -34,21 +34,21 @@ data/input/seattle/osm/south_seattle.osm,1107f8b545b09731c4619fde63bdbaed,https: data/input/seattle/osm/udistrict.osm,ed26f2405c07c63161e902bcb5049fab,https://www.dropbox.com/s/ozqk7lb5nn0wace/udistrict.osm.zip?dl=0 data/input/seattle/osm/washington-latest.osm.pbf,332c0bbd80621a52e3765381718f3454,https://www.dropbox.com/s/iitat4cnhj5dop8/washington-latest.osm.pbf.zip?dl=0 data/input/seattle/osm/west_seattle.osm,f829d6a1c87e782fafbc1552acc5a523,https://www.dropbox.com/s/3vlyve59lfo9s99/west_seattle.osm.zip?dl=0 -data/input/seattle/parcels.bin,8b5ce9b62edcc4ef15e97976416f4661,https://www.dropbox.com/s/in8a9hbhzvxyyy2/parcels.bin.zip?dl=0 +data/input/seattle/parcels.bin,88ca5d9c59bb7bd97db3de2bd709215c,https://www.dropbox.com/s/nk59387ysb84dcf/parcels.bin.zip?dl=0 data/input/seattle/parcels_urbansim.txt,db63d7d606e8702d12f9399e87e6a00f,https://www.dropbox.com/s/6g8rbsf200dssj3/parcels_urbansim.txt.zip?dl=0 data/input/seattle/popdat.bin,0fd10698d2c6bf41da3d57804c617d15,https://www.dropbox.com/s/iboctakleznvslq/popdat.bin.zip?dl=0 data/input/seattle/trips_2014.csv,d4a8e733045b28c0385fb81359d6df03,https://www.dropbox.com/s/5ppravwmk6bf20d/trips_2014.csv.zip?dl=0 data/system/cities/seattle.bin,eb9e9621e989247f8cbd7260594cb4b9,https://www.dropbox.com/s/l2srf7cb1d3bx20/seattle.bin.zip?dl=0 -data/system/maps/ballard.bin,85d4cff27f9cb3988e671e32d46c2790,https://www.dropbox.com/s/3ij8i0tczjrz57i/ballard.bin.zip?dl=0 -data/system/maps/berlin_center.bin,f6ce50c4fd6605d799a1749947384c47,https://www.dropbox.com/s/yissbkoxox4vpuw/berlin_center.bin.zip?dl=0 -data/system/maps/downtown.bin,149647d5a2a830c63713f209185f15fb,https://www.dropbox.com/s/bleeq53zz0lndyb/downtown.bin.zip?dl=0 -data/system/maps/huge_krakow.bin,a08fac162b5816e3fb49429bdef9fa0a,https://www.dropbox.com/s/thimax62jtydhvp/huge_krakow.bin.zip?dl=0 -data/system/maps/huge_seattle.bin,bd6d96c9ddd0d97b50cfb4d139395a42,https://www.dropbox.com/s/5kcynthbwju23aj/huge_seattle.bin.zip?dl=0 -data/system/maps/lakeslice.bin,71c042e4d50af4365230f390aed402ee,https://www.dropbox.com/s/2qnmdta35fmtt33/lakeslice.bin.zip?dl=0 -data/system/maps/montlake.bin,fa707f16525ec66141549859b193fb8d,https://www.dropbox.com/s/kj5phknx5ghi5fd/montlake.bin.zip?dl=0 -data/system/maps/south_seattle.bin,9b5761e98c66d36efc1c8f913259ea2c,https://www.dropbox.com/s/53lcpce4420fv5q/south_seattle.bin.zip?dl=0 -data/system/maps/udistrict.bin,64c575b6d693a4a54366b305ffd36552,https://www.dropbox.com/s/oa137355upa6ehs/udistrict.bin.zip?dl=0 -data/system/maps/west_seattle.bin,d919037c8eac40329e70ddf567db8415,https://www.dropbox.com/s/1ed36uaaomdhy02/west_seattle.bin.zip?dl=0 +data/system/maps/ballard.bin,00aed65d8920f8ecf3d0418f8d2ba022,https://www.dropbox.com/s/b7yv4haguj71fm0/ballard.bin.zip?dl=0 +data/system/maps/berlin_center.bin,20bdf638ae21fde2b59499c70d777b3c,https://www.dropbox.com/s/971gf8uzdgv8e2o/berlin_center.bin.zip?dl=0 +data/system/maps/downtown.bin,b9a7779770e65762e514d70f62fb6576,https://www.dropbox.com/s/o46idd1vtb63cby/downtown.bin.zip?dl=0 +data/system/maps/huge_krakow.bin,9927b32e2a895e696a8caa2783c1d3ab,https://www.dropbox.com/s/tn0w8e681cnvl1w/huge_krakow.bin.zip?dl=0 +data/system/maps/huge_seattle.bin,c5201cdfdfe7bb2f7131d8e9f19053a6,https://www.dropbox.com/s/qq1e56rwp4qh6v9/huge_seattle.bin.zip?dl=0 +data/system/maps/lakeslice.bin,d91d6420dec5e09bb98a8a406e57f5c0,https://www.dropbox.com/s/ku76oob8omnopih/lakeslice.bin.zip?dl=0 +data/system/maps/montlake.bin,032f4abaeab311a1f1bbf992a59ee882,https://www.dropbox.com/s/co7hpuc820b8t2y/montlake.bin.zip?dl=0 +data/system/maps/south_seattle.bin,c2ea73914031391444662a6ec2b2b325,https://www.dropbox.com/s/97l2cx62dwo7tzl/south_seattle.bin.zip?dl=0 +data/system/maps/udistrict.bin,65bd3b3890358bc6408578eb57d9e8ab,https://www.dropbox.com/s/0wvnn56fu7zzjm0/udistrict.bin.zip?dl=0 +data/system/maps/west_seattle.bin,0988d2fdee81c3b0907e6ec1231a3656,https://www.dropbox.com/s/chi7v6451yune0h/west_seattle.bin.zip?dl=0 data/system/prebaked_results/lakeslice/weekday.bin,1a5a02661bb67f69dafd78c0a2d4565d,https://www.dropbox.com/s/3wt7lxncc9mba9q/weekday.bin.zip?dl=0 data/system/prebaked_results/montlake/car vs bike contention.bin,2dbd9fb3ee0b4fc12c4e7651dbcb0e0b,https://www.dropbox.com/s/jefg0ikjy9dsrdd/car%20vs%20bike%20contention.bin.zip?dl=0 data/system/prebaked_results/montlake/weekday.bin,fc0baa2160dcddb151d4ebc315db6bf3,https://www.dropbox.com/s/zp8e5munh3t38h6/weekday.bin.zip?dl=0 diff --git a/docs/history/history.md b/docs/history/history.md index ffd5d6477d..acaff413fb 100644 --- a/docs/history/history.md +++ b/docs/history/history.md @@ -30,14 +30,14 @@ love with a Nintendo 64 game called Banjo Kazooie, which led me to the online fan communities of the early 2000's. I wanted to create games too, so I started learning programming via library books and lots of questions on IRC. Because I never had any confidence in art, I wound up working on -[roguelikes](https://github.com/dabreegster/mnemonicrl/), which led to a -fervent interest in pathfinding algorithms and [collaborative -diffusion](http://www.cs.colorado.edu/~ralex/papers/PDF/OOPSLA06antiobjects.pdf). -When I started driving in high school, I quickly realized how bad people were -at it. I remember being stuck at the intersection of [Florida Blvd and -Cloud](https://www.openstreetmap.org/node/1279204989) and first wondering if -the pathfinding algorithms could help with traffic. Can you see where this is -going? +[roguelikes](https://github.com/dabreegster/mnemonicrl/), which led to a fervent +interest in pathfinding algorithms and +[collaborative diffusion](http://www.cs.colorado.edu/~ralex/papers/PDF/OOPSLA06antiobjects.pdf). +When I started driving in high school, I quickly realized how bad people were at +it. I remember being stuck at the intersection of +[Florida Blvd and Cloud](https://www.openstreetmap.org/node/1279204989) and +first wondering if the pathfinding algorithms could help with traffic. Can you +see where this is going? ![Impatience is a virtue](cloud_florida.jpg) @@ -174,8 +174,19 @@ calling out milestones. "UI churn" is pretty much constantly happening. ## Year 3 (June 2020-2021) - June: parking lots, real minimap controls, road labels - - **June 22**: alpha launch! [r/Seattle](https://old.reddit.com/r/Seattle/comments/hdtucd/ab_street_think_you_can_fix_seattles_traffic/), [r/SeattleWA](https://old.reddit.com/r/SeattleWA/comments/hdttu8/ab_street_think_you_can_fix_seattles_traffic/), [r/UrbanPlanning](https://old.reddit.com/r/urbanplanning/comments/hdylmo/ab_street_a_traffic_simulation_game/), [HN](https://news.ycombinator.com/item?id=23605048#23608365), [GeekWire](https://www.geekwire.com/2020/want-fix-seattle-traffic-redditor-makes-game-allows-players-tweak-city-streets/), [The Stranger](https://www.thestranger.com/slog/2020/06/29/43999454/ab-streets-game-lets-you-create-the-seattle-street-grid-of-your-dreams) -- July: ... + - **June 22**: alpha launch! + [r/Seattle](https://old.reddit.com/r/Seattle/comments/hdtucd/ab_street_think_you_can_fix_seattles_traffic/), + [r/SeattleWA](https://old.reddit.com/r/SeattleWA/comments/hdttu8/ab_street_think_you_can_fix_seattles_traffic/), + [r/UrbanPlanning](https://old.reddit.com/r/urbanplanning/comments/hdylmo/ab_street_a_traffic_simulation_game/), + [HN](https://news.ycombinator.com/item?id=23605048#23608365), + [GeekWire](https://www.geekwire.com/2020/want-fix-seattle-traffic-redditor-makes-game-allows-players-tweak-city-streets/), + [The Stranger](https://www.thestranger.com/slog/2020/06/29/43999454/ab-streets-game-lets-you-create-the-seattle-street-grid-of-your-dreams) +- July: loads of bugfixes, map geometry improvements, UI cleanups, + access-restricted zones for private neighborhoods and no-through-traffic, + better traffic generation between home<->work for new maps, complete overhaul + to bus routes and introduction of light rail, commute pattern explorer, + importing Krakow and Berlin, smarter lane-changing, walkable shoulders for + roads without sidewalks - [KING 5 Evening](https://www.youtube.com/watch?v=Pk8V-egsUxU) interview ## Retrospective diff --git a/game/src/devtools/mapping.rs b/game/src/devtools/mapping.rs index 16a151302f..abc564f9cb 100644 --- a/game/src/devtools/mapping.rs +++ b/game/src/devtools/mapping.rs @@ -553,6 +553,8 @@ fn generate_osmc( fn find_divided_highways(app: &App) -> HashSet { let map = &app.primary.map; let mut closest: FindClosest = FindClosest::new(map.get_bounds()); + // TODO Consider not even filtering by oneway. I keep finding mistakes where people split a + // road, but didn't mark one side oneway! let mut oneways = Vec::new(); for r in map.all_roads() { if r.osm_tags.contains_key("oneway") { @@ -564,19 +566,25 @@ fn find_divided_highways(app: &App) -> HashSet { let mut found = HashSet::new(); for r1 in oneways { let r1 = map.get_r(r1); - let (middle, angle) = r1.center_pts.must_dist_along(r1.center_pts.length() / 2.0); - for (r2, _, _) in closest.all_close_pts(middle, Distance::meters(250.0)) { - if r1.id != r2 - && PolyLine::must_new(vec![ - middle.project_away(Distance::meters(100.0), angle.rotate_degs(90.0)), - middle.project_away(Distance::meters(100.0), angle.rotate_degs(-90.0)), - ]) - .intersection(&map.get_r(r2).center_pts) - .is_some() - && r1.get_name() == map.get_r(r2).get_name() - { - found.insert(r1.id); - found.insert(r2); + for dist in vec![ + Distance::ZERO, + r1.center_pts.length() / 2.0, + r1.center_pts.length(), + ] { + let (pt, angle) = r1.center_pts.must_dist_along(dist); + for (r2, _, _) in closest.all_close_pts(pt, Distance::meters(250.0)) { + if r1.id != r2 + && PolyLine::must_new(vec![ + pt.project_away(Distance::meters(100.0), angle.rotate_degs(90.0)), + pt.project_away(Distance::meters(100.0), angle.rotate_degs(-90.0)), + ]) + .intersection(&map.get_r(r2).center_pts) + .is_some() + && r1.get_name() == map.get_r(r2).get_name() + { + found.insert(r1.id); + found.insert(r2); + } } } } diff --git a/game/src/info/building.rs b/game/src/info/building.rs index 4c82cb7195..606bb4272b 100644 --- a/game/src/info/building.rs +++ b/game/src/info/building.rs @@ -3,7 +3,7 @@ use crate::info::{header_btns, make_table, make_tabs, Details, Tab}; use crate::render::DrawPedestrian; use ezgui::{Btn, Color, EventCtx, Line, Text, TextExt, Widget}; use geom::{Angle, Circle, Distance, Speed, Time}; -use map_model::{BuildingID, LaneID, Traversable, SIDEWALK_THICKNESS}; +use map_model::{BuildingID, LaneID, OffstreetParking, Traversable, SIDEWALK_THICKNESS}; use sim::{DrawPedestrianInput, PedestrianID, PersonID, TripMode, TripResult}; use std::collections::BTreeMap; @@ -21,20 +21,18 @@ pub fn info(ctx: &mut EventCtx, app: &App, details: &mut Details, id: BuildingID kv.push(("OSM ID", format!("{}", b.osm_way_id))); } - if let Some(ref p) = b.parking { + let num_spots = b.num_parking_spots(); + if num_spots > 0 { let free = app.primary.sim.get_free_offstreet_spots(b.id).len(); - if let Some(ref n) = p.public_garage_name { + if let OffstreetParking::PublicGarage(ref n, _) = b.parking { kv.push(( "Parking", - format!( - "{} / {} public spots available via {}", - free, p.num_spots, n - ), + format!("{} / {} public spots available via {}", free, num_spots, n), )); } else { kv.push(( "Parking", - format!("{} / {} private spots available", free, p.num_spots), + format!("{} / {} private spots available", free, num_spots), )); } } else { diff --git a/game/src/layer/parking.rs b/game/src/layer/parking.rs index 03ecbe3a69..638298e0d7 100644 --- a/game/src/layer/parking.rs +++ b/game/src/layer/parking.rs @@ -7,7 +7,7 @@ use ezgui::{ Outcome, Text, TextExt, VerticalAlignment, Widget, }; use geom::Time; -use map_model::{BuildingID, Map, ParkingLotID, RoadID}; +use map_model::{BuildingID, Map, OffstreetParking, ParkingLotID, RoadID}; use sim::{ParkingSpot, VehicleType}; use std::collections::BTreeSet; @@ -131,15 +131,8 @@ impl Occupancy { *public_counter += 1; } ParkingSpot::Offstreet(b, _) => { - if app - .primary - .map - .get_b(b) - .parking - .as_ref() - .unwrap() - .public_garage_name - .is_some() + if let OffstreetParking::PublicGarage(_, _) = + app.primary.map.get_b(b).parking { if !garages { continue; diff --git a/game/src/render/building.rs b/game/src/render/building.rs index 243ff01d30..08e47d6855 100644 --- a/game/src/render/building.rs +++ b/game/src/render/building.rs @@ -4,7 +4,7 @@ use crate::helpers::ID; use crate::render::{DrawOptions, Renderable, OUTLINE_THICKNESS}; use ezgui::{Color, Drawable, GeomBatch, GfxCtx, Line, Prerender, Text}; use geom::{Distance, Polygon, Pt2D}; -use map_model::{Building, BuildingID, Map, NORMAL_LANE_THICKNESS}; +use map_model::{Building, BuildingID, Map, OffstreetParking, NORMAL_LANE_THICKNESS}; use std::cell::RefCell; pub struct DrawBuilding { @@ -22,35 +22,28 @@ impl DrawBuilding { outlines_batch: &mut GeomBatch, prerender: &Prerender, ) -> DrawBuilding { - // Trim the front path line away from the sidewalk's center line, so that it doesn't - // overlap. For now, this cleanup is visual; it doesn't belong in the map_model layer. - let orig_line = &bldg.front_path.line; - let front_path_line = orig_line + // Trim the driveway away from the sidewalk's center line, so that it doesn't overlap. For + // now, this cleanup is visual; it doesn't belong in the map_model layer. + let orig_pl = &bldg.driveway_geom; + let driveway = orig_pl .slice( Distance::ZERO, - orig_line.length() - map.get_l(bldg.sidewalk()).width / 2.0, + orig_pl.length() - map.get_l(bldg.sidewalk()).width / 2.0, ) - .unwrap_or_else(|| orig_line.clone()); + .map(|(pl, _)| pl) + .unwrap_or_else(|_| orig_pl.clone()); if bldg.amenities.is_empty() { bldg_batch.push(cs.residential_building, bldg.polygon.clone()); } else { bldg_batch.push(cs.commerical_building, bldg.polygon.clone()); } - paths_batch.push( - cs.sidewalk, - front_path_line.make_polygons(NORMAL_LANE_THICKNESS), - ); + paths_batch.push(cs.sidewalk, driveway.make_polygons(NORMAL_LANE_THICKNESS)); if let Ok(p) = bldg.polygon.to_outline(Distance::meters(0.1)) { outlines_batch.push(cs.building_outline, p); } - if bldg - .parking - .as_ref() - .map(|p| p.public_garage_name.is_some()) - .unwrap_or(false) - { + if let OffstreetParking::PublicGarage(_, _) = bldg.parking { // Might need to scale down more for some buildings, but so far, this works everywhere. bldg_batch.append( GeomBatch::mapspace_svg(prerender, "system/assets/map/parking.svg") diff --git a/game/src/render/pedestrian.rs b/game/src/render/pedestrian.rs index f2f250539c..a27b03b128 100644 --- a/game/src/render/pedestrian.rs +++ b/game/src/render/pedestrian.rs @@ -211,13 +211,11 @@ impl DrawPedCrowd { map.right_shift(pl_slice, SIDEWALK_THICKNESS / 4.0) } } - PedCrowdLocation::BldgFrontPath(b) => map + PedCrowdLocation::BldgDriveway(b) => map .get_b(b) - .front_path - .line - .to_polyline() + .driveway_geom .exact_slice(input.low, input.high), - PedCrowdLocation::LotFrontPath(pl) => map + PedCrowdLocation::LotDriveway(pl) => map .get_pl(pl) .sidewalk_line .to_polyline() @@ -239,8 +237,8 @@ impl DrawPedCrowd { blob, zorder: match input.location { PedCrowdLocation::Sidewalk(on, _) => on.get_zorder(map), - PedCrowdLocation::BldgFrontPath(_) => 0, - PedCrowdLocation::LotFrontPath(_) => 0, + PedCrowdLocation::BldgDriveway(_) => 0, + PedCrowdLocation::LotDriveway(_) => 0, }, draw_default: prerender.upload(batch), } diff --git a/game/src/sandbox/dashboards/commuter.rs b/game/src/sandbox/dashboards/commuter.rs index 7a0ff20d7b..bf892506e4 100644 --- a/game/src/sandbox/dashboards/commuter.rs +++ b/game/src/sandbox/dashboards/commuter.rs @@ -578,6 +578,11 @@ fn partition_sidewalk_loops(app: &App) -> Vec { let mut todo_bldgs: BTreeSet = map.all_buildings().iter().map(|b| b.id).collect(); let mut remainder = HashSet::new(); + let mut sidewalk_to_bldgs = MultiMap::new(); + for b in map.all_buildings() { + sidewalk_to_bldgs.insert(b.sidewalk(), b.id); + } + while !todo_bldgs.is_empty() { let mut sidewalks = HashSet::new(); let mut bldgs = HashSet::new(); @@ -586,7 +591,7 @@ fn partition_sidewalk_loops(app: &App) -> Vec { let ok = loop { sidewalks.insert(current_l); - for b in &map.get_l(current_l).building_paths { + for b in sidewalk_to_bldgs.get(current_l) { bldgs.insert(*b); // TODO I wanted to assert that we haven't assigned this one yet, but... todo_bldgs.remove(b); diff --git a/game/src/sandbox/gameplay/freeform.rs b/game/src/sandbox/gameplay/freeform.rs index da0b5194a7..c9f6999518 100644 --- a/game/src/sandbox/gameplay/freeform.rs +++ b/game/src/sandbox/gameplay/freeform.rs @@ -447,7 +447,7 @@ fn path_request( fn pos(endpt: TripEndpoint, mode: TripMode, from: bool, map: &Map) -> Option { match endpt { TripEndpoint::Bldg(b) => match mode { - TripMode::Walk | TripMode::Transit => Some(map.get_b(b).front_path.sidewalk), + TripMode::Walk | TripMode::Transit => Some(map.get_b(b).sidewalk_pos), TripMode::Bike => Some(DrivingGoal::ParkNear(b).goal_pos(PathConstraints::Bike, map)), TripMode::Drive => Some(DrivingGoal::ParkNear(b).goal_pos(PathConstraints::Car, map)), }, diff --git a/game/src/sandbox/gameplay/tutorial.rs b/game/src/sandbox/gameplay/tutorial.rs index 222611ba63..eba23486e1 100644 --- a/game/src/sandbox/gameplay/tutorial.rs +++ b/game/src/sandbox/gameplay/tutorial.rs @@ -1087,7 +1087,7 @@ impl TutorialState { )], }); // Will definitely get there first - for i in 0..map.get_b(goal_bldg).parking.as_ref().unwrap().num_spots { + for i in 0..map.get_b(goal_bldg).num_parking_spots() { scenario.people.push(PersonSpec { id: PersonID(i + 1), orig_id: None, diff --git a/geom/src/polyline.rs b/geom/src/polyline.rs index 7bbea14f9a..d099cc3a8a 100644 --- a/geom/src/polyline.rs +++ b/geom/src/polyline.rs @@ -150,6 +150,10 @@ impl PolyLine { pub fn must_extend(self, other: PolyLine) -> PolyLine { self.extend(other).unwrap() } + pub fn must_push(self, pt: Pt2D) -> PolyLine { + let new = PolyLine::must_new(vec![self.last_pt(), pt]); + self.must_extend(new) + } // One or both args might be empty. pub fn append(first: Vec, second: Vec) -> Result, Box> { diff --git a/importer/src/soundcast/trips.rs b/importer/src/soundcast/trips.rs index e825a91e16..a2a74a7e88 100644 --- a/importer/src/soundcast/trips.rs +++ b/importer/src/soundcast/trips.rs @@ -71,14 +71,20 @@ fn endpoints( if let (Some(b1), Some(b2)) = (maybe_b1, maybe_b2) { // TODO Super rough... let start = if constraints == PathConstraints::Pedestrian { - Some(huge_map.get_b(b1).front_path.sidewalk) + Some(huge_map.get_b(b1).sidewalk_pos) } else { - huge_map.get_b(b1).parking.as_ref().map(|p| p.driving_pos) + huge_map + .get_b(b1) + .driving_connection(huge_map) + .map(|(pos, _)| pos) }; let end = if constraints == PathConstraints::Pedestrian { - Some(huge_map.get_b(b2).front_path.sidewalk) + Some(huge_map.get_b(b2).sidewalk_pos) } else { - huge_map.get_b(b2).parking.as_ref().map(|p| p.driving_pos) + huge_map + .get_b(b2) + .driving_connection(huge_map) + .map(|(pos, _)| pos) }; if let Some(path) = start.and_then(|start| { end.and_then(|end| { diff --git a/map_model/src/connectivity.rs b/map_model/src/connectivity.rs index b4dc453913..b20c0a9179 100644 --- a/map_model/src/connectivity.rs +++ b/map_model/src/connectivity.rs @@ -98,13 +98,13 @@ fn bidi_flood(map: &Map, start: LaneID, largest_group: &HashSet) -> Opti // TODO Different cost function pub fn all_costs_from(map: &Map, start: BuildingID) -> HashMap { let mut results = HashMap::new(); - let start = map.get_b(start).front_path.sidewalk; + let start = map.get_b(start).sidewalk_pos; // TODO This is SO inefficient. Flood out and mark off buildings as we go. Granularity of lane // makes more sense. for b in map.all_buildings() { if let Some(path) = map.pathfind(PathRequest { start, - end: b.front_path.sidewalk, + end: b.sidewalk_pos, constraints: PathConstraints::Pedestrian, }) { // TODO Distance isn't an interesting thing to show at all, we want the path cost diff --git a/map_model/src/lib.rs b/map_model/src/lib.rs index c98054f25a..ae77122215 100644 --- a/map_model/src/lib.rs +++ b/map_model/src/lib.rs @@ -16,9 +16,7 @@ pub use crate::edits::{ pub use crate::make::initial::lane_specs::RoadSpec; pub use crate::map::MapConfig; pub use crate::objects::area::{Area, AreaID, AreaType}; -pub use crate::objects::building::{ - Building, BuildingID, BuildingType, FrontPath, OffstreetParking, -}; +pub use crate::objects::building::{Building, BuildingID, BuildingType, OffstreetParking}; pub use crate::objects::bus_stop::{BusRoute, BusRouteID, BusStop, BusStopID}; pub use crate::objects::intersection::{Intersection, IntersectionID, IntersectionType}; pub use crate::objects::lane::{ diff --git a/map_model/src/make/buildings.rs b/map_model/src/make/buildings.rs index 703102c1fd..1103939a46 100644 --- a/map_model/src/make/buildings.rs +++ b/map_model/src/make/buildings.rs @@ -1,10 +1,8 @@ use crate::make::match_points_to_lanes; use crate::raw::{OriginalBuilding, RawBuilding}; -use crate::{ - osm, Building, BuildingID, BuildingType, FrontPath, LaneID, LaneType, Map, OffstreetParking, -}; +use crate::{osm, Building, BuildingID, BuildingType, LaneID, Map, OffstreetParking}; use abstutil::{Tags, Timer}; -use geom::{Distance, HashablePt2D, Line, PolyLine, Polygon}; +use geom::{Distance, HashablePt2D, Line, Polygon}; use rand::{Rng, SeedableRng}; use rand_xorshift::XorShiftRng; use std::collections::{BTreeMap, BTreeSet, HashSet}; @@ -27,7 +25,6 @@ pub fn make_all_buildings( // equiv_pos could be a little closer, so use two buffers let sidewalk_buffer = Distance::meters(7.5); - let driveway_buffer = Distance::meters(7.0); let sidewalk_pts = match_points_to_lanes( map.get_bounds(), query, @@ -41,7 +38,7 @@ pub fn make_all_buildings( ); let mut results = Vec::new(); - timer.start_iter("create building front paths", center_per_bldg.len()); + timer.start_iter("match buildings to sidewalks", center_per_bldg.len()); for (orig_id, bldg_center) in center_per_bldg { timer.next(); if let Some(sidewalk_pos) = sidewalk_pts.get(&bldg_center) { @@ -59,61 +56,24 @@ pub fn make_all_buildings( let id = BuildingID(results.len()); let mut rng = XorShiftRng::seed_from_u64(orig_id.osm_way_id as u64); - let mut bldg = Building { + results.push(Building { id, polygon: b.polygon.clone(), address: get_address(&b.osm_tags, sidewalk_pos.lane(), map), name: b.osm_tags.get(osm::NAME).cloned(), osm_way_id: orig_id.osm_way_id, - front_path: FrontPath { - sidewalk: *sidewalk_pos, - line: sidewalk_line.clone(), - }, - amenities: b.amenities.clone(), - parking: None, label_center: b.polygon.polylabel(), - bldg_type: classify_bldg( - b.osm_tags.clone(), - &b.amenities, - b.polygon.area(), - &mut rng, - ), - }; + amenities: b.amenities.clone(), + bldg_type: classify_bldg(&b.osm_tags, &b.amenities, b.polygon.area(), &mut rng), + parking: if let Some(n) = b.public_garage_name.clone() { + OffstreetParking::PublicGarage(n, b.num_parking_spots) + } else { + OffstreetParking::Private(b.num_parking_spots) + }, - // Can this building have a driveway? If it's not next to a driving lane, then no. - let sidewalk_lane = sidewalk_pos.lane(); - if let Ok(driving_lane) = map - .get_parent(sidewalk_lane) - .find_closest_lane(sidewalk_lane, vec![LaneType::Driving]) - { - let driving_pos = sidewalk_pos.equiv_pos(driving_lane, Distance::ZERO, map); - - // This shouldn't fail much anymore, unless equiv_pos winds up being pretty - // different - if driving_pos.dist_along() > driveway_buffer - && map.get_l(driving_lane).length() - driving_pos.dist_along() > driveway_buffer - { - let driveway_line = PolyLine::must_new(vec![ - sidewalk_line.pt1(), - sidewalk_line.pt2(), - driving_pos.pt(map), - ]); - bldg.parking = Some(OffstreetParking { - public_garage_name: b.public_garage_name.clone(), - num_spots: b.num_parking_spots, - driveway_line, - driving_pos, - }); - } - } - if bldg.parking.is_none() { - timer.warn(format!( - "{} can't have a driveway. Forfeiting {} parking spots", - bldg.id, b.num_parking_spots - )); - } - - results.push(bldg); + sidewalk_pos: *sidewalk_pos, + driveway_geom: sidewalk_line.to_polyline(), + }); } } @@ -150,7 +110,7 @@ fn get_address(tags: &Tags, sidewalk: LaneID, map: &Map) -> String { } fn classify_bldg( - tags: Tags, + tags: &Tags, amenities: &BTreeSet<(String, String)>, area_sq_meters: f64, rng: &mut XorShiftRng, diff --git a/map_model/src/make/mod.rs b/map_model/src/make/mod.rs index 682b543032..832fa35a28 100644 --- a/map_model/src/make/mod.rs +++ b/map_model/src/make/mod.rs @@ -188,7 +188,6 @@ impl Map { dst_i, lane_type: lane.lane_type, parent: road_id, - building_paths: Vec::new(), bus_stops: BTreeSet::new(), parking_blackhole: None, }); @@ -268,15 +267,6 @@ impl Map { timer.stop("find parking blackholes"); map.buildings = buildings::make_all_buildings(&raw.buildings, &map, timer); - for b in &map.buildings { - let lane = b.sidewalk(); - - // TODO Could be more performant and cleanly written - let mut bldgs = map.lanes[lane.0].building_paths.clone(); - bldgs.push(b.id); - bldgs.sort_by_key(|b| map.buildings[b.0].front_path.sidewalk.dist_along()); - map.lanes[lane.0].building_paths = bldgs; - } map.parking_lots = parking_lots::make_all_parking_lots( &raw.parking_lots, diff --git a/map_model/src/map.rs b/map_model/src/map.rs index f309823a43..fd70850734 100644 --- a/map_model/src/map.rs +++ b/map_model/src/map.rs @@ -2,8 +2,8 @@ use crate::raw::{DrivingSide, RawMap}; use crate::{ Area, AreaID, Building, BuildingID, BuildingType, BusRoute, BusRouteID, BusStop, BusStopID, ControlStopSign, ControlTrafficSignal, Intersection, IntersectionID, Lane, LaneID, LaneType, - Map, MapEdits, ParkingLot, ParkingLotID, Path, PathConstraints, PathRequest, Position, Road, - RoadID, Turn, TurnGroupID, TurnID, TurnType, + Map, MapEdits, OffstreetParking, ParkingLot, ParkingLotID, Path, PathConstraints, PathRequest, + Position, Road, RoadID, Turn, TurnGroupID, TurnID, TurnType, }; use abstutil::Timer; use geom::{Angle, Bounds, Distance, GPSBounds, Line, PolyLine, Polygon, Pt2D, Ring}; @@ -657,19 +657,15 @@ impl Map { // TODO Sort of a temporary hack pub fn hack_override_offstreet_spots(&mut self, spots_per_bldg: usize) { for b in &mut self.buildings { - if let Some(ref mut p) = b.parking { - if p.public_garage_name.is_none() { - p.num_spots = spots_per_bldg; - } + if let OffstreetParking::Private(ref mut num_spots) = b.parking { + *num_spots = spots_per_bldg; } } } pub fn hack_override_offstreet_spots_individ(&mut self, b: BuildingID, spots: usize) { let b = &mut self.buildings[b.0]; - if let Some(ref mut p) = b.parking { - if p.public_garage_name.is_none() { - p.num_spots = spots; - } + if let OffstreetParking::Private(ref mut num_spots) = b.parking { + *num_spots = spots; } } diff --git a/map_model/src/objects/building.rs b/map_model/src/objects/building.rs index 6bbe8a35ea..02472e154e 100644 --- a/map_model/src/objects/building.rs +++ b/map_model/src/objects/building.rs @@ -1,6 +1,6 @@ -use crate::{LaneID, Position}; +use crate::{LaneID, LaneType, Map, Position}; use abstutil::{deserialize_usize, serialize_usize}; -use geom::{Line, PolyLine, Polygon, Pt2D}; +use geom::{Distance, PolyLine, Polygon, Pt2D}; use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; use std::fmt; @@ -20,26 +20,6 @@ impl fmt::Display for BuildingID { } } -#[derive(Serialize, Deserialize, Debug)] -pub struct FrontPath { - pub sidewalk: Position, - // Goes from the building to the sidewalk - pub line: Line, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub struct OffstreetParking { - // If filled out, this is a public parking garage with a name. If not, it's private parking - // just for trips to/from this building. - pub public_garage_name: Option, - pub num_spots: usize, - // Goes from the building to the driving lane - pub driveway_line: PolyLine, - // Guaranteed to be at least 7m (MAX_CAR_LENGTH + a little buffer) away from both ends of the - // lane, to prevent various headaches - pub driving_pos: Position, -} - #[derive(Serialize, Deserialize, Debug)] pub struct Building { pub id: BuildingID, @@ -53,12 +33,22 @@ pub struct Building { // TODO Might fold these into BuildingType::Commercial // (Name, amenity) pub amenities: BTreeSet<(String, String)>, - - pub front_path: FrontPath, - // Every building can't have OffstreetParking, because the nearest usable driving lane (not in - // a parking blackhole) might be far away - pub parking: Option, pub bldg_type: BuildingType, + pub parking: OffstreetParking, + + // The building's connection for pedestrians is immutable. For cars and bikes, it can change + // based on map edits, so don't cache it. + pub sidewalk_pos: Position, + // Goes from building to sidewalk + pub driveway_geom: PolyLine, +} + +// Represent None as Private(0). +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum OffstreetParking { + // (Name, spots) + PublicGarage(String, usize), + Private(usize), } #[derive(Serialize, Deserialize, Debug)] @@ -81,7 +71,7 @@ impl BuildingType { impl Building { pub fn sidewalk(&self) -> LaneID { - self.front_path.sidewalk.lane() + self.sidewalk_pos.lane() } pub fn house_number(&self) -> Option { @@ -92,4 +82,29 @@ impl Building { None } } + + // The polyline goes from the building to the driving position + pub fn driving_connection(&self, map: &Map) -> Option<(Position, PolyLine)> { + // Is there even a driving lane on the same side as our sidewalk? + // TODO Handle offside + let lane = map + .get_parent(self.sidewalk()) + .find_closest_lane(self.sidewalk(), vec![LaneType::Driving]) + .ok()?; + let pos = self.sidewalk_pos.equiv_pos(lane, Distance::ZERO, map); + + // TODO Do we need to insist on this buffer, now that we can make cars gradually appear? + let buffer = Distance::meters(7.0); + if pos.dist_along() <= buffer || map.get_l(lane).length() - pos.dist_along() <= buffer { + return None; + } + Some((pos, self.driveway_geom.clone().must_push(pos.pt(map)))) + } + + pub fn num_parking_spots(&self) -> usize { + match self.parking { + OffstreetParking::PublicGarage(_, n) => n, + OffstreetParking::Private(n) => n, + } + } } diff --git a/map_model/src/objects/lane.rs b/map_model/src/objects/lane.rs index 6aa24ff7b7..60975a47e0 100644 --- a/map_model/src/objects/lane.rs +++ b/map_model/src/objects/lane.rs @@ -1,7 +1,6 @@ use crate::pathfind; use crate::{ - osm, BuildingID, BusStopID, DirectedRoadID, IntersectionID, Map, PathConstraints, Road, RoadID, - TurnType, + osm, BusStopID, DirectedRoadID, IntersectionID, Map, PathConstraints, Road, RoadID, TurnType, }; use abstutil::{deserialize_usize, serialize_usize}; use geom::{Distance, Line, PolyLine, Pt2D}; @@ -112,8 +111,6 @@ pub struct Lane { pub src_i: IntersectionID, pub dst_i: IntersectionID, - // Sorted by distance of the front path - pub building_paths: Vec, // Meaningless order pub bus_stops: BTreeSet, diff --git a/sim/src/lib.rs b/sim/src/lib.rs index d5c4795cfa..b7254a1818 100644 --- a/sim/src/lib.rs +++ b/sim/src/lib.rs @@ -415,11 +415,10 @@ impl SidewalkSpot { } } - pub fn building(bldg: BuildingID, map: &Map) -> SidewalkSpot { - let front_path = &map.get_b(bldg).front_path; + pub fn building(b: BuildingID, map: &Map) -> SidewalkSpot { SidewalkSpot { - connection: SidewalkPOI::Building(bldg), - sidewalk_pos: front_path.sidewalk, + connection: SidewalkPOI::Building(b), + sidewalk_pos: map.get_b(b).sidewalk_pos, } } diff --git a/sim/src/make/activity_model.rs b/sim/src/make/activity_model.rs index a0f1019aea..896ae7d40f 100644 --- a/sim/src/make/activity_model.rs +++ b/sim/src/make/activity_model.rs @@ -47,8 +47,8 @@ impl ScenarioGenerator { let work = *workplaces.choose(rng).unwrap(); // Decide mode based on walking distance. let dist = if let Some(path) = map.pathfind(PathRequest { - start: map.get_b(home).front_path.sidewalk, - end: map.get_b(work).front_path.sidewalk, + start: map.get_b(home).sidewalk_pos, + end: map.get_b(work).sidewalk_pos, constraints: PathConstraints::Pedestrian, }) { path.total_length() diff --git a/sim/src/make/scenario.rs b/sim/src/make/scenario.rs index 19a0da1c7b..1fceb62975 100644 --- a/sim/src/make/scenario.rs +++ b/sim/src/make/scenario.rs @@ -6,7 +6,8 @@ use crate::{ use abstutil::{prettyprint_usize, Counter, Timer}; use geom::{Distance, Duration, LonLat, Speed, Time, EPSILON_DIST}; use map_model::{ - BuildingID, BusRouteID, BusStopID, DirectedRoadID, Map, PathConstraints, Position, RoadID, + BuildingID, BusRouteID, BusStopID, DirectedRoadID, Map, OffstreetParking, PathConstraints, + Position, RoadID, }; use rand::seq::SliceRandom; use rand::{Rng, SeedableRng}; @@ -269,17 +270,9 @@ fn seed_parked_cars( ParkingSpot::Onstreet(l, _) => (map.get_l(l).parent, None), ParkingSpot::Offstreet(b, _) => ( map.get_l(map.get_b(b).sidewalk()).parent, - if map - .get_b(b) - .parking - .as_ref() - .unwrap() - .public_garage_name - .is_some() - { - None - } else { - Some(b) + match map.get_b(b).parking { + OffstreetParking::PublicGarage(_, _) => None, + OffstreetParking::Private(_) => Some(b), }, ), ParkingSpot::Lot(pl, _) => (map.get_l(map.get_pl(pl).driving_pos.lane()).parent, None), diff --git a/sim/src/make/spawner.rs b/sim/src/make/spawner.rs index 1950a600fd..492cc4b6dd 100644 --- a/sim/src/make/spawner.rs +++ b/sim/src/make/spawner.rs @@ -399,7 +399,7 @@ impl TripSpec { constraints: PathConstraints::Pedestrian, }), TripSpec::UsingBike { start, .. } => Some(PathRequest { - start: map.get_b(*start).front_path.sidewalk, + start: map.get_b(*start).sidewalk_pos, end: SidewalkSpot::bike_from_bike_rack(map.get_b(*start).sidewalk(), map) .unwrap() .sidewalk_pos, diff --git a/sim/src/mechanics/car.rs b/sim/src/mechanics/car.rs index 4f14217da8..aa8679a320 100644 --- a/sim/src/mechanics/car.rs +++ b/sim/src/mechanics/car.rs @@ -149,9 +149,9 @@ impl Car { _ => { let driveway = match spot { ParkingSpot::Offstreet(b, _) => { - &map.get_b(*b).parking.as_ref().unwrap().driveway_line + map.get_b(*b).driving_connection(map).unwrap().1 } - ParkingSpot::Lot(pl, _) => &map.get_pl(*pl).driveway_line, + ParkingSpot::Lot(pl, _) => map.get_pl(*pl).driveway_line.clone(), _ => unreachable!(), }; diff --git a/sim/src/mechanics/parking.rs b/sim/src/mechanics/parking.rs index 777c0c29ca..c0fea44484 100644 --- a/sim/src/mechanics/parking.rs +++ b/sim/src/mechanics/parking.rs @@ -5,8 +5,8 @@ use abstutil::{ }; use geom::{Distance, PolyLine, Pt2D}; use map_model::{ - BuildingID, Lane, LaneID, LaneType, Map, ParkingLotID, PathConstraints, PathStep, Position, - Traversable, TurnID, + BuildingID, Lane, LaneID, LaneType, Map, OffstreetParking, ParkingLotID, PathConstraints, + PathStep, Position, Traversable, TurnID, }; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap}; @@ -36,11 +36,12 @@ pub struct ParkingSimState { // Off-street num_spots_per_offstreet: BTreeMap, + // Cache dist_along #[serde( serialize_with = "serialize_multimap", deserialize_with = "deserialize_multimap" )] - driving_to_offstreet: MultiMap, + driving_to_offstreet: MultiMap, // Parking lots num_spots_per_lot: BTreeMap, @@ -78,10 +79,14 @@ impl ParkingSimState { } } for b in map.all_buildings() { - if let Some(ref p) = b.parking { - if map.get_l(p.driving_pos.lane()).parking_blackhole.is_none() { - sim.num_spots_per_offstreet.insert(b.id, p.num_spots); - sim.driving_to_offstreet.insert(p.driving_pos.lane(), b.id); + if let Some((pos, _)) = b.driving_connection(map) { + if map.get_l(pos.lane()).parking_blackhole.is_none() { + let num_spots = b.num_parking_spots(); + if num_spots > 0 { + sim.num_spots_per_offstreet.insert(b.id, num_spots); + sim.driving_to_offstreet + .insert(pos.lane(), (b.id, pos.dist_along())); + } } } } @@ -293,13 +298,13 @@ impl ParkingSimState { } } - for b in self.driving_to_offstreet.get(driving_pos.lane()) { - let parking = map.get_b(*b).parking.as_ref().unwrap(); - if parking.public_garage_name.is_none() && target != *b { - continue; + for (b, bldg_dist) in self.driving_to_offstreet.get(driving_pos.lane()) { + if let OffstreetParking::Private(_) = map.get_b(*b).parking { + if target != *b { + continue; + } } - let bldg_dist = parking.driving_pos.dist_along(); - if driving_pos.dist_along() < bldg_dist { + if driving_pos.dist_along() < *bldg_dist { for idx in 0..self.num_spots_per_offstreet[b] { let spot = ParkingSpot::Offstreet(*b, idx); if self.is_free(spot) { @@ -337,7 +342,7 @@ impl ParkingSimState { map, ) } - ParkingSpot::Offstreet(b, _) => map.get_b(b).parking.as_ref().unwrap().driving_pos, + ParkingSpot::Offstreet(b, _) => map.get_b(b).driving_connection(map).unwrap().0, ParkingSpot::Lot(pl, _) => map.get_pl(pl).driving_pos, } } @@ -353,7 +358,7 @@ impl ParkingSimState { ) .equiv_pos(lane.sidewalk, Distance::ZERO, map) } - ParkingSpot::Offstreet(b, _) => map.get_b(b).front_path.sidewalk, + ParkingSpot::Offstreet(b, _) => map.get_b(b).sidewalk_pos, ParkingSpot::Lot(pl, _) => map.get_pl(pl).sidewalk_pos, } } diff --git a/sim/src/mechanics/walking.rs b/sim/src/mechanics/walking.rs index d07b916ab4..02f4390930 100644 --- a/sim/src/mechanics/walking.rs +++ b/sim/src/mechanics/walking.rs @@ -72,7 +72,7 @@ impl WalkingSimState { SidewalkPOI::Building(b) | SidewalkPOI::ParkingSpot(ParkingSpot::Offstreet(b, _)) => { PedState::LeavingBuilding( b, - TimeInterval::new(now, now + map.get_b(b).front_path.line.length() / ped.speed), + TimeInterval::new(now, now + map.get_b(b).driveway_geom.length() / ped.speed), ) } SidewalkPOI::ParkingSpot(ParkingSpot::Lot(pl, _)) => PedState::LeavingParkingLot( @@ -158,7 +158,7 @@ impl WalkingSimState { b, TimeInterval::new( now, - now + map.get_b(b).front_path.line.length() / ped.speed, + now + map.get_b(b).driveway_geom.length() / ped.speed, ), ); scheduler.push(ped.state.get_end_time(), Command::UpdatePed(ped.id)); @@ -247,8 +247,7 @@ impl WalkingSimState { } } PedState::LeavingBuilding(b, _) => { - ped.state = - ped.crossing_state(map.get_b(b).front_path.sidewalk.dist_along(), now, map); + ped.state = ped.crossing_state(map.get_b(b).sidewalk_pos.dist_along(), now, map); scheduler.push(ped.state.get_end_time(), Command::UpdatePed(ped.id)); } PedState::EnteringBuilding(bldg, _) => { @@ -396,11 +395,11 @@ impl WalkingSimState { on: Traversable, map: &Map, ) -> (Vec, Vec) { - // Classify into direction-based groups or by building/parking lot front path. + // Classify into direction-based groups or by building/parking lot driveway. let mut forwards: Vec<(PedestrianID, Distance)> = Vec::new(); let mut backwards: Vec<(PedestrianID, Distance)> = Vec::new(); - let mut bldg_front_path: MultiMap = MultiMap::new(); - let mut lot_front_path: MultiMap = MultiMap::new(); + let mut bldg_driveway: MultiMap = MultiMap::new(); + let mut lot_driveway: MultiMap = MultiMap::new(); for id in self.peds_per_traversable.get(on) { let ped = &self.peds[id]; @@ -422,20 +421,20 @@ impl WalkingSimState { } } PedState::LeavingBuilding(b, ref int) => { - let len = map.get_b(b).front_path.line.length(); - bldg_front_path.insert(b, (*id, int.percent(now) * len)); + let len = map.get_b(b).driveway_geom.length(); + bldg_driveway.insert(b, (*id, int.percent(now) * len)); } PedState::EnteringBuilding(b, ref int) => { - let len = map.get_b(b).front_path.line.length(); - bldg_front_path.insert(b, (*id, (1.0 - int.percent(now)) * len)); + let len = map.get_b(b).driveway_geom.length(); + bldg_driveway.insert(b, (*id, (1.0 - int.percent(now)) * len)); } PedState::LeavingParkingLot(pl, ref int) => { let len = map.get_pl(pl).sidewalk_line.length(); - lot_front_path.insert(pl, (*id, int.percent(now) * len)); + lot_driveway.insert(pl, (*id, int.percent(now) * len)); } PedState::EnteringParkingLot(pl, ref int) => { let len = map.get_pl(pl).sidewalk_line.length(); - lot_front_path.insert(pl, (*id, (1.0 - int.percent(now)) * len)); + lot_driveway.insert(pl, (*id, (1.0 - int.percent(now)) * len)); } PedState::StartingToBike(_, _, _) | PedState::FinishingBiking(_, _, _) @@ -463,17 +462,17 @@ impl WalkingSimState { ), ] .into_iter() - .chain(bldg_front_path.consume().into_iter().map(|(b, set)| { + .chain(bldg_driveway.consume().into_iter().map(|(b, set)| { ( set.into_iter().collect::>(), - PedCrowdLocation::BldgFrontPath(b), - map.get_b(b).front_path.line.length(), + PedCrowdLocation::BldgDriveway(b), + map.get_b(b).driveway_geom.length(), ) })) - .chain(lot_front_path.consume().into_iter().map(|(pl, set)| { + .chain(lot_driveway.consume().into_iter().map(|(pl, set)| { ( set.into_iter().collect::>(), - PedCrowdLocation::LotFrontPath(pl), + PedCrowdLocation::LotDriveway(pl), map.get_pl(pl).sidewalk_line.length(), ) })) { @@ -542,7 +541,7 @@ impl Pedestrian { 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).front_path.sidewalk.dist_along() + map.get_b(b).sidewalk_pos.dist_along() } PedState::LeavingParkingLot(pl, _) | PedState::EnteringParkingLot(pl, _) => { map.get_pl(pl).sidewalk_pos.dist_along() @@ -592,24 +591,24 @@ impl Pedestrian { facing, ) } - // If we're on some tiny line and percent_along fails, just fall back to to some point - // on the line instead of crashing. PedState::LeavingBuilding(b, ref time_int) => { - let line = &map.get_b(b).front_path.line; - ( - line.percent_along(time_int.percent(now)) - .unwrap_or(line.pt1()), - line.angle(), - ) + let pl = &map.get_b(b).driveway_geom; + // If we're on some tiny line and percent_along fails, just fall back to to some + // point on the line instead of crashing. + if let Ok(pair) = pl.dist_along(time_int.percent(now) * pl.length()) { + pair + } else { + (pl.first_pt(), pl.first_line().angle()) + } } PedState::EnteringBuilding(b, ref time_int) => { - let line = &map.get_b(b).front_path.line; - ( - line.reverse() - .percent_along(time_int.percent(now)) - .unwrap_or(line.pt1()), - line.angle().opposite(), - ) + let pl = &map.get_b(b).driveway_geom; + if let Ok((pt, angle)) = pl.dist_along((1.0 - time_int.percent(now)) * pl.length()) + { + (pt, angle.opposite()) + } else { + (pl.first_pt(), pl.first_line().angle().opposite()) + } } PedState::LeavingParkingLot(pl, ref time_int) => { let line = &map.get_pl(pl).sidewalk_line; diff --git a/sim/src/render.rs b/sim/src/render.rs index bdafc9b5dd..d24b59e187 100644 --- a/sim/src/render.rs +++ b/sim/src/render.rs @@ -25,8 +25,8 @@ pub struct DrawPedCrowdInput { pub enum PedCrowdLocation { // bool is contraflow Sidewalk(Traversable, bool), - BldgFrontPath(BuildingID), - LotFrontPath(ParkingLotID), + BldgDriveway(BuildingID), + LotDriveway(ParkingLotID), } #[derive(Clone)] diff --git a/sim/src/router.rs b/sim/src/router.rs index 49599a139e..66c240f8db 100644 --- a/sim/src/router.rs +++ b/sim/src/router.rs @@ -243,25 +243,26 @@ impl Router { target, map, ); - let best = if let Some(ref p) = map.get_b(target).parking { - if p.driving_pos.lane() == current_lane { - let target_dist = p.driving_pos.dist_along(); - // Closest to the building - candidates - .into_iter() - .min_by_key(|(_, pos)| (pos.dist_along() - target_dist).abs()) + let best = + if let Some((driving_pos, _)) = map.get_b(target).driving_connection(map) { + if driving_pos.lane() == current_lane { + let target_dist = driving_pos.dist_along(); + // Closest to the building + candidates + .into_iter() + .min_by_key(|(_, pos)| (pos.dist_along() - target_dist).abs()) + } else { + // Closest to the road endpoint, I guess + candidates + .into_iter() + .min_by_key(|(_, pos)| pos.dist_along()) + } } else { // Closest to the road endpoint, I guess candidates .into_iter() .min_by_key(|(_, pos)| pos.dist_along()) - } - } else { - // Closest to the road endpoint, I guess - candidates - .into_iter() - .min_by_key(|(_, pos)| pos.dist_along()) - }; + }; if let Some((new_spot, new_pos)) = best { if let Some((t, p)) = trip_and_person { events.push(Event::TripPhaseStarting(