mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-27 00:12:55 +03:00
starting a generic viewer that works with InitialMap
This commit is contained in:
parent
142bd57aa0
commit
0346166304
@ -18,4 +18,5 @@ members = [
|
||||
"synthetic",
|
||||
"tests",
|
||||
"tmp_gfx",
|
||||
"viewer",
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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;
|
||||
|
2
rgrep.sh
2
rgrep.sh
@ -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 "$@"
|
||||
|
@ -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
12
viewer/Cargo.toml
Normal 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
70
viewer/src/main.rs
Normal 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
145
viewer/src/model.rs
Normal 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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user