mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 06:55:40 +03:00
Organize modal filter code before big changes
This commit is contained in:
parent
6826145de2
commit
e0b18d6d07
222
game/src/ltn/filters.rs
Normal file
222
game/src/ltn/filters.rs
Normal file
@ -0,0 +1,222 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use geom::{Circle, Distance, Line};
|
||||
use map_model::{IntersectionID, Map, RoadID, RoutingParams, TurnID};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{Color, EventCtx, GeomBatch};
|
||||
|
||||
use super::Neighborhood;
|
||||
use crate::app::App;
|
||||
|
||||
/// Stored in App session state
|
||||
#[derive(Default)]
|
||||
pub struct ModalFilters {
|
||||
/// For filters placed along a road, where is the filter located?
|
||||
pub roads: BTreeMap<RoadID, Distance>,
|
||||
pub intersections: BTreeMap<IntersectionID, DiagonalFilter>,
|
||||
}
|
||||
|
||||
/// A diagonal filter exists in an intersection. It's defined by two roads (the order is
|
||||
/// arbitrary). When all of the intersection's roads are sorted in clockwise order, this pair of
|
||||
/// roads splits the ordering into two groups. Turns in each group are still possible, but not
|
||||
/// across groups.
|
||||
///
|
||||
/// TODO Be careful with PartialEq! At a 4-way intersection, the same filter can be expressed as a
|
||||
/// different pair of two roads. And the (r1, r2) ordering is also arbitrary.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct DiagonalFilter {
|
||||
r1: RoadID,
|
||||
r2: RoadID,
|
||||
i: IntersectionID,
|
||||
|
||||
group1: BTreeSet<RoadID>,
|
||||
group2: BTreeSet<RoadID>,
|
||||
}
|
||||
|
||||
impl ModalFilters {
|
||||
/// Modify RoutingParams to respect these modal filters
|
||||
pub fn update_routing_params(&self, params: &mut RoutingParams) {
|
||||
params.avoid_roads.extend(self.roads.keys().cloned());
|
||||
for filter in self.intersections.values() {
|
||||
params
|
||||
.avoid_movements_between
|
||||
.extend(filter.avoid_movements_between_roads());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allows_turn(&self, t: TurnID) -> bool {
|
||||
if let Some(filter) = self.intersections.get(&t.parent) {
|
||||
return filter.allows_turn(t.src.road, t.dst.road);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Draw all modal filters. If `only_neighborhood` is specified, only draw filters belonging to
|
||||
/// one area.
|
||||
pub fn draw(
|
||||
&self,
|
||||
ctx: &EventCtx,
|
||||
map: &Map,
|
||||
only_neighborhood: Option<&Neighborhood>,
|
||||
) -> ToggleZoomed {
|
||||
let mut batch = ToggleZoomed::builder();
|
||||
for (r, dist) in &self.roads {
|
||||
if only_neighborhood
|
||||
.map(|n| !n.orig_perimeter.interior.contains(r))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let road = map.get_r(*r);
|
||||
if let Ok((pt, angle)) = road.center_pts.dist_along(*dist) {
|
||||
let road_width = road.get_width();
|
||||
|
||||
batch
|
||||
.unzoomed
|
||||
.push(Color::RED, Circle::new(pt, road_width).to_polygon());
|
||||
batch.unzoomed.push(
|
||||
Color::WHITE,
|
||||
Line::must_new(
|
||||
pt.project_away(0.8 * road_width, angle.rotate_degs(90.0)),
|
||||
pt.project_away(0.8 * road_width, angle.rotate_degs(-90.0)),
|
||||
)
|
||||
.make_polygons(Distance::meters(7.0)),
|
||||
);
|
||||
|
||||
// TODO Only cover the driving/parking lanes (and center appropriately)
|
||||
draw_zoomed_planters(
|
||||
ctx,
|
||||
&mut batch.zoomed,
|
||||
Line::must_new(
|
||||
pt.project_away(0.3 * road_width, angle.rotate_degs(90.0)),
|
||||
pt.project_away(0.3 * road_width, angle.rotate_degs(-90.0)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
for (i, filter) in &self.intersections {
|
||||
if only_neighborhood
|
||||
.map(|n| !n.interior_intersections.contains(i))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let line = filter.geometry(map);
|
||||
batch
|
||||
.unzoomed
|
||||
.push(Color::RED, line.make_polygons(Distance::meters(3.0)));
|
||||
|
||||
draw_zoomed_planters(
|
||||
ctx,
|
||||
&mut batch.zoomed,
|
||||
line.percent_slice(0.3, 0.7).unwrap_or(line),
|
||||
);
|
||||
}
|
||||
batch.build(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl DiagonalFilter {
|
||||
/// Find all possible diagonal filters at an intersection
|
||||
pub fn filters_for(app: &App, i: IntersectionID) -> Vec<DiagonalFilter> {
|
||||
let map = &app.primary.map;
|
||||
let roads = map.get_i(i).get_roads_sorted_by_incoming_angle(map);
|
||||
// TODO Handle >4-ways
|
||||
if roads.len() != 4 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
vec![
|
||||
DiagonalFilter::new(map, i, roads[0], roads[1]),
|
||||
DiagonalFilter::new(map, i, roads[1], roads[2]),
|
||||
]
|
||||
}
|
||||
|
||||
fn new(map: &Map, i: IntersectionID, r1: RoadID, r2: RoadID) -> DiagonalFilter {
|
||||
let mut roads = map.get_i(i).get_roads_sorted_by_incoming_angle(map);
|
||||
// Make self.r1 be the first entry
|
||||
while roads[0] != r1 {
|
||||
roads.rotate_right(1);
|
||||
}
|
||||
|
||||
let mut group1 = BTreeSet::new();
|
||||
group1.insert(roads.remove(0));
|
||||
loop {
|
||||
let next = roads.remove(0);
|
||||
group1.insert(next);
|
||||
if next == r2 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// This is only true for 4-ways...
|
||||
assert_eq!(group1.len(), 2);
|
||||
assert_eq!(roads.len(), 2);
|
||||
|
||||
DiagonalFilter {
|
||||
r1,
|
||||
r2,
|
||||
i,
|
||||
group1,
|
||||
group2: roads.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Physically where is the filter placed?
|
||||
fn geometry(&self, map: &Map) -> Line {
|
||||
let r1 = map.get_r(self.r1);
|
||||
let r2 = map.get_r(self.r2);
|
||||
|
||||
// Orient the road to face the intersection
|
||||
let mut pl1 = r1.center_pts.clone();
|
||||
if r1.src_i == self.i {
|
||||
pl1 = pl1.reversed();
|
||||
}
|
||||
let mut pl2 = r2.center_pts.clone();
|
||||
if r2.src_i == self.i {
|
||||
pl2 = pl2.reversed();
|
||||
}
|
||||
|
||||
// The other combinations of left/right here would produce points or a line across just one
|
||||
// road
|
||||
let pt1 = pl1.must_shift_right(r1.get_half_width()).last_pt();
|
||||
let pt2 = pl2.must_shift_left(r2.get_half_width()).last_pt();
|
||||
Line::must_new(pt1, pt2)
|
||||
}
|
||||
|
||||
pub fn allows_turn(&self, from: RoadID, to: RoadID) -> bool {
|
||||
self.group1.contains(&from) == self.group1.contains(&to)
|
||||
}
|
||||
|
||||
fn avoid_movements_between_roads(&self) -> Vec<(RoadID, RoadID)> {
|
||||
let mut pairs = Vec::new();
|
||||
for from in &self.group1 {
|
||||
for to in &self.group2 {
|
||||
pairs.push((*from, *to));
|
||||
pairs.push((*to, *from));
|
||||
}
|
||||
}
|
||||
pairs
|
||||
}
|
||||
}
|
||||
|
||||
// Draw two planters on each end of a line. They'll be offset so that they don't exceed the
|
||||
// endpoints.
|
||||
fn draw_zoomed_planters(ctx: &EventCtx, batch: &mut GeomBatch, line: Line) {
|
||||
let planter = GeomBatch::load_svg(ctx, "system/assets/map/planter.svg");
|
||||
let planter_width = planter.get_dims().width;
|
||||
let scaled_planter = planter.scale(0.3 * line.length().inner_meters() / planter_width);
|
||||
|
||||
batch.append(
|
||||
scaled_planter
|
||||
.clone()
|
||||
.centered_on(line.must_dist_along(0.15 * line.length()))
|
||||
.rotate(line.angle()),
|
||||
);
|
||||
batch.append(
|
||||
scaled_planter
|
||||
.centered_on(line.must_dist_along(0.85 * line.length()))
|
||||
.rotate(line.angle()),
|
||||
);
|
||||
}
|
@ -2,21 +2,23 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use maplit::btreeset;
|
||||
|
||||
use geom::{Circle, Distance, Line, Polygon};
|
||||
use geom::{Distance, Polygon};
|
||||
use map_gui::tools::DrawRoadLabels;
|
||||
use map_model::{IntersectionID, Map, PathConstraints, Perimeter, RoadID, RoutingParams, TurnID};
|
||||
use map_model::{IntersectionID, Map, PathConstraints, Perimeter, RoadID};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{Color, Drawable, EventCtx, GeomBatch};
|
||||
use widgetry::{Drawable, EventCtx, GeomBatch};
|
||||
|
||||
use crate::app::App;
|
||||
|
||||
pub use browse::BrowseNeighborhoods;
|
||||
pub use filters::{DiagonalFilter, ModalFilters};
|
||||
pub use partition::Partitioning;
|
||||
|
||||
mod auto;
|
||||
mod browse;
|
||||
mod connectivity;
|
||||
mod draw_cells;
|
||||
mod filters;
|
||||
mod partition;
|
||||
mod pathfinding;
|
||||
mod per_neighborhood;
|
||||
@ -40,115 +42,6 @@ pub struct Neighborhood {
|
||||
labels: DrawRoadLabels,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ModalFilters {
|
||||
/// For filters placed along a road, where is the filter located?
|
||||
pub roads: BTreeMap<RoadID, Distance>,
|
||||
pub intersections: BTreeMap<IntersectionID, DiagonalFilter>,
|
||||
}
|
||||
|
||||
impl ModalFilters {
|
||||
/// Modify RoutingParams to respect these modal filters
|
||||
pub fn update_routing_params(&self, params: &mut RoutingParams) {
|
||||
params.avoid_roads.extend(self.roads.keys().cloned());
|
||||
for filter in self.intersections.values() {
|
||||
params
|
||||
.avoid_movements_between
|
||||
.extend(filter.avoid_movements_between_roads());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allows_turn(&self, t: TurnID) -> bool {
|
||||
if let Some(filter) = self.intersections.get(&t.parent) {
|
||||
return filter.allows_turn(t.src.road, t.dst.road);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Draw all modal filters. If `only_neighborhood` is specified, only draw filters belonging to
|
||||
/// one area.
|
||||
pub fn draw(
|
||||
&self,
|
||||
ctx: &EventCtx,
|
||||
map: &Map,
|
||||
only_neighborhood: Option<&Neighborhood>,
|
||||
) -> ToggleZoomed {
|
||||
let mut batch = ToggleZoomed::builder();
|
||||
for (r, dist) in &self.roads {
|
||||
if only_neighborhood
|
||||
.map(|n| !n.orig_perimeter.interior.contains(r))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let road = map.get_r(*r);
|
||||
if let Ok((pt, angle)) = road.center_pts.dist_along(*dist) {
|
||||
let road_width = road.get_width();
|
||||
|
||||
batch
|
||||
.unzoomed
|
||||
.push(Color::RED, Circle::new(pt, road_width).to_polygon());
|
||||
batch.unzoomed.push(
|
||||
Color::WHITE,
|
||||
Line::must_new(
|
||||
pt.project_away(0.8 * road_width, angle.rotate_degs(90.0)),
|
||||
pt.project_away(0.8 * road_width, angle.rotate_degs(-90.0)),
|
||||
)
|
||||
.make_polygons(Distance::meters(7.0)),
|
||||
);
|
||||
|
||||
// TODO Only cover the driving/parking lanes (and center appropriately)
|
||||
draw_zoomed_planters(
|
||||
ctx,
|
||||
&mut batch.zoomed,
|
||||
Line::must_new(
|
||||
pt.project_away(0.3 * road_width, angle.rotate_degs(90.0)),
|
||||
pt.project_away(0.3 * road_width, angle.rotate_degs(-90.0)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
for (i, filter) in &self.intersections {
|
||||
if only_neighborhood
|
||||
.map(|n| !n.interior_intersections.contains(i))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let line = filter.geometry(map);
|
||||
batch
|
||||
.unzoomed
|
||||
.push(Color::RED, line.make_polygons(Distance::meters(3.0)));
|
||||
|
||||
draw_zoomed_planters(
|
||||
ctx,
|
||||
&mut batch.zoomed,
|
||||
line.percent_slice(0.3, 0.7).unwrap_or(line),
|
||||
);
|
||||
}
|
||||
batch.build(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// A diagonal filter exists in an intersection. It's defined by two roads (the order is
|
||||
/// arbitrary). When all of the intersection's roads are sorted in clockwise order, this pair of
|
||||
/// roads splits the ordering into two groups. Turns in each group are still possible, but not
|
||||
/// across groups.
|
||||
///
|
||||
/// TODO Be careful with PartialEq! At a 4-way intersection, the same filter can be expressed as a
|
||||
/// different pair of two roads. And the (r1, r2) ordering is also arbitrary.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct DiagonalFilter {
|
||||
r1: RoadID,
|
||||
r2: RoadID,
|
||||
i: IntersectionID,
|
||||
|
||||
group1: BTreeSet<RoadID>,
|
||||
group2: BTreeSet<RoadID>,
|
||||
}
|
||||
|
||||
/// A partitioning of the interior of a neighborhood based on driving connectivity
|
||||
pub struct Cell {
|
||||
/// Most roads are fully in one cell. Roads with modal filters on them are sometimes split
|
||||
@ -248,26 +141,6 @@ impl Neighborhood {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw two planters on each end of a line. They'll be offset so that they don't exceed the
|
||||
// endpoints.
|
||||
fn draw_zoomed_planters(ctx: &EventCtx, batch: &mut GeomBatch, line: Line) {
|
||||
let planter = GeomBatch::load_svg(ctx, "system/assets/map/planter.svg");
|
||||
let planter_width = planter.get_dims().width;
|
||||
let scaled_planter = planter.scale(0.3 * line.length().inner_meters() / planter_width);
|
||||
|
||||
batch.append(
|
||||
scaled_planter
|
||||
.clone()
|
||||
.centered_on(line.must_dist_along(0.15 * line.length()))
|
||||
.rotate(line.angle()),
|
||||
);
|
||||
batch.append(
|
||||
scaled_planter
|
||||
.centered_on(line.must_dist_along(0.85 * line.length()))
|
||||
.rotate(line.angle()),
|
||||
);
|
||||
}
|
||||
|
||||
// Find all of the disconnected "cells" of reachable areas, bounded by a perimeter. This is with
|
||||
// respect to driving.
|
||||
fn find_cells(
|
||||
@ -449,86 +322,3 @@ fn floodfill(
|
||||
car_free: false,
|
||||
}
|
||||
}
|
||||
|
||||
impl DiagonalFilter {
|
||||
/// Find all possible diagonal filters at an intersection
|
||||
fn filters_for(app: &App, i: IntersectionID) -> Vec<DiagonalFilter> {
|
||||
let map = &app.primary.map;
|
||||
let roads = map.get_i(i).get_roads_sorted_by_incoming_angle(map);
|
||||
// TODO Handle >4-ways
|
||||
if roads.len() != 4 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
vec![
|
||||
DiagonalFilter::new(map, i, roads[0], roads[1]),
|
||||
DiagonalFilter::new(map, i, roads[1], roads[2]),
|
||||
]
|
||||
}
|
||||
|
||||
fn new(map: &Map, i: IntersectionID, r1: RoadID, r2: RoadID) -> DiagonalFilter {
|
||||
let mut roads = map.get_i(i).get_roads_sorted_by_incoming_angle(map);
|
||||
// Make self.r1 be the first entry
|
||||
while roads[0] != r1 {
|
||||
roads.rotate_right(1);
|
||||
}
|
||||
|
||||
let mut group1 = BTreeSet::new();
|
||||
group1.insert(roads.remove(0));
|
||||
loop {
|
||||
let next = roads.remove(0);
|
||||
group1.insert(next);
|
||||
if next == r2 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// This is only true for 4-ways...
|
||||
assert_eq!(group1.len(), 2);
|
||||
assert_eq!(roads.len(), 2);
|
||||
|
||||
DiagonalFilter {
|
||||
r1,
|
||||
r2,
|
||||
i,
|
||||
group1,
|
||||
group2: roads.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Physically where is the filter placed?
|
||||
fn geometry(&self, map: &Map) -> Line {
|
||||
let r1 = map.get_r(self.r1);
|
||||
let r2 = map.get_r(self.r2);
|
||||
|
||||
// Orient the road to face the intersection
|
||||
let mut pl1 = r1.center_pts.clone();
|
||||
if r1.src_i == self.i {
|
||||
pl1 = pl1.reversed();
|
||||
}
|
||||
let mut pl2 = r2.center_pts.clone();
|
||||
if r2.src_i == self.i {
|
||||
pl2 = pl2.reversed();
|
||||
}
|
||||
|
||||
// The other combinations of left/right here would produce points or a line across just one
|
||||
// road
|
||||
let pt1 = pl1.must_shift_right(r1.get_half_width()).last_pt();
|
||||
let pt2 = pl2.must_shift_left(r2.get_half_width()).last_pt();
|
||||
Line::must_new(pt1, pt2)
|
||||
}
|
||||
|
||||
fn allows_turn(&self, from: RoadID, to: RoadID) -> bool {
|
||||
self.group1.contains(&from) == self.group1.contains(&to)
|
||||
}
|
||||
|
||||
fn avoid_movements_between_roads(&self) -> Vec<(RoadID, RoadID)> {
|
||||
let mut pairs = Vec::new();
|
||||
for from in &self.group1 {
|
||||
for to in &self.group2 {
|
||||
pairs.push((*from, *to));
|
||||
pairs.push((*to, *from));
|
||||
}
|
||||
}
|
||||
pairs
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user