diff --git a/map_gui/src/render/intersection.rs b/map_gui/src/render/intersection.rs index f395fab7c0..53df133d09 100644 --- a/map_gui/src/render/intersection.rs +++ b/map_gui/src/render/intersection.rs @@ -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 { 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 { 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 { } 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 { 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 { // 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 { } } 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)); } diff --git a/map_model/src/make/turns.rs b/map_model/src/make/turns.rs index a8bc96953d..dd66dc4dce 100644 --- a/map_model/src/make/turns.rs +++ b/map_model/src/make/turns.rs @@ -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 { dst: dst.id, }, turn_type, - other_crosswalk_ids: BTreeSet::new(), geom, }); } diff --git a/map_model/src/make/walking_turns.rs b/map_model/src/make/walking_turns.rs index b5ff2176f3..0d5e6b771f 100644 --- a/map_model/src/make/walking_turns.rs +++ b/map_model/src/make/walking_turns.rs @@ -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 { - 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(); + // 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> = 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(); + } + + 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 { + back = Some(l); + } + } + } + + let (in_lane, out_lane) = if road.src_i == i.id { + (back, fwd) + } else { + (fwd, back) + }; + + // 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); + } + if out_lane.is_some() { + lanes.push(out_lane); + } + } else { + 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 = Vec::new(); - // 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 - } else { - 1 - }; + 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 i.is_degenerate() { - if let Some(turns) = make_degenerate_crosswalks(map, i.id, roads[0], roads[1]) { - result.extend(turns); + if from.is_none() { + from = *l; + adj = true; + continue; } - // 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(), - }); - } - } - } + let l1 = from.unwrap(); + + if l.is_none() { + adj = false; + continue; } - 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); + 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::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(), + turn_type: TurnType::Crosswalk, + geom: make_crosswalk(i, l1, l2), }); } + from = Some(l2); + adj = true; } - 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(), - ); - } - } - } + // 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, map: &Map, i: &Intersection) -> Vec Vec { - 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> = 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 = 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 { - 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::>(); - 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> { - 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(), - }, - 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(), - }, + l1.width / 2.0 + }, + ), + 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> { - 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::>(), - ) { + ) || 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 -} diff --git a/map_model/src/objects/traffic_signals.rs b/map_model/src/objects/traffic_signals.rs index aab3dd5b5b..d9252433ed 100644 --- a/map_model/src/objects/traffic_signals.rs +++ b/map_model/src/objects/traffic_signals.rs @@ -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); - if pri == TurnPriority::Protected { - self.protected_movements.insert(id); - } else if pri == TurnPriority::Yield { - self.yield_movements.insert(id); - } + self.protected_movements.remove(&g.id); + self.yield_movements.remove(&g.id); + if pri == TurnPriority::Protected { + self.protected_movements.insert(g.id); + } else if pri == TurnPriority::Yield { + self.yield_movements.insert(g.id); } } pub fn enforce_minimum_crosswalk_time(&mut self, movement: &Movement) { diff --git a/map_model/src/objects/turn.rs b/map_model/src/objects/turn.rs index fc2d6f0665..59fe92daac 100644 --- a/map_model/src/objects/turn.rs +++ b/map_model/src/objects/turn.rs @@ -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, } impl Turn {