abstreet/game/src/ui.rs

530 lines
19 KiB
Rust
Raw Normal View History

2019-04-30 00:40:48 +03:00
use crate::helpers::{ColorScheme, ID};
use crate::options::Options;
use crate::render::{
2019-08-19 01:11:05 +03:00
draw_vehicle, AgentCache, AgentColorScheme, DrawCtx, DrawMap, DrawOptions, DrawPedCrowd,
DrawPedestrian, Renderable, MIN_ZOOM_FOR_DETAIL,
};
use abstutil::{MeasureMemory, Timer};
use ezgui::{Canvas, Color, EventCtx, GfxCtx, Prerender, TextureType};
2019-09-05 00:26:55 +03:00
use geom::{Bounds, Circle, Distance, Pt2D};
use map_model::{Map, Traversable};
2019-06-21 01:15:14 +03:00
use rand::seq::SliceRandom;
2019-11-12 02:28:50 +03:00
use sim::{Analytics, GetDrawAgents, Sim, SimFlags};
2019-04-22 03:30:04 +03:00
pub struct UI {
2019-04-29 19:27:43 +03:00
pub primary: PerMapUI,
// Invariant: This is Some(...) iff we're in A/B test mode or a sub-state.
pub secondary: Option<PerMapUI>,
2019-04-29 19:27:43 +03:00
pub cs: ColorScheme,
2019-08-19 01:11:05 +03:00
pub agent_cs: AgentColorScheme,
2019-11-12 02:28:50 +03:00
pub prebaked: Analytics,
pub opts: Options,
}
2019-04-22 03:30:04 +03:00
impl UI {
2019-12-03 21:56:47 +03:00
pub fn new(flags: Flags, opts: Options, ctx: &mut EventCtx, splash: bool) -> UI {
let cs = ColorScheme::load(opts.color_scheme.clone());
2019-11-12 02:28:50 +03:00
let (primary, prebaked) = ctx.loading_screen("load map", |ctx, mut timer| {
// Always load some small icons.
let mut textures = vec![
("assets/pregame/back.png", TextureType::Stretch),
("assets/pregame/challenges.png", TextureType::Stretch),
("assets/pregame/quit.png", TextureType::Stretch),
("assets/pregame/sandbox.png", TextureType::Stretch),
("assets/pregame/tutorial.png", TextureType::Stretch),
("assets/pregame/logo.png", TextureType::Stretch),
("assets/speed/jump_to_time.png", TextureType::Stretch),
("assets/speed/large_step.png", TextureType::Stretch),
("assets/speed/pause.png", TextureType::Stretch),
("assets/speed/resume.png", TextureType::Stretch),
("assets/speed/slow_down.png", TextureType::Stretch),
("assets/speed/small_step.png", TextureType::Stretch),
("assets/speed/speed_up.png", TextureType::Stretch),
("assets/speed/sunrise.png", TextureType::Stretch),
("assets/speed/sunset.png", TextureType::Stretch),
("assets/ui/edit_bike.png", TextureType::Stretch),
("assets/ui/edit_bus.png", TextureType::Stretch),
("assets/ui/edit_construction.png", TextureType::Stretch),
("assets/ui/edit_contraflow.png", TextureType::Stretch),
("assets/ui/edit_driving.png", TextureType::Stretch),
("assets/ui/edit_parking.png", TextureType::Stretch),
("assets/ui/hamburger.png", TextureType::Stretch),
("assets/ui/hide.png", TextureType::Stretch),
("assets/ui/info.png", TextureType::Stretch),
("assets/ui/location.png", TextureType::Stretch),
2019-10-30 20:26:44 +03:00
("assets/ui/save.png", TextureType::Stretch),
("assets/ui/show.png", TextureType::Stretch),
];
let skip_textures = if flags.textures {
textures.extend(vec![
("assets/water_texture.png", TextureType::Tile),
("assets/grass_texture.png", TextureType::Tile),
("assets/pedestrian.png", TextureType::Stretch),
("assets/car.png", TextureType::CustomUV),
]);
Vec::new()
} else {
vec![
2019-11-01 03:06:03 +03:00
(
"assets/water_texture.png",
cs.get_def("water", Color::rgb(164, 200, 234)),
),
(
"assets/grass_texture.png",
cs.get_def("grass", Color::hex("#94C84A")),
2019-11-01 03:06:03 +03:00
),
("assets/pedestrian.png", Color::rgb(51, 178, 178)),
("assets/car.png", Color::CYAN),
]
};
ctx.set_textures(skip_textures, textures, &mut timer);
2019-11-12 02:28:50 +03:00
let primary = PerMapUI::new(flags, &cs, ctx, &mut timer);
let prebaked: Analytics = abstutil::maybe_read_binary(
abstutil::path_prebaked_results(primary.map.get_name()),
2019-11-12 02:28:50 +03:00
&mut timer,
)
.unwrap_or_else(|_| {
println!("WARNING! No prebaked sim analytics. Only freeform mode will work.");
Analytics::new()
});
(primary, prebaked)
});
2019-06-21 01:15:14 +03:00
let mut rng = primary.current_flags.sim_flags.make_rng();
let rand_focus_pt = primary
.map
.all_buildings()
.choose(&mut rng)
.and_then(|b| ID::Building(b.id).canonical_point(&primary))
.or_else(|| {
primary
.map
.all_lanes()
.choose(&mut rng)
.and_then(|l| ID::Lane(l.id).canonical_point(&primary))
})
.expect("Can't get canonical_point of a random building or lane");
if splash {
ctx.canvas.center_on_map_pt(rand_focus_pt);
} else {
if !ctx.canvas.load_camera_state(primary.map.get_name()) {
println!("Couldn't load camera state, just focusing on an arbitrary building");
ctx.canvas.center_on_map_pt(rand_focus_pt);
2019-06-21 01:15:14 +03:00
}
}
UI {
primary,
secondary: None,
2019-08-19 01:11:05 +03:00
cs,
agent_cs: AgentColorScheme::VehicleTypes,
2019-11-12 02:28:50 +03:00
prebaked,
2019-12-03 21:56:47 +03:00
opts,
}
2018-10-22 05:23:47 +03:00
}
pub fn switch_map(&mut self, ctx: &mut EventCtx, name: &str) {
ctx.canvas.save_camera_state(self.primary.map.get_name());
let mut flags = self.primary.current_flags.clone();
flags.sim_flags.load = abstutil::path_map(name);
2019-12-03 21:56:47 +03:00
*self = UI::new(flags, self.opts.clone(), ctx, false);
}
pub fn draw_ctx(&self) -> DrawCtx<'_> {
DrawCtx {
cs: &self.cs,
map: &self.primary.map,
draw_map: &self.primary.draw_map,
sim: &self.primary.sim,
opts: &self.opts,
}
}
pub fn draw(
&self,
g: &mut GfxCtx,
opts: DrawOptions,
source: &dyn GetDrawAgents,
show_objs: &dyn ShowObject,
) {
let ctx = self.draw_ctx();
let mut sample_intersection: Option<String> = None;
2019-04-29 19:27:43 +03:00
g.clear(self.cs.get_def("true background", Color::BLACK));
g.redraw(&self.primary.draw_map.boundary_polygon);
if g.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL && !g.is_screencap() {
// Unzoomed mode
let layers = show_objs.layers();
if layers.show_areas {
2019-04-29 19:27:43 +03:00
g.redraw(&self.primary.draw_map.draw_all_areas);
}
if layers.show_lanes {
2019-04-29 19:27:43 +03:00
g.redraw(&self.primary.draw_map.draw_all_thick_roads);
}
if layers.show_intersections {
2019-04-29 19:27:43 +03:00
g.redraw(&self.primary.draw_map.draw_all_unzoomed_intersections);
}
if layers.show_buildings {
2019-04-29 19:27:43 +03:00
g.redraw(&self.primary.draw_map.draw_all_buildings);
}
2019-05-10 00:00:07 +03:00
if layers.show_extra_shapes {
for es in &self.primary.draw_map.extra_shapes {
if show_objs.show(&es.get_id()) {
2019-05-10 00:00:07 +03:00
es.draw(g, &opts, &ctx);
}
}
}
// Still show area/extra shape selection when zoomed out.
2019-04-29 19:27:43 +03:00
if let Some(ID::Area(id)) = self.primary.current_selection {
g.draw_polygon(
2019-04-29 19:27:43 +03:00
self.cs.get("selected"),
&ctx.draw_map.get_a(id).get_outline(&ctx.map),
);
2019-05-10 00:00:07 +03:00
} else if let Some(ID::ExtraShape(id)) = self.primary.current_selection {
g.draw_polygon(
self.cs.get("selected"),
&ctx.draw_map.get_es(id).get_outline(&ctx.map),
2019-05-10 00:00:07 +03:00
);
}
let mut cache = self.primary.draw_map.agents.borrow_mut();
cache.draw_unzoomed_agents(source, &self.primary.map, self.agent_cs, &self.cs, g);
} else {
2019-04-29 19:27:43 +03:00
let mut cache = self.primary.draw_map.agents.borrow_mut();
let objects = self.get_renderables_back_to_front(
g.get_screen_bounds(),
&g.prerender,
&g.canvas,
&mut cache,
source,
show_objs,
);
let mut drawn_all_buildings = false;
let mut drawn_all_areas = false;
for obj in objects {
2019-08-16 22:38:39 +03:00
obj.draw(g, &opts, &ctx);
match obj.get_id() {
ID::Building(_) => {
if !drawn_all_buildings {
2019-04-29 19:27:43 +03:00
g.redraw(&self.primary.draw_map.draw_all_buildings);
drawn_all_buildings = true;
}
2019-08-16 22:38:39 +03:00
}
ID::Area(_) => {
if !drawn_all_areas {
2019-04-29 19:27:43 +03:00
g.redraw(&self.primary.draw_map.draw_all_areas);
drawn_all_areas = true;
}
}
_ => {}
};
2019-04-29 19:27:43 +03:00
if self.primary.current_selection == Some(obj.get_id()) {
g.draw_polygon(
self.cs.get_def("selected", Color::RED.alpha(0.7)),
&obj.get_outline(&ctx.map),
);
}
if g.is_screencap() && sample_intersection.is_none() {
if let ID::Intersection(id) = obj.get_id() {
sample_intersection = Some(format!("_i{}", id.0));
}
}
}
}
if let Some(i) = sample_intersection {
g.set_screencap_naming_hint(i);
}
}
// Assumes some defaults.
pub fn recalculate_current_selection(&mut self, ctx: &EventCtx) {
self.primary.current_selection =
self.calculate_current_selection(ctx, &self.primary.sim, &ShowEverything::new(), false);
}
// Because we have to sometimes borrow part of self for GetDrawAgents, this just returns the
// Option<ID> that the caller should assign. When this monolithic UI nonsense is dismantled,
// this weirdness goes away.
pub fn calculate_current_selection(
&self,
ctx: &EventCtx,
source: &dyn GetDrawAgents,
show_objs: &dyn ShowObject,
debug_mode: bool,
) -> Option<ID> {
// Unzoomed mode. Ignore when debugging areas and extra shapes.
if ctx.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL && !debug_mode {
return None;
}
2019-04-30 00:44:43 +03:00
let pt = ctx.canvas.get_cursor_in_map_space()?;
let mut cache = self.primary.draw_map.agents.borrow_mut();
let mut objects = self.get_renderables_back_to_front(
Circle::new(pt, Distance::meters(3.0)).get_bounds(),
ctx.prerender,
ctx.canvas,
&mut cache,
source,
show_objs,
);
objects.reverse();
for obj in objects {
// In unzoomed mode, can only mouseover areas
match obj.get_id() {
ID::Area(_) | ID::ExtraShape(_) => {
if !debug_mode {
continue;
}
}
// Never mouseover these
ID::Road(_) => {
continue;
}
_ => {
if ctx.canvas.cam_zoom < MIN_ZOOM_FOR_DETAIL {
continue;
}
}
2019-04-30 00:44:43 +03:00
}
if obj.contains_pt(pt, &self.primary.map) {
return Some(obj.get_id());
}
2019-04-24 03:26:10 +03:00
}
None
2019-04-24 03:26:10 +03:00
}
2019-02-02 23:49:17 +03:00
// TODO This could probably belong to DrawMap again, but it's annoying to plumb things that
// State does, like show_icons_for() and show().
fn get_renderables_back_to_front<'a>(
&'a self,
bounds: Bounds,
prerender: &Prerender,
canvas: &Canvas,
agents: &'a mut AgentCache,
source: &dyn GetDrawAgents,
show_objs: &dyn ShowObject,
) -> Vec<&'a (dyn Renderable + 'a)> {
2019-04-29 19:27:43 +03:00
let map = &self.primary.map;
let draw_map = &self.primary.draw_map;
let mut areas: Vec<&dyn Renderable> = Vec::new();
let mut lanes: Vec<&dyn Renderable> = Vec::new();
let mut roads: Vec<&dyn Renderable> = Vec::new();
let mut intersections: Vec<&dyn Renderable> = Vec::new();
let mut buildings: Vec<&dyn Renderable> = Vec::new();
let mut extra_shapes: Vec<&dyn Renderable> = Vec::new();
let mut bus_stops: Vec<&dyn Renderable> = Vec::new();
let mut agents_on: Vec<Traversable> = Vec::new();
for id in draw_map.get_matching_objects(bounds) {
if !show_objs.show(&id) {
continue;
}
match id {
ID::Area(id) => areas.push(draw_map.get_a(id)),
ID::Lane(id) => {
lanes.push(draw_map.get_l(id));
agents_on.push(Traversable::Lane(id));
for bs in &map.get_l(id).bus_stops {
bus_stops.push(draw_map.get_bs(*bs));
}
}
ID::Road(id) => {
roads.push(draw_map.get_r(id));
}
ID::Intersection(id) => {
intersections.push(draw_map.get_i(id));
for t in &map.get_i(id).turns {
agents_on.push(Traversable::Turn(*t));
}
}
// TODO front paths will get drawn over buildings, depending on quadtree order.
// probably just need to make them go around other buildings instead of having
// two passes through buildings.
ID::Building(id) => buildings.push(draw_map.get_b(id)),
ID::ExtraShape(id) => extra_shapes.push(draw_map.get_es(id)),
ID::BusStop(_) | ID::Turn(_) | ID::Car(_) | ID::Pedestrian(_) | ID::PedCrowd(_) => {
panic!("{:?} shouldn't be in the quadtree", id)
}
}
}
// From background to foreground Z-order
let mut borrows: Vec<&dyn Renderable> = Vec::new();
borrows.extend(areas);
borrows.extend(lanes);
borrows.extend(roads);
borrows.extend(intersections);
borrows.extend(buildings);
borrows.extend(extra_shapes);
borrows.extend(bus_stops);
// Expand all of the Traversables into agents, populating the cache if needed.
{
2019-02-26 23:50:43 +03:00
let time = source.time();
let step_count = source.step_count();
for on in &agents_on {
2019-02-26 23:50:43 +03:00
if !agents.has(time, *on) {
let mut list: Vec<Box<dyn Renderable>> = Vec::new();
for c in source.get_draw_cars(*on, map).into_iter() {
list.push(draw_vehicle(
c,
map,
prerender,
canvas,
&self.cs,
self.agent_cs,
self.primary.current_flags.textures,
));
}
let (loners, crowds) = source.get_draw_peds(*on, map);
for p in loners {
2019-05-06 22:12:31 +03:00
list.push(Box::new(DrawPedestrian::new(
p,
step_count,
map,
prerender,
canvas,
&self.cs,
self.agent_cs,
self.primary.current_flags.textures,
2019-05-06 22:12:31 +03:00
)));
}
for c in crowds {
list.push(Box::new(DrawPedCrowd::new(c, map, prerender, &self.cs)));
}
2019-02-26 23:50:43 +03:00
agents.put(time, *on, list);
}
}
}
for on in agents_on {
for obj in agents.get(on) {
borrows.push(obj);
}
}
// This is a stable sort.
borrows.sort_by_key(|x| x.get_zorder());
borrows
}
2018-06-22 21:01:44 +03:00
}
pub struct ShowLayers {
pub show_buildings: bool,
pub show_intersections: bool,
pub show_lanes: bool,
pub show_areas: bool,
pub show_extra_shapes: bool,
2019-08-16 22:38:39 +03:00
pub show_labels: bool,
}
impl ShowLayers {
pub fn new() -> ShowLayers {
ShowLayers {
show_buildings: true,
show_intersections: true,
show_lanes: true,
show_areas: true,
show_extra_shapes: true,
2019-08-16 22:38:39 +03:00
show_labels: false,
}
}
}
pub trait ShowObject {
fn show(&self, obj: &ID) -> bool;
fn layers(&self) -> &ShowLayers;
}
pub struct ShowEverything {
layers: ShowLayers,
}
impl ShowEverything {
pub fn new() -> ShowEverything {
ShowEverything {
layers: ShowLayers::new(),
}
}
}
impl ShowObject for ShowEverything {
fn show(&self, _: &ID) -> bool {
true
}
fn layers(&self) -> &ShowLayers {
&self.layers
}
}
2019-04-29 19:27:43 +03:00
2019-09-19 03:29:34 +03:00
#[derive(Clone)]
2019-04-29 19:27:43 +03:00
pub struct Flags {
pub sim_flags: SimFlags,
pub kml: Option<String>,
2019-09-19 03:29:34 +03:00
pub draw_lane_markings: bool,
// Number of agents to generate when requested. If unspecified, trips to/from borders will be
// included.
pub num_agents: Option<usize>,
2019-09-19 03:29:34 +03:00
pub textures: bool,
2019-04-29 19:27:43 +03:00
}
// All of the state that's bound to a specific map+edit has to live here.
pub struct PerMapUI {
pub map: Map,
pub draw_map: DrawMap,
pub sim: Sim,
pub current_selection: Option<ID>,
pub current_flags: Flags,
2019-09-05 00:26:55 +03:00
pub last_warped_from: Option<(Pt2D, f64)>,
2019-04-29 19:27:43 +03:00
}
impl PerMapUI {
pub fn new(flags: Flags, cs: &ColorScheme, ctx: &mut EventCtx, timer: &mut Timer) -> PerMapUI {
let mut mem = MeasureMemory::new();
2019-08-16 23:08:53 +03:00
let (map, sim, _) = flags.sim_flags.load(timer);
mem.reset("Map and Sim", timer);
timer.start("draw_map");
let draw_map = DrawMap::new(&map, &flags, cs, ctx, timer);
timer.stop("draw_map");
mem.reset("DrawMap", timer);
2019-04-29 19:27:43 +03:00
PerMapUI {
map,
draw_map,
sim,
current_selection: None,
current_flags: flags.clone(),
2019-09-05 00:26:55 +03:00
last_warped_from: None,
2019-04-29 19:27:43 +03:00
}
}
pub fn clear_sim(&mut self) {
self.sim = Sim::new(
&self.map,
self.current_flags.sim_flags.opts.clone(),
&mut Timer::new("reset simulation"),
);
}
2019-04-29 19:27:43 +03:00
}