mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 09:24:26 +03:00
Only make crosswalks in one direction.
Don't create duplicate crosswalks in edit_movement Remove other_crosswalk_ids and switch to make_walking_turns_v2 Allow make_shared_sidewalk_corner and make_crosswalks to work in either direction with a point order check Don't skip rendering some corners since they're no longer duplicated Draw sidewalk corners the same regardless of lane direction with a point order check Only make one crosswalk at dead ends and degenerate intersections Make footways only get sharedsidewalkcorner turns Don't panic on bad sharedsidewalkcorner geometry
This commit is contained in:
parent
65f086ecb3
commit
9369ac229f
@ -55,10 +55,7 @@ impl DrawIntersection {
|
||||
}
|
||||
|
||||
for turn in &i.turns {
|
||||
// Avoid double-rendering
|
||||
if turn.turn_type.pedestrian_crossing()
|
||||
&& !turn.other_crosswalk_ids.iter().any(|id| *id < turn.id)
|
||||
{
|
||||
if turn.turn_type.pedestrian_crossing() {
|
||||
make_crosswalk(&mut default_geom, turn, map, app.cs());
|
||||
}
|
||||
}
|
||||
@ -265,10 +262,6 @@ pub fn calculate_corners(i: &Intersection, map: &Map) -> Vec<Polygon> {
|
||||
|
||||
for turn in &i.turns {
|
||||
if turn.turn_type == TurnType::SharedSidewalkCorner {
|
||||
// Avoid double-rendering
|
||||
if map.get_l(turn.id.src).dst_i != i.id {
|
||||
continue;
|
||||
}
|
||||
let l1 = map.get_l(turn.id.src);
|
||||
let l2 = map.get_l(turn.id.dst);
|
||||
|
||||
@ -278,23 +271,36 @@ pub fn calculate_corners(i: &Intersection, map: &Map) -> Vec<Polygon> {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is point2 counter-clockwise of point1?
|
||||
let dir = if i
|
||||
.polygon
|
||||
.center()
|
||||
.angle_to(turn.geom.first_pt())
|
||||
.simple_shortest_rotation_towards(i.polygon.center().angle_to(turn.geom.last_pt()))
|
||||
> 0.0
|
||||
{
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
|
||||
if l1.width == l2.width {
|
||||
// When two sidewalks or two shoulders meet, use the turn geometry to create some
|
||||
// nice rounding.
|
||||
let width = l1.width;
|
||||
let shift = dir * l1.width / 2.0;
|
||||
if let Some(poly) = (|| {
|
||||
let mut pts = turn.geom.shift_left(width / 2.0).ok()?.into_points();
|
||||
pts.push(l2.first_line().shift_left(width / 2.0).pt1());
|
||||
pts.push(l2.first_line().shift_right(width / 2.0).pt1());
|
||||
let mut pts = turn.geom.shift_either_direction(-shift).ok()?.into_points();
|
||||
pts.push(l2.end_line(i.id).shift_either_direction(shift).pt2());
|
||||
pts.push(l2.end_line(i.id).shift_either_direction(-shift).pt2());
|
||||
pts.extend(
|
||||
turn.geom
|
||||
.shift_right(width / 2.0)
|
||||
.shift_either_direction(shift)
|
||||
.ok()?
|
||||
.reversed()
|
||||
.into_points(),
|
||||
);
|
||||
pts.push(l1.last_line().shift_right(width / 2.0).pt2());
|
||||
pts.push(l1.last_line().shift_left(width / 2.0).pt2());
|
||||
pts.push(l1.end_line(i.id).shift_either_direction(shift).pt2());
|
||||
pts.push(l1.end_line(i.id).shift_either_direction(-shift).pt2());
|
||||
pts.push(pts[0]);
|
||||
// Many resulting shapes aren't valid rings, but we can still triangulate them.
|
||||
Some(Polygon::buggy_new(pts))
|
||||
@ -304,10 +310,18 @@ pub fn calculate_corners(i: &Intersection, map: &Map) -> Vec<Polygon> {
|
||||
} else {
|
||||
// When a sidewalk and a shoulder meet, use a simpler shape to connect them.
|
||||
let mut pts = vec![
|
||||
l2.first_line().shift_left(l2.width / 2.0).pt1(),
|
||||
l2.first_line().shift_right(l2.width / 2.0).pt1(),
|
||||
l1.last_line().shift_right(l1.width / 2.0).pt2(),
|
||||
l1.last_line().shift_left(l1.width / 2.0).pt2(),
|
||||
l2.end_line(i.id)
|
||||
.shift_either_direction(dir * l2.width / 2.0)
|
||||
.pt2(),
|
||||
l2.end_line(i.id)
|
||||
.shift_either_direction(-dir * l2.width / 2.0)
|
||||
.pt2(),
|
||||
l1.end_line(i.id)
|
||||
.shift_either_direction(-dir * l1.width / 2.0)
|
||||
.pt2(),
|
||||
l1.end_line(i.id)
|
||||
.shift_either_direction(dir * l1.width / 2.0)
|
||||
.pt2(),
|
||||
];
|
||||
pts.push(pts[0]);
|
||||
if let Ok(ring) = Ring::new(pts) {
|
||||
@ -332,20 +346,26 @@ fn calculate_corner_curbs(i: &Intersection, map: &Map) -> Vec<Polygon> {
|
||||
|
||||
for turn in &i.turns {
|
||||
if turn.turn_type == TurnType::SharedSidewalkCorner {
|
||||
// Avoid double-rendering
|
||||
if map.get_l(turn.id.src).dst_i != i.id {
|
||||
continue;
|
||||
}
|
||||
let dir = if turn
|
||||
.geom
|
||||
.first_pt()
|
||||
.angle_to(i.polygon.center())
|
||||
.simple_shortest_rotation_towards(
|
||||
turn.geom.first_pt().angle_to(turn.geom.last_pt()),
|
||||
)
|
||||
> 0.0
|
||||
{
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
let l1 = map.get_l(turn.id.src);
|
||||
let l2 = map.get_l(turn.id.dst);
|
||||
|
||||
if l1.width == l2.width {
|
||||
// When two sidewalks or two shoulders meet, use the turn geometry to create some
|
||||
// nice rounding.
|
||||
let mut width = shift(l1.width);
|
||||
if map.get_config().driving_side == DrivingSide::Right {
|
||||
width *= -1.0;
|
||||
}
|
||||
let width = dir * shift(l1.width);
|
||||
|
||||
if let Some(pl) = (|| {
|
||||
let mut pts = turn.geom.shift_either_direction(width).ok()?.into_points();
|
||||
@ -354,15 +374,18 @@ fn calculate_corner_curbs(i: &Intersection, map: &Map) -> Vec<Polygon> {
|
||||
// this causes "zig-zaggy" artifacts. The approx_eq check helps some (but not
|
||||
// all) of those cases, but sometimes introduces visual "gaps". This still
|
||||
// needs more work.
|
||||
let first_line = l2.first_line().shift_either_direction(width);
|
||||
if !pts.last().unwrap().approx_eq(first_line.pt1(), thickness) {
|
||||
pts.push(first_line.pt1());
|
||||
pts.push(first_line.unbounded_dist_along(thickness));
|
||||
let first_line = l2.end_line(i.id).shift_either_direction(-width);
|
||||
if !pts.last().unwrap().approx_eq(first_line.pt2(), thickness) {
|
||||
pts.push(first_line.pt2());
|
||||
pts.push(first_line.unbounded_dist_along(first_line.length() - thickness));
|
||||
}
|
||||
let last_line = l1.last_line().shift_either_direction(width).reversed();
|
||||
if !pts[0].approx_eq(last_line.pt1(), thickness) {
|
||||
pts.insert(0, last_line.pt1());
|
||||
pts.insert(0, last_line.unbounded_dist_along(thickness));
|
||||
let last_line = l1.end_line(i.id).shift_either_direction(width);
|
||||
if !pts[0].approx_eq(last_line.pt2(), thickness) {
|
||||
pts.insert(0, last_line.pt2());
|
||||
pts.insert(
|
||||
0,
|
||||
last_line.unbounded_dist_along(last_line.length() - thickness),
|
||||
);
|
||||
}
|
||||
PolyLine::deduping_new(pts).ok()
|
||||
})() {
|
||||
@ -370,22 +393,17 @@ fn calculate_corner_curbs(i: &Intersection, map: &Map) -> Vec<Polygon> {
|
||||
}
|
||||
} else {
|
||||
// When a sidewalk and a shoulder meet, use a simpler shape to connect them.
|
||||
let direction = if map.get_config().driving_side == DrivingSide::Right {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let last_line = l1
|
||||
.last_line()
|
||||
.shift_either_direction(direction * shift(l1.width));
|
||||
let first_line = l2
|
||||
.first_line()
|
||||
.shift_either_direction(direction * shift(l2.width));
|
||||
let l1_line = l1
|
||||
.end_line(i.id)
|
||||
.shift_either_direction(dir * shift(l1.width));
|
||||
let l2_line = l2
|
||||
.end_line(i.id)
|
||||
.shift_either_direction(-dir * shift(l2.width));
|
||||
if let Ok(pl) = PolyLine::deduping_new(vec![
|
||||
last_line.reversed().unbounded_dist_along(thickness),
|
||||
last_line.pt2(),
|
||||
first_line.pt1(),
|
||||
first_line.unbounded_dist_along(thickness),
|
||||
l1_line.unbounded_dist_along(l1_line.length() - thickness),
|
||||
l1_line.pt2(),
|
||||
l2_line.pt2(),
|
||||
l2_line.unbounded_dist_along(l2_line.length() - thickness),
|
||||
]) {
|
||||
curbs.push(pl.make_polygons(thickness));
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
|
||||
use anyhow::Result;
|
||||
use nbez::{Bez3o, BezCurve, Point2d};
|
||||
@ -219,7 +219,6 @@ fn make_vehicle_turns(i: &Intersection, map: &Map) -> Vec<Turn> {
|
||||
dst: dst.id,
|
||||
},
|
||||
turn_type,
|
||||
other_crosswalk_ids: BTreeSet::new(),
|
||||
geom,
|
||||
});
|
||||
}
|
||||
|
@ -1,172 +1,126 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use abstutil::wraparound_get;
|
||||
use geom::{Distance, Line, PolyLine, Pt2D, Ring};
|
||||
use geom::{Distance, PolyLine, Pt2D, Ring};
|
||||
|
||||
use crate::{
|
||||
Direction, DrivingSide, Intersection, IntersectionID, Lane, LaneID, LaneType, Map, Road, Turn,
|
||||
TurnID, TurnType,
|
||||
Direction, DrivingSide, Intersection, IntersectionID, Lane, LaneID, Map, Turn, TurnID, TurnType,
|
||||
};
|
||||
|
||||
/// Generate Crosswalk and SharedSidewalkCorner (places where two sidewalks directly meet) turns.
|
||||
/// UnmarkedCrossings are not generated here; another process later "downgrades" crosswalks to
|
||||
/// unmarked.
|
||||
/// A complete rewrite of make_walking_turns, which looks at all sidewalks (or lack thereof) in
|
||||
/// counter-clockwise order around an intersection. Based on adjacency, create a
|
||||
/// SharedSidewalkCorner or a Crosswalk.
|
||||
pub fn make_walking_turns(map: &Map, i: &Intersection) -> Vec<Turn> {
|
||||
if i.merged {
|
||||
return make_walking_turns_v2(map, i);
|
||||
}
|
||||
|
||||
if i.is_footway(map) {
|
||||
return make_footway_turns(map, i);
|
||||
}
|
||||
|
||||
let driving_side = map.config.driving_side;
|
||||
|
||||
let roads: Vec<&Road> = i
|
||||
.get_roads_sorted_by_incoming_angle(map)
|
||||
.into_iter()
|
||||
.map(|id| map.get_r(id))
|
||||
.collect();
|
||||
let mut result: Vec<Turn> = Vec::new();
|
||||
// Consider all roads in counter-clockwise order. Every road has up to two sidewalks. Gather
|
||||
// those in order, remembering what roads don't have them.
|
||||
let mut lanes: Vec<Option<&Lane>> = Vec::new();
|
||||
let mut sorted_roads = i.get_roads_sorted_by_incoming_angle(map);
|
||||
// And for left-handed driving, we need to walk around in the opposite order.
|
||||
if driving_side == DrivingSide::Left {
|
||||
sorted_roads.reverse();
|
||||
}
|
||||
|
||||
// I'm a bit confused when to do -1 and +1 honestly, but this works in practice. Angle sorting
|
||||
// may be a little backwards.
|
||||
let idx_offset = if driving_side == DrivingSide::Right {
|
||||
-1
|
||||
for r in sorted_roads {
|
||||
let road = map.get_r(r);
|
||||
let mut fwd = None;
|
||||
let mut back = None;
|
||||
for l in &road.lanes {
|
||||
if l.lane_type.is_walkable() {
|
||||
if l.dir == Direction::Fwd {
|
||||
fwd = Some(l);
|
||||
} else {
|
||||
1
|
||||
back = Some(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (in_lane, out_lane) = if road.src_i == i.id {
|
||||
(back, fwd)
|
||||
} else {
|
||||
(fwd, back)
|
||||
};
|
||||
|
||||
if i.is_degenerate() {
|
||||
if let Some(turns) = make_degenerate_crosswalks(map, i.id, roads[0], roads[1]) {
|
||||
result.extend(turns);
|
||||
// Don't add None entries for footways even if they only have one lane
|
||||
if map.get_r(r).is_footway() {
|
||||
if in_lane.is_some() {
|
||||
lanes.push(in_lane);
|
||||
}
|
||||
// TODO Argh, duplicate logic for SharedSidewalkCorners
|
||||
for idx1 in 0..roads.len() {
|
||||
if let Some(l1) = get_sidewalk(map, roads[idx1].incoming_lanes(i.id)) {
|
||||
if let Some(l2) = get_sidewalk(
|
||||
map,
|
||||
wraparound_get(&roads, (idx1 as isize) + idx_offset).outgoing_lanes(i.id),
|
||||
) {
|
||||
if l1.last_pt() != l2.first_pt() {
|
||||
let geom = make_shared_sidewalk_corner(driving_side, i, l1, l2);
|
||||
result.push(Turn {
|
||||
id: turn_id(i.id, l1.id, l2.id),
|
||||
turn_type: TurnType::SharedSidewalkCorner,
|
||||
other_crosswalk_ids: BTreeSet::new(),
|
||||
geom: geom.clone(),
|
||||
});
|
||||
result.push(Turn {
|
||||
id: turn_id(i.id, l2.id, l1.id),
|
||||
turn_type: TurnType::SharedSidewalkCorner,
|
||||
other_crosswalk_ids: BTreeSet::new(),
|
||||
geom: geom.reversed(),
|
||||
});
|
||||
if out_lane.is_some() {
|
||||
lanes.push(out_lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if roads.len() == 1 {
|
||||
if let Some(l1) = get_sidewalk(map, roads[0].incoming_lanes(i.id)) {
|
||||
if let Some(l2) = get_sidewalk(map, roads[0].outgoing_lanes(i.id)) {
|
||||
let geom = make_shared_sidewalk_corner(driving_side, i, l1, l2);
|
||||
result.push(Turn {
|
||||
id: turn_id(i.id, l1.id, l2.id),
|
||||
turn_type: TurnType::SharedSidewalkCorner,
|
||||
other_crosswalk_ids: BTreeSet::new(),
|
||||
geom: geom.clone(),
|
||||
});
|
||||
result.push(Turn {
|
||||
id: turn_id(i.id, l2.id, l1.id),
|
||||
turn_type: TurnType::SharedSidewalkCorner,
|
||||
other_crosswalk_ids: BTreeSet::new(),
|
||||
geom: geom.reversed(),
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
for idx1 in 0..roads.len() {
|
||||
if let Some(l1) = get_sidewalk(map, roads[idx1].incoming_lanes(i.id)) {
|
||||
// Make the crosswalk to the other side
|
||||
if let Some(l2) = get_sidewalk(map, roads[idx1].outgoing_lanes(i.id)) {
|
||||
result.extend(
|
||||
make_crosswalks(i.id, l1, l2, driving_side)
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
);
|
||||
}
|
||||
|
||||
// Find the shared corner
|
||||
if let Some(l2) = get_sidewalk(
|
||||
map,
|
||||
wraparound_get(&roads, (idx1 as isize) + idx_offset).outgoing_lanes(i.id),
|
||||
) {
|
||||
if l1.last_pt() != l2.first_pt() {
|
||||
let geom = make_shared_sidewalk_corner(driving_side, i, l1, l2);
|
||||
result.push(Turn {
|
||||
id: turn_id(i.id, l1.id, l2.id),
|
||||
turn_type: TurnType::SharedSidewalkCorner,
|
||||
other_crosswalk_ids: BTreeSet::new(),
|
||||
geom: geom.clone(),
|
||||
});
|
||||
result.push(Turn {
|
||||
id: turn_id(i.id, l2.id, l1.id),
|
||||
turn_type: TurnType::SharedSidewalkCorner,
|
||||
other_crosswalk_ids: BTreeSet::new(),
|
||||
geom: geom.reversed(),
|
||||
});
|
||||
}
|
||||
} else if let Some(l2) = get_sidewalk(
|
||||
map,
|
||||
wraparound_get(&roads, (idx1 as isize) + idx_offset).incoming_lanes(i.id),
|
||||
) {
|
||||
// Adjacent road is missing a sidewalk on the near side, but has one on the far
|
||||
// side
|
||||
result.extend(
|
||||
make_crosswalks(i.id, l1, l2, driving_side)
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
);
|
||||
} else {
|
||||
// We may need to add a crosswalk over this intermediate road that has no
|
||||
// sidewalks at all. There might be a few in the way -- think highway onramps.
|
||||
// TODO Refactor and loop until we find something to connect it to?
|
||||
if let Some(l2) = get_sidewalk(
|
||||
map,
|
||||
wraparound_get(&roads, (idx1 as isize) + 2 * idx_offset).outgoing_lanes(i.id),
|
||||
) {
|
||||
result.extend(
|
||||
make_crosswalks(i.id, l1, l2, driving_side)
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
);
|
||||
} else if let Some(l2) = get_sidewalk(
|
||||
map,
|
||||
wraparound_get(&roads, (idx1 as isize) + 2 * idx_offset).incoming_lanes(i.id),
|
||||
) {
|
||||
result.extend(
|
||||
make_crosswalks(i.id, l1, l2, driving_side)
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
);
|
||||
} else if roads.len() > 3 {
|
||||
if let Some(l2) = get_sidewalk(
|
||||
map,
|
||||
wraparound_get(&roads, (idx1 as isize) + 3 * idx_offset)
|
||||
.outgoing_lanes(i.id),
|
||||
) {
|
||||
result.extend(
|
||||
make_crosswalks(i.id, l1, l2, driving_side)
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
);
|
||||
lanes.push(in_lane);
|
||||
lanes.push(out_lane);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are 0 or 1 sidewalks there are no turns to be made
|
||||
if lanes.iter().filter(|l| l.is_some()).count() <= 1 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// Make sure we start with a sidewalk.
|
||||
while lanes[0].is_none() {
|
||||
lanes.rotate_left(1);
|
||||
}
|
||||
let mut result: Vec<Turn> = Vec::new();
|
||||
|
||||
let mut from: Option<&Lane> = lanes[0];
|
||||
let first_from = from.unwrap().id;
|
||||
let mut adj = true;
|
||||
for l in lanes.iter().skip(1).chain(lanes.iter()) {
|
||||
if i.id.0 == 284 {
|
||||
debug!(
|
||||
"looking at {:?}. from is {:?}, first_from is {}, adj is {}",
|
||||
l.map(|l| l.id),
|
||||
from.map(|l| l.id),
|
||||
first_from,
|
||||
adj
|
||||
);
|
||||
}
|
||||
|
||||
if from.is_none() {
|
||||
from = *l;
|
||||
adj = true;
|
||||
continue;
|
||||
}
|
||||
let l1 = from.unwrap();
|
||||
|
||||
if l.is_none() {
|
||||
adj = false;
|
||||
continue;
|
||||
}
|
||||
let l2 = l.unwrap();
|
||||
|
||||
if adj && l1.id.road != l2.id.road {
|
||||
result.push(Turn {
|
||||
id: turn_id(i.id, l1.id, l2.id),
|
||||
turn_type: TurnType::SharedSidewalkCorner,
|
||||
geom: make_shared_sidewalk_corner(i, l1, l2),
|
||||
});
|
||||
|
||||
from = Some(l2);
|
||||
// adj stays true
|
||||
} else {
|
||||
// Only make one crosswalk for degenerate intersections
|
||||
if !(i.is_degenerate() || i.is_deadend())
|
||||
|| !result.iter().any(|t| t.turn_type == TurnType::Crosswalk)
|
||||
{
|
||||
result.push(Turn {
|
||||
id: turn_id(i.id, l1.id, l2.id),
|
||||
turn_type: TurnType::Crosswalk,
|
||||
geom: make_crosswalk(i, l1, l2),
|
||||
});
|
||||
}
|
||||
from = Some(l2);
|
||||
adj = true;
|
||||
}
|
||||
|
||||
// Have we made it all the way around?
|
||||
if first_from == from.unwrap().id {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,285 +169,62 @@ pub fn filter_turns(mut input: Vec<Turn>, map: &Map, i: &Intersection) -> Vec<Tu
|
||||
input
|
||||
}
|
||||
|
||||
/// A complete rewrite of make_walking_turns, which looks at all sidewalks (or lack thereof) in
|
||||
/// counter-clockwise order around an intersection. Based on adjacency, create a
|
||||
/// SharedSidewalkCorner or a Crosswalk.
|
||||
///
|
||||
/// TODO This is only used for consolidated intersections right now. Cut over to this completely
|
||||
/// after fixing problems like:
|
||||
/// - too many crosswalks at the Boyer roundabout
|
||||
/// - one centered crosswalk for degenerate intersections
|
||||
fn make_walking_turns_v2(map: &Map, i: &Intersection) -> Vec<Turn> {
|
||||
let driving_side = map.config.driving_side;
|
||||
fn make_crosswalk(i: &Intersection, l1: &Lane, l2: &Lane) -> PolyLine {
|
||||
let l1_line = l1.end_line(i.id);
|
||||
let l2_line = l2.end_line(i.id);
|
||||
|
||||
// Consider all roads in counter-clockwise order. Every road has up to two sidewalks. Gather
|
||||
// those in order, remembering what roads don't have them.
|
||||
let mut lanes: Vec<Option<&Lane>> = Vec::new();
|
||||
let mut num_sidewalks = 0;
|
||||
let mut sorted_roads = i.get_roads_sorted_by_incoming_angle(map);
|
||||
// And for left-handed driving, we need to walk around in the opposite order.
|
||||
if driving_side == DrivingSide::Left {
|
||||
sorted_roads.reverse();
|
||||
}
|
||||
|
||||
for r in sorted_roads {
|
||||
let road = map.get_r(r);
|
||||
let mut fwd = None;
|
||||
let mut back = None;
|
||||
for l in &road.lanes {
|
||||
if l.lane_type.is_walkable() {
|
||||
if l.dir == Direction::Fwd {
|
||||
fwd = Some(l);
|
||||
// Jut out a bit into the intersection, cross over, then jut back in.
|
||||
// Put degenerate intersection crosswalks in the middle (DEGENERATE_HALF_LENGTH).
|
||||
PolyLine::deduping_new(vec![
|
||||
l1_line.pt2(),
|
||||
l1_line.unbounded_dist_along(
|
||||
l1_line.length()
|
||||
+ if i.is_degenerate() {
|
||||
Distance::const_meters(2.5)
|
||||
} else {
|
||||
back = Some(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
if fwd.is_some() {
|
||||
num_sidewalks += 1;
|
||||
}
|
||||
if back.is_some() {
|
||||
num_sidewalks += 1;
|
||||
}
|
||||
let (in_lane, out_lane) = if road.src_i == i.id {
|
||||
(back, fwd)
|
||||
} else {
|
||||
(fwd, back)
|
||||
};
|
||||
lanes.push(in_lane);
|
||||
lanes.push(out_lane);
|
||||
}
|
||||
if num_sidewalks <= 1 {
|
||||
return Vec::new();
|
||||
}
|
||||
// Make sure we start with a sidewalk.
|
||||
while lanes[0].is_none() {
|
||||
lanes.rotate_left(1);
|
||||
}
|
||||
let mut result: Vec<Turn> = Vec::new();
|
||||
|
||||
let mut from: Option<&Lane> = lanes[0];
|
||||
let first_from = from.unwrap().id;
|
||||
let mut adj = true;
|
||||
for l in lanes.iter().skip(1).chain(lanes.iter()) {
|
||||
if i.id.0 == 284 {
|
||||
debug!(
|
||||
"looking at {:?}. from is {:?}, first_from is {}, adj is {}",
|
||||
l.map(|l| l.id),
|
||||
from.map(|l| l.id),
|
||||
first_from,
|
||||
adj
|
||||
);
|
||||
}
|
||||
|
||||
if from.is_none() {
|
||||
from = *l;
|
||||
adj = true;
|
||||
continue;
|
||||
}
|
||||
let l1 = from.unwrap();
|
||||
|
||||
if l.is_none() {
|
||||
adj = false;
|
||||
continue;
|
||||
}
|
||||
let l2 = l.unwrap();
|
||||
|
||||
if adj && l1.id.road != l2.id.road {
|
||||
// Because of the order we go, have to swap l1 and l2 here. l1 is the outgoing, l2 the
|
||||
// incoming.
|
||||
let geom = make_shared_sidewalk_corner(driving_side, i, l2, l1);
|
||||
result.push(Turn {
|
||||
id: turn_id(i.id, l1.id, l2.id),
|
||||
turn_type: TurnType::SharedSidewalkCorner,
|
||||
other_crosswalk_ids: BTreeSet::new(),
|
||||
geom: geom.reversed(),
|
||||
});
|
||||
result.push(Turn {
|
||||
id: turn_id(i.id, l2.id, l1.id),
|
||||
turn_type: TurnType::SharedSidewalkCorner,
|
||||
other_crosswalk_ids: BTreeSet::new(),
|
||||
geom,
|
||||
});
|
||||
|
||||
from = Some(l2);
|
||||
// adj stays true
|
||||
} else {
|
||||
// TODO Just one for degenerate intersections
|
||||
result.extend(
|
||||
make_crosswalks(i.id, l1, l2, driving_side)
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
);
|
||||
from = Some(l2);
|
||||
adj = true;
|
||||
}
|
||||
|
||||
// Have we made it all the way around?
|
||||
if first_from == from.unwrap().id {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// At an intersection of footpaths only, just generate a turn between every pair of lanes.
|
||||
fn make_footway_turns(map: &Map, i: &Intersection) -> Vec<Turn> {
|
||||
let lanes = i
|
||||
.incoming_lanes
|
||||
.iter()
|
||||
.chain(&i.outgoing_lanes)
|
||||
.filter_map(|l| {
|
||||
let l = map.get_l(*l);
|
||||
if l.is_walkable() {
|
||||
Some(l)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<&Lane>>();
|
||||
let mut results = Vec::new();
|
||||
for l1 in &lanes {
|
||||
for l2 in &lanes {
|
||||
if l1.id == l2.id {
|
||||
continue;
|
||||
}
|
||||
let maybe_geom = PolyLine::new(vec![l1.endpoint(i.id), l2.endpoint(i.id)]);
|
||||
let geom = maybe_geom.unwrap_or_else(|_| {
|
||||
// TODO Gross! After improving intersection geometry where these cases are
|
||||
// happening, if this still happens, maybe it's time to make turn geometry be
|
||||
// optional.
|
||||
PolyLine::must_new(vec![l1.endpoint(i.id), l1.endpoint(i.id).offset(0.1, 0.1)])
|
||||
});
|
||||
results.push(Turn {
|
||||
id: turn_id(i.id, l1.id, l2.id),
|
||||
turn_type: TurnType::SharedSidewalkCorner,
|
||||
other_crosswalk_ids: BTreeSet::new(),
|
||||
geom,
|
||||
});
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
fn make_crosswalks(
|
||||
i: IntersectionID,
|
||||
l1: &Lane,
|
||||
l2: &Lane,
|
||||
driving_side: DrivingSide,
|
||||
) -> Option<Vec<Turn>> {
|
||||
let l1_pt = l1.endpoint(i);
|
||||
let l2_pt = l2.endpoint(i);
|
||||
// This is one of those uncomfortably "trial-and-error" kind of things.
|
||||
let mut direction = if (l1.dst_i == i) == (l2.dst_i == i) {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
if driving_side == DrivingSide::Left {
|
||||
direction *= -1.0;
|
||||
}
|
||||
|
||||
// Jut out a bit into the intersection, cross over, then jut back in. Assumes sidewalks are the
|
||||
// same width.
|
||||
let line = Line::new(l1_pt, l2_pt)?.shift_either_direction(direction * l1.width / 2.0);
|
||||
let geom_fwds = PolyLine::deduping_new(vec![l1_pt, line.pt1(), line.pt2(), l2_pt]).ok()?;
|
||||
|
||||
Some(vec![
|
||||
Turn {
|
||||
id: turn_id(i, l1.id, l2.id),
|
||||
turn_type: TurnType::Crosswalk,
|
||||
other_crosswalk_ids: vec![turn_id(i, l2.id, l1.id)].into_iter().collect(),
|
||||
geom: geom_fwds.clone(),
|
||||
l1.width / 2.0
|
||||
},
|
||||
Turn {
|
||||
id: turn_id(i, l2.id, l1.id),
|
||||
turn_type: TurnType::Crosswalk,
|
||||
other_crosswalk_ids: vec![turn_id(i, l1.id, l2.id)].into_iter().collect(),
|
||||
geom: geom_fwds.reversed(),
|
||||
),
|
||||
l2_line.unbounded_dist_along(
|
||||
l2_line.length()
|
||||
+ if i.is_degenerate() {
|
||||
Distance::const_meters(2.5)
|
||||
} else {
|
||||
l2.width / 2.0
|
||||
},
|
||||
),
|
||||
l2_line.pt2(),
|
||||
])
|
||||
}
|
||||
|
||||
// Only one physical crosswalk for degenerate intersections, right in the middle.
|
||||
fn make_degenerate_crosswalks(
|
||||
map: &Map,
|
||||
i: IntersectionID,
|
||||
r1: &Road,
|
||||
r2: &Road,
|
||||
) -> Option<Vec<Turn>> {
|
||||
let l1_in = get_sidewalk(map, r1.incoming_lanes(i))?;
|
||||
let l1_out = get_sidewalk(map, r1.outgoing_lanes(i))?;
|
||||
let l2_in = get_sidewalk(map, r2.incoming_lanes(i))?;
|
||||
let l2_out = get_sidewalk(map, r2.outgoing_lanes(i))?;
|
||||
|
||||
let pt1 = Line::new(l1_in.last_pt(), l2_out.first_pt())?.percent_along(0.5)?;
|
||||
let pt2 = Line::new(l1_out.first_pt(), l2_in.last_pt())?.percent_along(0.5)?;
|
||||
|
||||
if pt1 == pt2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut all_ids = BTreeSet::new();
|
||||
all_ids.insert(turn_id(i, l1_in.id, l1_out.id));
|
||||
all_ids.insert(turn_id(i, l1_out.id, l1_in.id));
|
||||
all_ids.insert(turn_id(i, l2_in.id, l2_out.id));
|
||||
all_ids.insert(turn_id(i, l2_out.id, l2_in.id));
|
||||
|
||||
Some(
|
||||
vec![
|
||||
Turn {
|
||||
id: turn_id(i, l1_in.id, l1_out.id),
|
||||
turn_type: TurnType::Crosswalk,
|
||||
other_crosswalk_ids: all_ids.clone(),
|
||||
geom: PolyLine::deduping_new(vec![l1_in.last_pt(), pt1, pt2, l1_out.first_pt()])
|
||||
.ok()?,
|
||||
},
|
||||
Turn {
|
||||
id: turn_id(i, l1_out.id, l1_in.id),
|
||||
turn_type: TurnType::Crosswalk,
|
||||
other_crosswalk_ids: all_ids.clone(),
|
||||
geom: PolyLine::deduping_new(vec![l1_out.first_pt(), pt2, pt1, l1_in.last_pt()])
|
||||
.ok()?,
|
||||
},
|
||||
Turn {
|
||||
id: turn_id(i, l2_in.id, l2_out.id),
|
||||
turn_type: TurnType::Crosswalk,
|
||||
other_crosswalk_ids: all_ids.clone(),
|
||||
geom: PolyLine::deduping_new(vec![l2_in.last_pt(), pt2, pt1, l2_out.first_pt()])
|
||||
.ok()?,
|
||||
},
|
||||
Turn {
|
||||
id: turn_id(i, l2_out.id, l2_in.id),
|
||||
turn_type: TurnType::Crosswalk,
|
||||
other_crosswalk_ids: all_ids,
|
||||
geom: PolyLine::deduping_new(vec![l2_out.first_pt(), pt1, pt2, l2_in.last_pt()])
|
||||
.ok()?,
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.map(|mut t| {
|
||||
t.other_crosswalk_ids.remove(&t.id);
|
||||
t
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.unwrap_or_else(|_| PolyLine::unchecked_new(vec![l1.endpoint(i.id), l2.endpoint(i.id)]))
|
||||
}
|
||||
|
||||
// TODO This doesn't handle sidewalk/shoulder transitions
|
||||
fn make_shared_sidewalk_corner(
|
||||
driving_side: DrivingSide,
|
||||
i: &Intersection,
|
||||
l1: &Lane,
|
||||
l2: &Lane,
|
||||
) -> PolyLine {
|
||||
let baseline = PolyLine::must_new(vec![l1.last_pt(), l2.first_pt()]);
|
||||
fn make_shared_sidewalk_corner(i: &Intersection, l1: &Lane, l2: &Lane) -> PolyLine {
|
||||
// This may produce a polyline with two identical points. Nothing better to do here.
|
||||
let baseline = PolyLine::unchecked_new(vec![l1.endpoint(i.id), l2.endpoint(i.id)]);
|
||||
|
||||
// Is point2 counter-clockwise of point1?
|
||||
let dir = if i
|
||||
.polygon
|
||||
.center()
|
||||
.angle_to(l1.endpoint(i.id))
|
||||
.simple_shortest_rotation_towards(i.polygon.center().angle_to(l2.endpoint(i.id)))
|
||||
> 0.0
|
||||
{
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
// Find all of the points on the intersection polygon between the two sidewalks. Assumes
|
||||
// sidewalks are the same length.
|
||||
let corner1 = l1.last_line().shift_right(l1.width / 2.0).pt2();
|
||||
let corner2 = l2.first_line().shift_right(l2.width / 2.0).pt1();
|
||||
let corner1 = l1
|
||||
.end_line(i.id)
|
||||
.shift_either_direction(dir * l1.width / 2.0)
|
||||
.pt2();
|
||||
let corner2 = l2
|
||||
.end_line(i.id)
|
||||
.shift_either_direction(-dir * l2.width / 2.0)
|
||||
.pt2();
|
||||
|
||||
// TODO Something like this will be MUCH simpler and avoid going around the long way sometimes.
|
||||
if false {
|
||||
@ -504,12 +235,15 @@ fn make_shared_sidewalk_corner(
|
||||
|
||||
// The order of the points here seems backwards, but it's because we scan from corner2
|
||||
// to corner1 below.
|
||||
let mut pts_between = vec![l2.first_pt()];
|
||||
|
||||
let mut pts_between = vec![l2.endpoint(i.id)];
|
||||
// Intersection polygons are constructed in clockwise order, so do corner2 to corner1.
|
||||
let mut i_pts = i.polygon.points().clone();
|
||||
if driving_side == DrivingSide::Left {
|
||||
|
||||
if dir < 0.0 {
|
||||
i_pts.reverse();
|
||||
}
|
||||
|
||||
if let Some(pts) = Pt2D::find_pts_between(&i_pts, corner2, corner1, Distance::meters(0.5)) {
|
||||
let mut deduped = pts;
|
||||
deduped.dedup();
|
||||
@ -528,7 +262,9 @@ fn make_shared_sidewalk_corner(
|
||||
return baseline;
|
||||
}
|
||||
|
||||
if let Ok(pl) = PolyLine::must_new(deduped).shift_right(l1.width.min(l2.width) / 2.0) {
|
||||
if let Ok(pl) = PolyLine::must_new(deduped)
|
||||
.shift_either_direction(dir * l1.width.min(l2.width) / 2.0)
|
||||
{
|
||||
pts_between.extend(pl.points());
|
||||
} else {
|
||||
warn!(
|
||||
@ -540,7 +276,7 @@ fn make_shared_sidewalk_corner(
|
||||
}
|
||||
}
|
||||
}
|
||||
pts_between.push(l1.last_pt());
|
||||
pts_between.push(l1.endpoint(i.id));
|
||||
pts_between.reverse();
|
||||
// Pretty big smoothing; I'm observing funky backtracking about 0.5m long.
|
||||
let mut final_pts = Pt2D::approx_dedupe(pts_between.clone(), Distance::meters(1.0));
|
||||
@ -554,16 +290,17 @@ fn make_shared_sidewalk_corner(
|
||||
}
|
||||
// The last point might be removed as a duplicate, but we want the start/end to exactly match
|
||||
// up at least.
|
||||
if *final_pts.last().unwrap() != l2.first_pt() {
|
||||
if *final_pts.last().unwrap() != l2.endpoint(i.id) {
|
||||
final_pts.pop();
|
||||
final_pts.push(l2.first_pt());
|
||||
final_pts.push(l2.endpoint(i.id));
|
||||
}
|
||||
if abstutil::contains_duplicates(
|
||||
&final_pts
|
||||
.iter()
|
||||
.map(|pt| pt.to_hashable())
|
||||
.collect::<Vec<_>>(),
|
||||
) {
|
||||
) || final_pts.len() < 2
|
||||
{
|
||||
warn!(
|
||||
"SharedSidewalkCorner between {} and {} has weird duplicate geometry, so just doing \
|
||||
straight line",
|
||||
@ -588,12 +325,3 @@ fn make_shared_sidewalk_corner(
|
||||
fn turn_id(parent: IntersectionID, src: LaneID, dst: LaneID) -> TurnID {
|
||||
TurnID { parent, src, dst }
|
||||
}
|
||||
|
||||
fn get_sidewalk(map: &Map, children: Vec<(LaneID, LaneType)>) -> Option<&Lane> {
|
||||
for (id, lt) in children {
|
||||
if lt.is_walkable() {
|
||||
return Some(map.get_l(id));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
@ -329,24 +329,15 @@ impl Stage {
|
||||
}
|
||||
|
||||
pub fn edit_movement(&mut self, g: &Movement, pri: TurnPriority) {
|
||||
let mut ids = vec![g.id];
|
||||
if g.turn_type.pedestrian_crossing() {
|
||||
ids.push(MovementID {
|
||||
from: g.id.to,
|
||||
to: g.id.from,
|
||||
parent: g.id.parent,
|
||||
crosswalk: true,
|
||||
});
|
||||
self.enforce_minimum_crosswalk_time(g);
|
||||
}
|
||||
for id in ids {
|
||||
self.protected_movements.remove(&id);
|
||||
self.yield_movements.remove(&id);
|
||||
self.protected_movements.remove(&g.id);
|
||||
self.yield_movements.remove(&g.id);
|
||||
if pri == TurnPriority::Protected {
|
||||
self.protected_movements.insert(id);
|
||||
self.protected_movements.insert(g.id);
|
||||
} else if pri == TurnPriority::Yield {
|
||||
self.yield_movements.insert(id);
|
||||
}
|
||||
self.yield_movements.insert(g.id);
|
||||
}
|
||||
}
|
||||
pub fn enforce_minimum_crosswalk_time(&mut self, movement: &Movement) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -79,9 +79,6 @@ pub struct Turn {
|
||||
// TODO Some turns might not actually have geometry. Currently encoded by two equal points.
|
||||
// Represent more directly?
|
||||
pub geom: PolyLine,
|
||||
/// Empty except for TurnType::Crosswalk and UnmarkedCrossing. Usually just one other ID,
|
||||
/// except for the case of 4 duplicates at a degenerate intersection.
|
||||
pub other_crosswalk_ids: BTreeSet<TurnID>,
|
||||
}
|
||||
|
||||
impl Turn {
|
||||
|
Loading…
Reference in New Issue
Block a user