diff --git a/docs/design/code.md b/docs/design/code.md index 3d7471b7ac..445b6097c6 100644 --- a/docs/design/code.md +++ b/docs/design/code.md @@ -22,6 +22,8 @@ ControlMap, DrawMap, etc. Need to figure out how to handle reaping old IDs for transient objects like cars, but also things like modified roads. Slot maps? +Sort of related -- http://smallcultfollowing.com/babysteps/blog/2018/11/01/after-nll-interprocedural-conflicts/ + ## Everything as FSMs Driving and walking layer are both kind of broken, since they know about diff --git a/docs/design/walking.md b/docs/design/walking.md index 5a480542e1..c3f34afa6c 100644 --- a/docs/design/walking.md +++ b/docs/design/walking.md @@ -55,3 +55,5 @@ Small complication with two directional sidewalks original direction always? How to start/end walking? - do they belong to children {forwards, backwards}? They'd no longer be in order. + +And what about modeling shared left-turn lanes? diff --git a/map_model/src/make/intersections.rs b/map_model/src/make/intersections.rs index 209da621a8..11daca72f4 100644 --- a/map_model/src/make/intersections.rs +++ b/map_model/src/make/intersections.rs @@ -1,106 +1,145 @@ -use geom::{PolyLine, Pt2D}; +use dimensioned::si; +use geom::{PolyLine, Angle, Pt2D}; use std::collections::BTreeSet; +use std::marker; use {Intersection, Road, RoadID, LANE_THICKNESS}; +const DEGENERATE_INTERSECTION_HALF_LENGTH: si::Meter = si::Meter { + value_unsafe: 5.0, + _marker: marker::PhantomData, +}; + +// The polygon should exist entirely within the thick bands around all original roads -- it just +// carves up part of that space, doesn't reach past it. pub fn intersection_polygon( i: &Intersection, road_ids: BTreeSet, roads: &Vec, ) -> Vec { - // Turn all of the incident roads into the center PolyLine, always pointing at the intersection - // (endpoint is pt). The f64's are the width to shift without transforming the points, and then - // the width to shift when reversing the points. - // TODO Simplify code by just stashing the two transformed PolyLines here. ;) - let mut center_lines: Vec<(PolyLine, RoadID, f64, f64)> = road_ids + // Turn all of the incident roads into two PolyLines (the "forwards" and "backwards" borders of + // the road), both with an endpoint at i.point, and the angle of the last segment of the center + // line. + let mut lines: Vec<(RoadID, Angle, PolyLine, PolyLine)> = road_ids .into_iter() .map(|id| { let r = &roads[id.0]; - let line = &r.center_pts; + let center_line = &r.center_pts; let fwd_width = LANE_THICKNESS * (r.children_forwards.len() as f64); let back_width = LANE_THICKNESS * (r.children_backwards.len() as f64); - if line.first_pt() == i.point { - (line.reversed(), id, back_width, fwd_width) - } else if line.last_pt() == i.point { - (line.clone(), id, fwd_width, back_width) + let (line, width_normal, width_reverse) = if center_line.first_pt() == i.point { + (center_line.reversed(), back_width, fwd_width) + } else if center_line.last_pt() == i.point { + (center_line.clone(), fwd_width, back_width) } else { panic!("Incident road {} doesn't have an endpoint at {}", id, i.id); - } + }; + + let pl_normal = line.shift(width_normal).unwrap(); + let pl_reverse = line.reversed().shift(width_reverse).unwrap().reversed(); + (id, line.last_line().angle(), pl_normal, pl_reverse) }).collect(); // Sort the polylines by the angle of their last segment. // TODO This might break weirdly for polylines with very short last lines! - center_lines.sort_by_key(|(pl, _, _, _)| pl.last_line().angle().normalized_degrees() as i64); + lines.sort_by_key(|(_, angle, _, _)| angle.normalized_degrees() as i64); - // Now look at adjacent pairs of these polylines... + // Special cases for degenerate intersections. let mut endpoints: Vec = Vec::new(); - for idx1 in 0..center_lines.len() as isize { - let idx2 = idx1 + 1; - - let (center1, id1, _, width1_reverse) = wraparound_get(¢er_lines, idx1); - let (center2, id2, width2_normal, _) = wraparound_get(¢er_lines, idx2); - - // Turn the center polylines into one of the road's border polylines. Every road should - // have a chance to be shifted in both directions. - let pl1 = center1 + if lines.len() == 1 { + // Dead-ends! + let (id, _, pl_a, pl_b) = &lines[0]; + let pt1 = pl_a .reversed() - .shift(*width1_reverse) - .unwrap() - .reversed(); - let pl2 = center2.shift(*width2_normal).unwrap(); + .safe_dist_along(DEGENERATE_INTERSECTION_HALF_LENGTH * 2.0) + .map(|(pt, _)| pt); + let pt2 = pl_b + .reversed() + .safe_dist_along(DEGENERATE_INTERSECTION_HALF_LENGTH * 2.0) + .map(|(pt, _)| pt); + if pt1.is_some() && pt2.is_some() { + endpoints.extend(vec![ + pt1.unwrap(), + pt2.unwrap(), + pl_b.last_pt(), + pl_a.last_pt(), + ]); + } else { + error!("{} is a dead-end for {}, which is too short to make degenerate intersection geometry", i.id, id); + endpoints.extend(vec![pl_a.last_pt(), pl_b.last_pt()]); + } + } else if lines.len() == 2 { + let (id1, _, pl1_a, pl1_b) = &lines[0]; + let (id2, _, pl2_a, pl2_b) = &lines[1]; + endpoints.extend( + vec![pl1_a, pl1_b, pl2_a, pl2_b] + .into_iter() + .filter_map(|l| { + l.reversed() + .safe_dist_along(DEGENERATE_INTERSECTION_HALF_LENGTH) + .map(|(pt, _)| pt) + }).collect::>(), + ); + if endpoints.len() != 4 { + error!("{} has only {} and {}, some of which are too short to make degenerate intersection geometry", i.id, id1, id2); + endpoints.clear(); + endpoints.extend(vec![ + pl1_a.last_pt(), + pl1_b.last_pt(), + pl2_a.last_pt(), + pl2_b.last_pt(), + ]); + } + } else { + // Look at adjacent pairs of these polylines... + for idx1 in 0..lines.len() as isize { + let idx2 = idx1 + 1; - // If the two lines are too close in angle, they'll either not hit or even if they do, it - // won't be right. - let angle_diff = (pl1.last_line().angle().opposite().normalized_degrees() - - pl2.last_line().angle().normalized_degrees()).abs(); + let (id1, _, _, pl1) = wraparound_get(&lines, idx1); + let (id2, _, pl2, _) = wraparound_get(&lines, idx2); - // TODO A tuning challenge. :) - if angle_diff > 15.0 { - // The easy case! - if let Some(hit) = pl1.intersection(&pl2) { - endpoints.push(hit); - continue; + // If the two lines are too close in angle, they'll either not hit or even if they do, it + // won't be right. + let angle_diff = (pl1.last_line().angle().opposite().normalized_degrees() + - pl2.last_line().angle().normalized_degrees()).abs(); + + // TODO A tuning challenge. :) + if angle_diff > 15.0 { + // The easy case! + if let Some(hit) = pl1.intersection(&pl2) { + endpoints.push(hit); + continue; + } } - } - let mut ok = true; + let mut ok = true; - // Use the next adjacent road, doing line to line segment intersection instead. - let inf_line1 = { - let (center, _, _, width_reverse) = wraparound_get(¢er_lines, idx1 - 1); - center - .reversed() - .shift(*width_reverse) - .unwrap() - .reversed() - .last_line() - }; - if let Some(hit) = pl1.intersection_infinite_line(inf_line1) { - endpoints.push(hit); - } else { - endpoints.push(pl1.last_pt()); - ok = false; - } + // Use the next adjacent road, doing line to line segment intersection instead. + let inf_line1 = wraparound_get(&lines, idx1 - 1).3.last_line(); + if let Some(hit) = pl1.intersection_infinite_line(inf_line1) { + endpoints.push(hit); + } else { + endpoints.push(pl1.last_pt()); + ok = false; + } - let inf_line2 = { - let (center, _, width_normal, _) = wraparound_get(¢er_lines, idx2 + 1); - center.shift(*width_normal).unwrap().last_line() - }; - if let Some(hit) = pl2.intersection_infinite_line(inf_line2) { - endpoints.push(hit); - } else { - endpoints.push(pl2.last_pt()); - ok = false; - } + let inf_line2 = wraparound_get(&lines, idx2 + 1).2.last_line(); + if let Some(hit) = pl2.intersection_infinite_line(inf_line2) { + endpoints.push(hit); + } else { + endpoints.push(pl2.last_pt()); + ok = false; + } - if !ok { - warn!( - "No hit btwn {} and {}, for {} with {} incident roads", - id1, - id2, - i.id, - center_lines.len() - ); + if !ok { + warn!( + "No hit btwn {} and {}, for {} with {} incident roads", + id1, + id2, + i.id, + lines.len() + ); + } } }