mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 12:12:00 +03:00
builder API for World. restoring synthetic labels
This commit is contained in:
parent
672899c20f
commit
180fdd1412
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
use crate::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, Prerender, Text};
|
||||
use crate::{Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, Prerender, Text};
|
||||
use aabb_quadtree::{ItemId, QuadTree};
|
||||
use geom::{Bounds, Circle, Distance, Polygon};
|
||||
use std::collections::HashMap;
|
||||
@ -10,15 +10,63 @@ pub trait ObjectID: Clone + Copy + Debug + Eq + Hash {
|
||||
fn zorder(&self) -> usize;
|
||||
}
|
||||
|
||||
struct Object {
|
||||
pub struct Object<ID: ObjectID> {
|
||||
id: ID,
|
||||
geometry: Vec<(Color, Polygon)>,
|
||||
tooltip: Option<Text>,
|
||||
label: Option<Text>,
|
||||
}
|
||||
|
||||
impl<ID: ObjectID> Object<ID> {
|
||||
pub fn new(id: ID, color: Color, poly: Polygon) -> Object<ID> {
|
||||
Object {
|
||||
id,
|
||||
geometry: vec![(color, poly)],
|
||||
tooltip: None,
|
||||
label: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> ID {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn push(mut self, color: Color, poly: Polygon) -> Object<ID> {
|
||||
self.geometry.push((color, poly));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tooltip(mut self, txt: Text) -> Object<ID> {
|
||||
assert!(self.tooltip.is_none());
|
||||
self.tooltip = Some(txt);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label(mut self, txt: Text) -> Object<ID> {
|
||||
assert!(self.label.is_none());
|
||||
self.label = Some(txt);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn maybe_label(mut self, label: Option<String>) -> Object<ID> {
|
||||
assert!(self.label.is_none());
|
||||
if let Some(s) = label {
|
||||
self.label = Some(Text::from(Line(s)));
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct WorldObject {
|
||||
unioned_polygon: Polygon,
|
||||
draw: Drawable,
|
||||
info: Text,
|
||||
tooltip: Option<Text>,
|
||||
label: Option<Text>,
|
||||
quadtree_id: ItemId,
|
||||
}
|
||||
|
||||
pub struct World<ID: ObjectID> {
|
||||
objects: HashMap<ID, Object>,
|
||||
objects: HashMap<ID, WorldObject>,
|
||||
quadtree: QuadTree<ID>,
|
||||
current_selection: Option<ID>,
|
||||
}
|
||||
@ -40,13 +88,19 @@ impl<ID: ObjectID> World<ID> {
|
||||
objects.sort_by_key(|id| id.zorder());
|
||||
|
||||
for id in objects {
|
||||
g.redraw(&self.objects[&id].draw);
|
||||
let obj = &self.objects[&id];
|
||||
g.redraw(&obj.draw);
|
||||
if let Some(ref txt) = obj.label {
|
||||
g.draw_text_at(txt, obj.unioned_polygon.center());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(id) = self.current_selection {
|
||||
let obj = &self.objects[&id];
|
||||
g.draw_polygon(Color::CYAN, &obj.unioned_polygon);
|
||||
g.draw_mouse_tooltip(&obj.info);
|
||||
if let Some(ref txt) = obj.tooltip {
|
||||
g.draw_mouse_tooltip(txt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,35 +136,30 @@ impl<ID: ObjectID> World<ID> {
|
||||
self.current_selection
|
||||
}
|
||||
|
||||
// TODO This and delete_obj assume the original bounds passed to the quadtree are still valid.
|
||||
pub fn add_obj(
|
||||
&mut self,
|
||||
prerender: &Prerender,
|
||||
id: ID,
|
||||
geometry: Vec<(Color, Polygon)>,
|
||||
info: Text,
|
||||
) {
|
||||
let mut unioned_polygon = geometry[0].1.clone();
|
||||
for (_, p) in &geometry[1..] {
|
||||
// TODO This and delete assume the original bounds passed to the quadtree are still valid.
|
||||
pub fn add(&mut self, prerender: &Prerender, obj: Object<ID>) {
|
||||
let mut unioned_polygon = obj.geometry[0].1.clone();
|
||||
for (_, p) in &obj.geometry[1..] {
|
||||
unioned_polygon = unioned_polygon.union(p.clone());
|
||||
}
|
||||
|
||||
let quadtree_id = self
|
||||
.quadtree
|
||||
.insert_with_box(id, unioned_polygon.get_bounds().as_bbox());
|
||||
let draw = prerender.upload(GeomBatch::from(geometry));
|
||||
.insert_with_box(obj.id, unioned_polygon.get_bounds().as_bbox());
|
||||
let draw = prerender.upload(GeomBatch::from(obj.geometry));
|
||||
self.objects.insert(
|
||||
id,
|
||||
Object {
|
||||
obj.id,
|
||||
WorldObject {
|
||||
unioned_polygon,
|
||||
draw,
|
||||
info,
|
||||
quadtree_id,
|
||||
tooltip: obj.tooltip,
|
||||
label: obj.label,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn delete_obj(&mut self, id: ID) {
|
||||
pub fn delete(&mut self, id: ID) {
|
||||
let obj = self.objects.remove(&id).unwrap();
|
||||
self.quadtree.remove(obj.quadtree_id).unwrap();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use abstutil::Timer;
|
||||
use ezgui::world::{ObjectID, World};
|
||||
use ezgui::world::{Object, ObjectID, World};
|
||||
use ezgui::{
|
||||
hotkey, Color, EventCtx, EventLoopMode, GfxCtx, Key, Line, ModalMenu, Text, WarpingItemSlider,
|
||||
GUI,
|
||||
@ -413,10 +413,10 @@ fn initial_map_to_world(data: &InitialMap, ctx: &mut EventCtx) -> World<ID> {
|
||||
let mut w = World::new(&data.bounds);
|
||||
|
||||
for r in data.roads.values() {
|
||||
w.add_obj(
|
||||
w.add(
|
||||
ctx.prerender,
|
||||
ID::Road(r.id),
|
||||
vec![(
|
||||
Object::new(
|
||||
ID::Road(r.id),
|
||||
if r.trimmed_center_pts.length() < MIN_ROAD_LENGTH {
|
||||
Color::CYAN
|
||||
} else if r.has_parking() {
|
||||
@ -433,24 +433,24 @@ fn initial_map_to_world(data: &InitialMap, ctx: &mut EventCtx) -> World<ID> {
|
||||
})
|
||||
.unwrap()
|
||||
.make_polygons(r.fwd_width + r.back_width),
|
||||
)],
|
||||
Text::from(Line(r.id.to_string())),
|
||||
)
|
||||
.tooltip(Text::from(Line(r.id.to_string()))),
|
||||
);
|
||||
}
|
||||
|
||||
for i in data.intersections.values() {
|
||||
w.add_obj(
|
||||
w.add(
|
||||
ctx.prerender,
|
||||
ID::Intersection(i.id),
|
||||
vec![(
|
||||
Object::new(
|
||||
ID::Intersection(i.id),
|
||||
if i.roads.len() == 2 {
|
||||
Color::RED
|
||||
} else {
|
||||
Color::BLACK
|
||||
},
|
||||
Polygon::new(&i.polygon),
|
||||
)],
|
||||
Text::from(Line(i.id.to_string())),
|
||||
)
|
||||
.tooltip(Text::from(Line(i.id.to_string()))),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use abstutil::{read_binary, Timer};
|
||||
use ezgui::world::{ObjectID, World};
|
||||
use ezgui::{Color, EventCtx, GfxCtx, Prerender, Text};
|
||||
use ezgui::world::{Object, ObjectID, World};
|
||||
use ezgui::{Color, EventCtx, GfxCtx, Prerender};
|
||||
use geom::{Bounds, Circle, Distance, LonLat, PolyLine, Polygon, Pt2D};
|
||||
use map_model::raw_data::{StableIntersectionID, StableRoadID};
|
||||
use map_model::{raw_data, IntersectionType, LaneType, RoadSpec, LANE_THICKNESS};
|
||||
@ -66,7 +66,7 @@ struct Road {
|
||||
}
|
||||
|
||||
impl Road {
|
||||
fn lane_polygons(&self, model: &Model) -> Vec<(Direction, usize, Vec<(Color, Polygon)>)> {
|
||||
fn lanes(&self, id: StableRoadID, model: &Model) -> Vec<Object<ID>> {
|
||||
let base = PolyLine::new(vec![
|
||||
model.intersections[&self.i1].center,
|
||||
model.intersections[&self.i2].center,
|
||||
@ -83,47 +83,43 @@ impl Road {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for (idx, lt) in self.lanes.fwd.iter().enumerate() {
|
||||
let polygon = centered_base
|
||||
.shift_right(LANE_THICKNESS * ((idx as f64) + 0.5))
|
||||
.unwrap()
|
||||
.make_polygons(LANE_THICKNESS);
|
||||
let mut geom = vec![(Road::lt_to_color(*lt), polygon)];
|
||||
let mut obj = Object::new(
|
||||
ID::Lane(id, FORWARDS, idx),
|
||||
Road::lt_to_color(*lt),
|
||||
centered_base
|
||||
.shift_right(LANE_THICKNESS * ((idx as f64) + 0.5))
|
||||
.unwrap()
|
||||
.make_polygons(LANE_THICKNESS),
|
||||
);
|
||||
if idx == 0 {
|
||||
geom.push((
|
||||
obj = obj.push(
|
||||
Color::YELLOW,
|
||||
centered_base.make_polygons(CENTER_LINE_THICKNESS),
|
||||
));
|
||||
);
|
||||
}
|
||||
result.push((FORWARDS, idx, geom));
|
||||
if idx == self.lanes.fwd.len() / 2 {
|
||||
obj = obj.maybe_label(self.fwd_label.clone());
|
||||
}
|
||||
result.push(obj);
|
||||
}
|
||||
for (idx, lt) in self.lanes.back.iter().enumerate() {
|
||||
let polygon = centered_base
|
||||
.shift_left(LANE_THICKNESS * ((idx as f64) + 0.5))
|
||||
.unwrap()
|
||||
.make_polygons(LANE_THICKNESS);
|
||||
result.push((BACKWARDS, idx, vec![(Road::lt_to_color(*lt), polygon)]));
|
||||
let mut obj = Object::new(
|
||||
ID::Lane(id, BACKWARDS, idx),
|
||||
Road::lt_to_color(*lt),
|
||||
centered_base
|
||||
.shift_left(LANE_THICKNESS * ((idx as f64) + 0.5))
|
||||
.unwrap()
|
||||
.make_polygons(LANE_THICKNESS),
|
||||
);
|
||||
if idx == self.lanes.back.len() / 2 {
|
||||
obj = obj.maybe_label(self.back_label.clone());
|
||||
}
|
||||
result.push(obj);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/*fn draw(&self, model: &Model, g: &mut GfxCtx, highlight_fwd: bool, highlight_back: bool) {
|
||||
g.draw_polygon(Color::YELLOW, &base.make_polygons(CENTER_LINE_THICKNESS));
|
||||
|
||||
if let Some(ref label) = self.fwd_label {
|
||||
g.draw_text_at(
|
||||
&Text::from(Line(label)),
|
||||
self.polygon(FORWARDS, model).center(),
|
||||
);
|
||||
}
|
||||
if let Some(ref label) = self.back_label {
|
||||
g.draw_text_at(
|
||||
&Text::from(Line(label)),
|
||||
self.polygon(BACKWARDS, model).center(),
|
||||
);
|
||||
}
|
||||
}*/
|
||||
|
||||
// Copied from render/lane.rs. :(
|
||||
fn lt_to_color(lt: LaneType) -> Color {
|
||||
match lt {
|
||||
@ -163,11 +159,6 @@ impl Model {
|
||||
g.clear(Color::WHITE);
|
||||
|
||||
self.world.draw(g);
|
||||
|
||||
// TODO Always draw labels?
|
||||
/*if let Some(ref label) = i.label {
|
||||
g.draw_text_at(&Text::from(Line(label)), i.center);
|
||||
}*/
|
||||
}
|
||||
|
||||
pub fn handle_mouseover(&mut self, ctx: &EventCtx) {
|
||||
@ -304,7 +295,7 @@ impl Model {
|
||||
if !exclude_bldgs {
|
||||
for (idx, b) in data.buildings.iter().enumerate() {
|
||||
let b = Building {
|
||||
label: None,
|
||||
label: b.osm_tags.get("label").cloned(),
|
||||
center: b.polygon.center(),
|
||||
};
|
||||
m.buildings.insert(idx, b);
|
||||
@ -335,18 +326,18 @@ impl Model {
|
||||
impl Model {
|
||||
fn intersection_added(&mut self, id: StableIntersectionID, prerender: &Prerender) {
|
||||
let i = &self.intersections[&id];
|
||||
self.world.add_obj(
|
||||
self.world.add(
|
||||
prerender,
|
||||
ID::Intersection(id),
|
||||
vec![(
|
||||
Object::new(
|
||||
ID::Intersection(id),
|
||||
match i.intersection_type {
|
||||
IntersectionType::TrafficSignal => Color::GREEN,
|
||||
IntersectionType::StopSign => Color::RED,
|
||||
IntersectionType::Border => Color::BLUE,
|
||||
},
|
||||
i.circle().to_polygon(),
|
||||
)],
|
||||
Text::new(),
|
||||
)
|
||||
.maybe_label(i.label.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
@ -367,7 +358,7 @@ impl Model {
|
||||
}
|
||||
|
||||
pub fn move_i(&mut self, id: StableIntersectionID, center: Pt2D, prerender: &Prerender) {
|
||||
self.world.delete_obj(ID::Intersection(id));
|
||||
self.world.delete(ID::Intersection(id));
|
||||
|
||||
self.intersections.get_mut(&id).unwrap().center = center;
|
||||
|
||||
@ -380,8 +371,12 @@ impl Model {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_i_label(&mut self, id: StableIntersectionID, label: String) {
|
||||
pub fn set_i_label(&mut self, id: StableIntersectionID, label: String, prerender: &Prerender) {
|
||||
self.world.delete(ID::Intersection(id));
|
||||
|
||||
self.intersections.get_mut(&id).unwrap().label = Some(label);
|
||||
|
||||
self.intersection_added(id, prerender);
|
||||
}
|
||||
|
||||
pub fn get_i_label(&self, id: StableIntersectionID) -> Option<String> {
|
||||
@ -389,7 +384,7 @@ impl Model {
|
||||
}
|
||||
|
||||
pub fn toggle_i_type(&mut self, id: StableIntersectionID, prerender: &Prerender) {
|
||||
self.world.delete_obj(ID::Intersection(id));
|
||||
self.world.delete(ID::Intersection(id));
|
||||
|
||||
let i = self.intersections.get_mut(&id).unwrap();
|
||||
i.intersection_type = match i.intersection_type {
|
||||
@ -419,7 +414,7 @@ impl Model {
|
||||
}
|
||||
self.intersections.remove(&id);
|
||||
|
||||
self.world.delete_obj(ID::Intersection(id));
|
||||
self.world.delete(ID::Intersection(id));
|
||||
}
|
||||
|
||||
pub fn get_i_center(&self, id: StableIntersectionID) -> Pt2D {
|
||||
@ -429,15 +424,14 @@ impl Model {
|
||||
|
||||
impl Model {
|
||||
fn road_added(&mut self, id: StableRoadID, prerender: &Prerender) {
|
||||
for (dir, idx, geom) in self.roads[&id].lane_polygons(self) {
|
||||
self.world
|
||||
.add_obj(prerender, ID::Lane(id, dir, idx), geom, Text::new());
|
||||
for obj in self.roads[&id].lanes(id, self) {
|
||||
self.world.add(prerender, obj);
|
||||
}
|
||||
}
|
||||
|
||||
fn road_deleted(&mut self, id: StableRoadID) {
|
||||
for (dir, idx, _) in self.roads[&id].lane_polygons(self) {
|
||||
self.world.delete_obj(ID::Lane(id, dir, idx));
|
||||
for obj in self.roads[&id].lanes(id, self) {
|
||||
self.world.delete(obj.get_id());
|
||||
}
|
||||
}
|
||||
|
||||
@ -489,19 +483,30 @@ impl Model {
|
||||
}
|
||||
|
||||
pub fn swap_lanes(&mut self, id: StableRoadID, prerender: &Prerender) {
|
||||
self.road_deleted(id);
|
||||
|
||||
let lanes = &mut self.roads.get_mut(&id).unwrap().lanes;
|
||||
mem::swap(&mut lanes.fwd, &mut lanes.back);
|
||||
|
||||
self.road_added(id, prerender);
|
||||
}
|
||||
|
||||
pub fn set_r_label(&mut self, pair: (StableRoadID, Direction), label: String) {
|
||||
pub fn set_r_label(
|
||||
&mut self,
|
||||
pair: (StableRoadID, Direction),
|
||||
label: String,
|
||||
prerender: &Prerender,
|
||||
) {
|
||||
self.road_deleted(pair.0);
|
||||
|
||||
let r = self.roads.get_mut(&pair.0).unwrap();
|
||||
if pair.1 {
|
||||
r.fwd_label = Some(label);
|
||||
} else {
|
||||
r.back_label = Some(label);
|
||||
}
|
||||
|
||||
self.road_added(pair.0, prerender);
|
||||
}
|
||||
|
||||
pub fn get_r_label(&self, pair: (StableRoadID, Direction)) -> Option<String> {
|
||||
@ -528,12 +533,10 @@ impl Model {
|
||||
|
||||
impl Model {
|
||||
fn bldg_added(&mut self, id: BuildingID, prerender: &Prerender) {
|
||||
self.world.add_obj(
|
||||
let b = &self.buildings[&id];
|
||||
self.world.add(
|
||||
prerender,
|
||||
ID::Building(id),
|
||||
vec![(Color::BLUE, self.buildings[&id].polygon())],
|
||||
// TODO Always show its label?
|
||||
Text::new(),
|
||||
Object::new(ID::Building(id), Color::BLUE, b.polygon()).maybe_label(b.label.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
@ -552,15 +555,19 @@ impl Model {
|
||||
}
|
||||
|
||||
pub fn move_b(&mut self, id: BuildingID, center: Pt2D, prerender: &Prerender) {
|
||||
self.world.delete_obj(ID::Building(id));
|
||||
self.world.delete(ID::Building(id));
|
||||
|
||||
self.buildings.get_mut(&id).unwrap().center = center;
|
||||
|
||||
self.bldg_added(id, prerender);
|
||||
}
|
||||
|
||||
pub fn set_b_label(&mut self, id: BuildingID, label: String) {
|
||||
pub fn set_b_label(&mut self, id: BuildingID, label: String, prerender: &Prerender) {
|
||||
self.world.delete(ID::Building(id));
|
||||
|
||||
self.buildings.get_mut(&id).unwrap().label = Some(label);
|
||||
|
||||
self.bldg_added(id, prerender);
|
||||
}
|
||||
|
||||
pub fn get_b_label(&self, id: BuildingID) -> Option<String> {
|
||||
@ -568,7 +575,7 @@ impl Model {
|
||||
}
|
||||
|
||||
pub fn remove_b(&mut self, id: BuildingID) {
|
||||
self.world.delete_obj(ID::Building(id));
|
||||
self.world.delete(ID::Building(id));
|
||||
|
||||
self.buildings.remove(&id);
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ impl GUI for UI {
|
||||
"Label the building",
|
||||
self.model.get_b_label(id).unwrap_or_else(String::new),
|
||||
) {
|
||||
self.model.set_b_label(id, label);
|
||||
self.model.set_b_label(id, label, ctx.prerender);
|
||||
self.state = State::Viewing;
|
||||
} else if wizard.aborted() {
|
||||
self.state = State::Viewing;
|
||||
@ -81,7 +81,7 @@ impl GUI for UI {
|
||||
"Label this side of the road",
|
||||
self.model.get_r_label(pair).unwrap_or_else(String::new),
|
||||
) {
|
||||
self.model.set_r_label(pair, label);
|
||||
self.model.set_r_label(pair, label, ctx.prerender);
|
||||
self.state = State::Viewing;
|
||||
} else if wizard.aborted() {
|
||||
self.state = State::Viewing;
|
||||
@ -92,7 +92,7 @@ impl GUI for UI {
|
||||
"Label the intersection",
|
||||
self.model.get_i_label(id).unwrap_or_else(String::new),
|
||||
) {
|
||||
self.model.set_i_label(id, label);
|
||||
self.model.set_i_label(id, label, ctx.prerender);
|
||||
self.state = State::Viewing;
|
||||
} else if wizard.aborted() {
|
||||
self.state = State::Viewing;
|
||||
|
Loading…
Reference in New Issue
Block a user