diff --git a/editor/src/mission/all_trips.rs b/editor/src/mission/all_trips.rs index 396ea559dd..7c96b35cfd 100644 --- a/editor/src/mission/all_trips.rs +++ b/editor/src/mission/all_trips.rs @@ -1,9 +1,11 @@ use crate::common::CommonState; use crate::mission::{clip_trips, Trip}; use crate::ui::{ShowEverything, UI}; -use abstutil::{prettyprint_usize, Timer}; +use abstutil::prettyprint_usize; use ezgui::{Color, EventCtx, GeomBatch, GfxCtx, Key, ModalMenu, Text}; use geom::{Circle, Distance, Duration}; +use map_model::LANE_THICKNESS; +use popdat::psrc::Mode; use popdat::PopDat; pub struct TripsVisualizer { @@ -16,28 +18,40 @@ pub struct TripsVisualizer { impl TripsVisualizer { pub fn new(ctx: &mut EventCtx, ui: &UI) -> TripsVisualizer { - let mut timer = Timer::new("initialize popdat"); - let popdat: PopDat = abstutil::read_binary("../data/shapes/popdat", &mut timer) - .expect("Couldn't load popdat"); - let mut trips = clip_trips(&popdat, ui, &mut timer); - timer.start_iter("calculate routes", trips.len()); - for trip in trips.iter_mut() { - timer.next(); - let req = trip.path_req(&ui.primary.map); - trip.route = ui - .primary - .map - .pathfind(req.clone()) - .and_then(|path| path.trace(&ui.primary.map, req.start.dist_along(), None)); - } + let trips = ctx.loading_screen(|_, mut timer| { + let popdat: PopDat = abstutil::read_binary("../data/shapes/popdat", &mut timer) + .expect("Couldn't load popdat"); + let mut all_trips = clip_trips(&popdat, ui, &mut timer); + timer.start_iter("calculate routes", all_trips.len()); + let mut final_trips = Vec::new(); + for mut trip in all_trips.drain(..) { + timer.next(); + let req = trip.path_req(&ui.primary.map); + if let Some(route) = ui + .primary + .map + .pathfind(req.clone()) + .and_then(|path| path.trace(&ui.primary.map, req.start.dist_along(), None)) + { + trip.route = Some(route); + final_trips.push(trip); + } else { + timer.warn(format!("Couldn't satisfy {}", req)); + } + } + final_trips + }); + // TODO It'd be awesome to use the generic timer controls for this TripsVisualizer { menu: ModalMenu::new( "Trips Visualizer", vec![ (Some(Key::Escape), "quit"), - (Some(Key::Dot), "forwards 1 minute"), - (Some(Key::Comma), "backwards 1 minute"), + (Some(Key::Dot), "forwards 10 seconds"), + (Some(Key::RightArrow), "forwards 30 minutes"), + (Some(Key::Comma), "backwards 10 seconds"), + (Some(Key::LeftArrow), "backwards 30 minutes"), (Some(Key::F), "goto start of day"), (Some(Key::L), "goto end of day"), ], @@ -63,14 +77,22 @@ impl TripsVisualizer { ui.primary.current_selection = ui.handle_mouseover(ctx, &ui.primary.sim, &ShowEverything::new(), false); - let last_time = Duration::parse("23:59:00.0").unwrap(); + let last_time = Duration::parse("23:59:50.0").unwrap(); + let ten_secs = Duration::seconds(10.0); + let thirty_mins = Duration::minutes(30); if self.menu.action("quit") { return true; - } else if self.time != last_time && self.menu.action("forwards 1 minute") { - self.time += Duration::minutes(1); - } else if self.time != Duration::ZERO && self.menu.action("backwards 1 minute") { - self.time -= Duration::minutes(1); + } else if self.time != last_time && self.menu.action("forwards 10 seconds") { + self.time += ten_secs; + } else if self.time + thirty_mins <= last_time && self.menu.action("forwards 30 minutes") { + self.time += thirty_mins; + } else if self.time != Duration::ZERO && self.menu.action("backwards 10 seconds") { + self.time -= ten_secs; + } else if self.time - thirty_mins >= Duration::ZERO + && self.menu.action("backwards 30 minutes") + { + self.time -= thirty_mins; } else if self.time != Duration::ZERO && self.menu.action("goto start of day") { self.time = Duration::ZERO; } else if self.time != last_time && self.menu.action("goto end of day") { @@ -95,18 +117,41 @@ impl TripsVisualizer { let mut batch = GeomBatch::new(); for idx in &self.active_trips { let trip = &self.trips[*idx]; - let from = ui.primary.map.get_b(trip.from); - let to = ui.primary.map.get_b(trip.to); + let percent = (self.time - trip.depart_at) / trip.trip_time; - let percent = ((self.time - trip.depart_at) / trip.trip_time) as f32; - batch.push( - Color::RED.alpha(1.0 - percent), - Circle::new(from.polygon.center(), Distance::meters(100.0)).to_polygon(), - ); - batch.push( - Color::BLUE.alpha(percent), - Circle::new(to.polygon.center(), Distance::meters(100.0)).to_polygon(), - ); + if true { + let pl = trip.route.as_ref().unwrap(); + let color = match trip.mode { + Mode::Drive => Color::RED, + Mode::Walk => Color::GREEN, + Mode::Bike => Color::BLUE, + } + .alpha(0.5); + batch.push( + color, + Circle::new( + pl.dist_along(percent * pl.length()).0, + // Draw bigger circles when zoomed out, but don't go smaller than the lane + // once fully zoomed in. + (Distance::meters(10.0) / g.canvas.cam_zoom).max(LANE_THICKNESS), + ) + .to_polygon(), + ); + // TODO Draw the entire route, once sharp angled polylines are fixed + } else { + // Draw the start and end, gradually fading the color. + let from = ui.primary.map.get_b(trip.from); + let to = ui.primary.map.get_b(trip.to); + + batch.push( + Color::RED.alpha(1.0 - (percent as f32)), + Circle::new(from.polygon.center(), Distance::meters(100.0)).to_polygon(), + ); + batch.push( + Color::BLUE.alpha(percent as f32), + Circle::new(to.polygon.center(), Distance::meters(100.0)).to_polygon(), + ); + } } batch.draw(g); diff --git a/map_model/src/pathfind/mod.rs b/map_model/src/pathfind/mod.rs index ec247a0844..2dfc4d7346 100644 --- a/map_model/src/pathfind/mod.rs +++ b/map_model/src/pathfind/mod.rs @@ -8,6 +8,7 @@ use crate::{BusRouteID, BusStopID, LaneID, LaneType, Map, Position, Traversable, use geom::{Distance, PolyLine}; use serde_derive::{Deserialize, Serialize}; use std::collections::{BTreeSet, VecDeque}; +use std::fmt; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum PathStep { @@ -233,7 +234,7 @@ impl Path { } } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct PathRequest { pub start: Position, pub end: Position, @@ -241,6 +242,27 @@ pub struct PathRequest { pub can_use_bus_lanes: bool, } +impl fmt::Display for PathRequest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "PathRequest({} along {}... to {} along {}", + self.start.dist_along(), + self.start.lane(), + self.end.dist_along(), + self.end.lane() + )?; + // TODO can_use_bike_lanes and can_use_bus_lanes are mutex, encode that directly. + if self.can_use_bike_lanes { + write!(f, ", bike lanes)") + } else if self.can_use_bus_lanes { + write!(f, ", bus lanes)") + } else { + write!(f, ")") + } + } +} + fn validate(map: &Map, steps: &Vec) { if steps.is_empty() { panic!("Empty Path"); diff --git a/sim/src/make/spawner.rs b/sim/src/make/spawner.rs index ff7463e84c..81421ea9c4 100644 --- a/sim/src/make/spawner.rs +++ b/sim/src/make/spawner.rs @@ -167,7 +167,7 @@ impl TripSpawner { self.trips.drain(..).zip(paths) { if maybe_path.is_none() { - timer.warn(format!("{:?} couldn't find the first path {:?}", spec, req)); + timer.warn(format!("{:?} couldn't find the first path {}", spec, req)); continue; } let path = maybe_path.unwrap();