starting a generic viewer that works with InitialMap

This commit is contained in:
Dustin Carlino 2019-01-31 15:25:55 -08:00
parent 142bd57aa0
commit 0346166304
11 changed files with 260 additions and 6 deletions

View File

@ -18,4 +18,5 @@ members = [
"synthetic",
"tests",
"tmp_gfx",
"viewer",
]

View File

@ -1,5 +1,11 @@
# TODO - GUI and UX
## Performance
- cache draw stuff
- lazy SimStats
- objects onscreen is overkill for mouseover. can maybe get rid of the min zoom for mouseover thing!
## Quick n easy
- try showing traffic signals by little boxes at the end of lanes

View File

@ -580,3 +580,18 @@ Plugin styles are blocking or ambient. And some can conflict...
- warp... exclusive blocking. apparently we used to still let ambient plugins draw and color stuff while it's active, but meh, doesnt seem important.
- search... argh, this is the one that's SOMETIMES exclusive blocking and sometimes stackable modal.
- finally, make time travel exclusive blocking, since lots of other stuff doesnt actually work with it.
## Quick viewers
I need a way to render and debug InitialMaps. Don't want to squeeze it into
editor, but also don't want to go overboard making a new crate with lots of
copy-pasta code. It's like the synthetic editor. Can we extract out a pattern
for this kind of thing?
- debug functionality
- automatically center on something interesting
- go forwards/backwards
- cleanup
- take away ability to load a map from a saved InitialMap
- make synthetic use stuff
- make halloween use stuff

View File

