diff --git a/editor/src/plugins/show_activity.rs b/editor/src/plugins/show_activity.rs index f786418ad3..179c948aac 100644 --- a/editor/src/plugins/show_activity.rs +++ b/editor/src/plugins/show_activity.rs @@ -1,14 +1,14 @@ -use ezgui::Color; -use map_model::LaneID; -use objects::{Ctx, DEBUG, ID}; +use ezgui::{Color, GfxCtx}; +use geom::{Bounds, Pt2D}; +use map_model::Map; +use objects::{Ctx, DEBUG}; use piston::input::Key; use plugins::{Plugin, PluginCtx}; -use sim::Tick; -use std::collections::HashSet; +use sim::{Sim, Tick}; pub enum ShowActivityState { Inactive, - Active(Tick, HashSet), + Active(Tick, Heatmap), } impl ShowActivityState { @@ -29,21 +29,26 @@ impl Plugin for ShowActivityState { ) { new_state = Some(ShowActivityState::Active( ctx.primary.sim.time, - ctx.primary.sim.find_lanes_with_movement(), + active_agent_heatmap( + ctx.canvas.get_screen_bounds(), + &ctx.primary.sim, + &ctx.primary.map, + ), )); } } - ShowActivityState::Active(time, _) => { + ShowActivityState::Active(time, ref old_heatmap) => { if ctx .input .key_pressed(Key::Return, "stop showing lanes with active traffic") { new_state = Some(ShowActivityState::Inactive); } - if *time != ctx.primary.sim.time { + let bounds = ctx.canvas.get_screen_bounds(); + if *time != ctx.primary.sim.time || bounds != old_heatmap.bounds { new_state = Some(ShowActivityState::Active( ctx.primary.sim.time, - ctx.primary.sim.find_lanes_with_movement(), + active_agent_heatmap(bounds, &ctx.primary.sim, &ctx.primary.map), )); } } @@ -57,18 +62,81 @@ impl Plugin for ShowActivityState { } } - fn color_for(&self, obj: ID, ctx: Ctx) -> Option { - match (obj, self) { - (ID::Lane(id), ShowActivityState::Active(_, ref lanes)) => { - if lanes.contains(&id) { - None - } else { - // TODO I want to modify the color that'd happen anyway and just make it more - // transparent. But how? - Some(ctx.cs.get("inactive lane", Color::rgba(0, 0, 0, 0.2))) - } - } - _ => None, + fn draw(&self, g: &mut GfxCtx, _ctx: Ctx) { + if let ShowActivityState::Active(_, ref heatmap) = self { + heatmap.draw(g); } } } + +// A nice 10x10 +const NUM_TILES: usize = 10; + +pub struct Heatmap { + bounds: Bounds, + + counts: [[usize; NUM_TILES]; NUM_TILES], + max: usize, +} + +impl Heatmap { + fn new(bounds: Bounds) -> Heatmap { + Heatmap { + bounds, + counts: [[0; NUM_TILES]; NUM_TILES], + max: 0, + } + } + + fn add(&mut self, pt: Pt2D) { + // TODO Could also query sim with this filter + if !self.bounds.contains(pt) { + return; + } + + let x = ((pt.x() - self.bounds.min_x) / (self.bounds.max_x - self.bounds.min_x) + * (NUM_TILES as f64)) + .floor() as usize; + let y = ((pt.y() - self.bounds.min_y) / (self.bounds.max_y - self.bounds.min_y) + * (NUM_TILES as f64)) + .floor() as usize; + self.counts[x][y] += 1; + self.max = self.max.max(self.counts[x][y]); + } + + fn draw(&self, g: &mut GfxCtx) { + let tile_width = (self.bounds.max_x - self.bounds.min_x) / (NUM_TILES as f64); + let tile_height = (self.bounds.max_y - self.bounds.min_y) / (NUM_TILES as f64); + + for x in 0..NUM_TILES { + for y in 0..NUM_TILES { + if self.counts[x][y] == 0 { + continue; + } + + let percent = (self.counts[x][y] as f32) / (self.max as f32); + // TODO Map percent to hot/cold colors + let color = Color::rgba(255, 0, 0, percent); + g.draw_rectangle( + color, + [ + (x as f64) * tile_width, + (y as f64) * tile_height, + tile_width, + tile_height, + ], + ); + } + } + } +} + +fn active_agent_heatmap(bounds: Bounds, sim: &Sim, map: &Map) -> Heatmap { + let mut h = Heatmap::new(bounds); + for trip in sim.get_active_trips().into_iter() { + if let Some(pt) = sim.get_canonical_point_for_trip(trip, map) { + h.add(pt); + } + } + h +} diff --git a/geom/src/pt.rs b/geom/src/pt.rs index 59c6df72a3..339895d082 100644 --- a/geom/src/pt.rs +++ b/geom/src/pt.rs @@ -144,7 +144,7 @@ impl From for HashablePt2D { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Bounds { pub min_x: f64, pub min_y: f64, diff --git a/sim/src/driving.rs b/sim/src/driving.rs index 8743749461..ee622f41f6 100644 --- a/sim/src/driving.rs +++ b/sim/src/driving.rs @@ -874,17 +874,6 @@ impl DrivingSimState { (moving_cars, stuck_cars, buses) } - - pub fn find_lanes_with_movement(&self, active: &mut HashSet) { - for c in self.cars.values() { - if c.speed > kinematics::EPSILON_SPEED { - match c.on { - Traversable::Lane(id) => active.insert(id), - Traversable::Turn(t) => active.insert(t.src), - }; - } - } - } } pub struct CreateCar { diff --git a/sim/src/sim.rs b/sim/src/sim.rs index 7a7e687f03..806d84f183 100644 --- a/sim/src/sim.rs +++ b/sim/src/sim.rs @@ -452,15 +452,6 @@ impl Sim { trips_with_ab_test_divergence: 0, } } - - // TODO This is another query like summarize() - // Turns count as activity on the origin lane - pub fn find_lanes_with_movement(&self) -> HashSet { - let mut active: HashSet = HashSet::new(); - self.driving_state.find_lanes_with_movement(&mut active); - self.walking_state.find_lanes_with_movement(&mut active); - active - } } pub struct Benchmark { diff --git a/sim/src/walking.rs b/sim/src/walking.rs index ede46c66c4..0da4e04a79 100644 --- a/sim/src/walking.rs +++ b/sim/src/walking.rs @@ -680,17 +680,6 @@ impl WalkingSimState { (moving_peds, stuck_peds) } - - pub fn find_lanes_with_movement(&self, active: &mut HashSet) { - for p in self.peds.values() { - if p.waiting_for.is_none() { - match p.on { - Traversable::Lane(id) => active.insert(id), - Traversable::Turn(t) => active.insert(t.src), - }; - } - } - } } fn is_contraflow(map: &Map, from: LaneID, to: LaneID) -> bool {