Organize modal filter code before big changes

This commit is contained in:
Dustin Carlino 2022-01-12 13:55:50 +00:00
parent 6826145de2
commit e0b18d6d07
2 changed files with 227 additions and 215 deletions

222
game/src/ltn/filters.rs Normal file
View 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()),
);
}

View File

@ -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
}
}