@ -5,7 +5,7 @@ mod merge;
use crate::raw_data::{StableIntersectionID, StableRoadID};
use crate::{raw_data, MapEdits, LANE_THICKNESS};
use abstutil::Timer;
use geom::{Distance, GPSBounds, PolyLine, Pt2D};
use geom::{Bounds, Distance, GPSBounds, PolyLine, Pt2D};
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
@ -15,6 +15,7 @@ pub struct InitialMap {
pub intersections: BTreeMap<StableIntersectionID, Intersection>,
pub name: String,
pub bounds: Bounds,
versions_saved: usize,
}
@ -42,6 +43,7 @@ impl InitialMap {
name: String,
data: &raw_data::Map,
gps_bounds: &GPSBounds,
bounds: &Bounds,
edits: &MapEdits,
timer: &mut Timer,
) -> InitialMap {
@ -49,6 +51,7 @@ impl InitialMap {
roads: BTreeMap::new(),
intersections: BTreeMap::new(),
name,
bounds: bounds.clone(),
versions_saved: 0,
};
@ -126,11 +129,12 @@ impl InitialMap {
m
}
pub fn save(&mut self, filename: String) {
if true {
return;
}
let path = format!("../in_progress/{:03}_{}", self.versions_saved, filename);
let path = format!("../initial_maps/{:03}_{}", self.versions_saved, filename);
self.versions_saved += 1;
abstutil::write_binary(&path, self).expect(&format!("Saving {} failed", path));
info!("Saved {}", path);

View File

@ -45,7 +45,7 @@ pub struct Map {
impl Map {
pub fn new(path: &str, edits: MapEdits, timer: &mut Timer) -> Result<Map, io::Error> {
if path.starts_with("../in_progress/") {
if path.starts_with("../initial_maps/") {
let initial_map: make::InitialMap = abstutil::read_binary(path, timer)?;
let data: raw_data::Map = abstutil::read_binary(
&format!("../data/raw_maps/{}.abst", initial_map.name),
@ -86,7 +86,7 @@ impl Map {
timer.start("raw_map to InitialMap");
let gps_bounds = data.get_gps_bounds();
let bounds = gps_bounds.to_bounds();
let initial_map = make::InitialMap::new(name, &data, &gps_bounds, &edits, timer);
let initial_map = make::InitialMap::new(name, &data, &gps_bounds, &bounds, &edits, timer);
timer.stop("raw_map to InitialMap");
Map::create_from_raw_and_initial(data, initial_map, gps_bounds, bounds, edits, timer)
}

View File

@ -1,4 +1,5 @@
use crate::make::get_lane_types;
pub use crate::make::InitialMap;
use crate::{AreaType, IntersectionType, RoadSpec};
use geom::{Distance, GPSBounds, LonLat};
use gtfs::Route;

View File

@ -1,3 +1,3 @@
#!/bin/bash
grep -R --exclude-dir=.git --exclude-dir=target --exclude-dir=data --exclude-dir=in_progress --exclude=Cargo.lock --color=auto "$@"
grep -R --exclude-dir=.git --exclude-dir=target --exclude-dir=data --exclude-dir=initial_maps --exclude=Cargo.lock --color=auto "$@"

View File

@ -102,7 +102,7 @@ pub fn load(
);
scenario.instantiate(&mut sim, &map);
(map, sim)
} else if flags.load.contains("data/raw_maps/") || flags.load.contains("in_progress/") {
} else if flags.load.contains("data/raw_maps/") || flags.load.contains("initial_maps/") {
// TODO relative dir is brittle; match more cautiously
let map_name = flags
.load

12
viewer/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "viewer"
version = "0.1.0"
authors = ["Dustin Carlino <dabreegster@gmail.com>"]
edition = "2018"
[dependencies]
aabb-quadtree = "0.1.0"
abstutil = { path = "../abstutil" }
ezgui = { path = "../ezgui" }
geom = { path = "../geom" }
map_model = { path = "../map_model" }

70
viewer/src/main.rs Normal file
View File

@ -0,0 +1,70 @@
mod model;
use crate::model::{World, ID};
use ezgui::{Canvas, Color, EventLoopMode, GfxCtx, Key, Prerender, Text, UserInput, GUI};
use std::{env, process};
struct UI {
canvas: Canvas,
world: World,
state: State,
}
struct State {
selected: Option<ID>,
}
impl UI {
fn new(world: World, canvas: Canvas) -> UI {
UI {
canvas,
world,
state: State { selected: None },
}
}
}
impl GUI<Text> for UI {
fn event(&mut self, input: &mut UserInput, _: &Prerender) -> (EventLoopMode, Text) {
self.canvas.handle_event(input);
if !self.canvas.is_dragging() && input.get_moved_mouse().is_some() {
self.state.selected = self.world.mouseover_something(&self.canvas);
}
if input.unimportant_key_pressed(Key::Escape, "quit") {
process::exit(0);
}
let mut osd = Text::new();
input.populate_osd(&mut osd);
(EventLoopMode::InputOnly, osd)
}
fn get_mut_canvas(&mut self) -> &mut Canvas {
&mut self.canvas
}
fn draw(&self, g: &mut GfxCtx, osd: &Text) {
g.clear(Color::WHITE);
self.world.draw(g, &self.canvas);
if let Some(id) = self.state.selected {
self.world.draw_selected(g, &self.canvas, id);
}
self.canvas
.draw_blocking_text(g, osd.clone(), ezgui::BOTTOM_LEFT);
}
}
fn main() {
let args: Vec<String> = env::args().collect();
ezgui::run(
"Generic viewer of things",
1024.0,
768.0,
|canvas, prerender| UI::new(World::load_initial_map(&args[1], prerender), canvas),
);
}

145
viewer/src/model.rs Normal file
View File

@ -0,0 +1,145 @@
use aabb_quadtree::QuadTree;
use abstutil::{read_binary, Timer};
use ezgui::{Canvas, Color, Drawable, GfxCtx, Prerender, Text};
use geom::{Circle, Distance, Polygon};
use map_model::raw_data;
use map_model::raw_data::{StableIntersectionID, StableRoadID};
use std::collections::HashMap;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum ID {
// Forwards?
HalfRoad(StableRoadID, bool),
Intersection(StableIntersectionID),
}
impl ID {
// Higher shows up in the front
fn zorder(&self) -> usize {
match self {
ID::HalfRoad(_, _) => 0,
ID::Intersection(_) => 1,
}
}
}
struct Object {
polygon: Polygon,
draw: Drawable,
info: Text,
}
pub struct World {
objects: HashMap<ID, Object>,
quadtree: QuadTree<ID>,
}
impl World {
pub fn load_initial_map(filename: &str, prerender: &Prerender) -> World {
let data: raw_data::InitialMap =
read_binary(filename, &mut Timer::new("load data")).unwrap();
let mut w = World {
objects: HashMap::new(),
quadtree: QuadTree::default(data.bounds.as_bbox()),
};
for r in data.roads.values() {
if r.fwd_width > Distance::ZERO {
w.add_obj(
prerender,
ID::HalfRoad(r.id, true),
r.trimmed_center_pts
.shift_right(r.fwd_width / 2.0)
.make_polygons(r.fwd_width),
Color::grey(0.8),
Text::from_line(format!("{} forwards", r.id)),
);
}
if r.back_width > Distance::ZERO {
w.add_obj(
prerender,
ID::HalfRoad(r.id, false),
r.trimmed_center_pts
.shift_left(r.back_width / 2.0)
.make_polygons(r.back_width),
Color::grey(0.6),
Text::from_line(format!("{} backwards", r.id)),
);
}
}
for i in data.intersections.values() {
w.add_obj(
prerender,
ID::Intersection(i.id),
Polygon::new(&i.polygon),
Color::RED,
Text::from_line(format!("{}", i.id)),
);
}
w
}
pub fn draw(&self, g: &mut GfxCtx, canvas: &Canvas) {
let mut objects: Vec<ID> = Vec::new();
for &(id, _, _) in &self.quadtree.query(canvas.get_screen_bounds().as_bbox()) {
objects.push(*id);
}
objects.sort_by_key(|id| id.zorder());
for id in objects {
g.redraw(&self.objects[&id].draw);
}
}
pub fn draw_selected(&self, g: &mut GfxCtx, canvas: &Canvas, id: ID) {
let obj = &self.objects[&id];
g.draw_polygon(Color::BLUE, &obj.polygon);
canvas.draw_text_at(g, obj.info.clone(), obj.polygon.center());
}
pub fn mouseover_something(&self, canvas: &Canvas) -> Option<ID> {
let cursor = canvas.get_cursor_in_map_space()?;
let mut objects: Vec<ID> = Vec::new();
for &(id, _, _) in &self.quadtree.query(
Circle::new(cursor, Distance::meters(3.0))
.get_bounds()
.as_bbox(),
) {
objects.push(*id);
}
objects.sort_by_key(|id| id.zorder());
objects.reverse();
for id in objects {
if self.objects[&id].polygon.contains_pt(cursor) {
return Some(id);
}
}
None
}
fn add_obj(
&mut self,
prerender: &Prerender,
id: ID,
polygon: Polygon,
color: Color,
info: Text,
) {
self.quadtree
.insert_with_box(id, polygon.get_bounds().as_bbox());
let draw = prerender.upload_borrowed(vec![(color, &polygon)]);
self.objects.insert(
id,
Object {
polygon,
draw,
info,
},
);
}
}