oops, gigantic change trying to handle bad lane geometry

- debug points and triangles of lanes
- disabled fixes for polylines with points that change angles by 180 and can be fixed by swapping them
- organize intersection polygon code in different methods for the degenerate cases
- try something new for degenerate-two's... fix road centers, then calculate intersection corners. it fixed a few spots!
This commit is contained in:
Dustin Carlino 2019-01-15 11:49:34 -08:00
parent 7579cd9f7e
commit aae021f478
7 changed files with 344 additions and 182 deletions

View File

@ -1,26 +1,70 @@
use crate::objects::{Ctx, ID};
use crate::plugins::{Plugin, PluginCtx};
use ezgui::{GfxCtx, Key, Text};
use geom::Pt2D;
use geom::{Pt2D, Triangle};
enum Item {
Point(Pt2D),
Triangle(Triangle),
}
pub struct DebugPolygon {
pts: Vec<Pt2D>,
current_pt: usize,
items: Vec<Item>,
current: usize,
}
impl DebugPolygon {
pub fn new(ctx: &mut PluginCtx) -> Option<DebugPolygon> {
if let Some(ID::Intersection(id)) = ctx.primary.current_selection {
match ctx.primary.current_selection {
Some(ID::Intersection(id)) => {
if ctx
.input
.contextual_action(Key::X, "debug intersection geometry")
{
return Some(DebugPolygon {
pts: ctx.primary.map.get_i(id).polygon.clone(),
current_pt: 0,
items: ctx
.primary
.map
.get_i(id)
.polygon
.iter()
.map(|pt| Item::Point(*pt))
.collect(),
current: 0,
});
}
}
Some(ID::Lane(id)) => {
if ctx.input.contextual_action(Key::X, "debug lane geometry") {
return Some(DebugPolygon {
items: ctx
.primary
.map
.get_l(id)
.lane_center_pts
.points()
.iter()
.map(|pt| Item::Point(*pt))
.collect(),
current: 0,
});
} else if ctx.input.contextual_action(Key::F2, "debug lane triangles") {
return Some(DebugPolygon {
items: ctx
.primary
.draw_map
.get_l(id)
.polygon
.triangles
.iter()
.map(|tri| Item::Triangle(tri.clone()))
.collect(),
current: 0,
});
}
}
_ => {}
}
None
}
}
@ -30,19 +74,26 @@ impl Plugin for DebugPolygon {
ctx.input.set_mode("Polygon Debugger", &ctx.canvas);
if ctx.input.modal_action("quit") {
return false;
} else if self.current_pt != self.pts.len() - 1 && ctx.input.modal_action("next point") {
self.current_pt += 1;
} else if self.current_pt != 0 && ctx.input.modal_action("prev point") {
self.current_pt -= 1;
} else if self.current != self.items.len() - 1 && ctx.input.modal_action("next item") {
self.current += 1;
} else if self.current != 0 && ctx.input.modal_action("prev item") {
self.current -= 1;
}
true
}
fn draw(&self, g: &mut GfxCtx, ctx: &Ctx) {
ctx.canvas.draw_text_at(
g,
Text::from_line(format!("{}", self.current_pt)),
self.pts[self.current_pt],
);
match self.items[self.current] {
Item::Point(pt) => {
ctx.canvas
.draw_text_at(g, Text::from_line(format!("{}", self.current)), pt);
}
Item::Triangle(ref tri) => {
for pt in &[tri.pt1, tri.pt2, tri.pt3] {
ctx.canvas
.draw_text_at(g, Text::from_line(format!("{}", self.current)), *pt);
}
}
}
}
}

View File

@ -174,8 +174,8 @@ impl<S: UIState> GUI<RenderingHints> for UI<S> {
"Polygon Debugger",
vec![
(Key::Enter, "quit"),
(Key::Dot, "next point"),
(Key::Comma, "prev point"),
(Key::Dot, "next item"),
(Key::Comma, "prev item"),
],
),
]

View File

@ -153,6 +153,17 @@ pub enum Key {
UpArrow,
DownArrow,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
}
impl Key {
@ -212,7 +223,18 @@ impl Key {
| Key::RightArrow
| Key::UpArrow
| Key::DownArrow
| Key::F1 => None,
| Key::F1
| Key::F2
| Key::F3
| Key::F4
| Key::F5
| Key::F6
| Key::F7
| Key::F8
| Key::F9
| Key::F10
| Key::F11
| Key::F12 => None,
}
}
@ -230,6 +252,17 @@ impl Key {
Key::UpArrow => "".to_string(),
Key::DownArrow => "".to_string(),
Key::F1 => "F1".to_string(),
Key::F2 => "F2".to_string(),
Key::F3 => "F3".to_string(),
Key::F4 => "F4".to_string(),
Key::F5 => "F5".to_string(),
Key::F6 => "F6".to_string(),
Key::F7 => "F7".to_string(),
Key::F8 => "F8".to_string(),
Key::F9 => "F9".to_string(),
Key::F10 => "F10".to_string(),
Key::F11 => "F11".to_string(),
Key::F12 => "F12".to_string(),
// These have to_char, but override here
Key::Space => "Space".to_string(),
_ => self.to_char(false).unwrap().to_string(),
@ -298,6 +331,17 @@ impl Key {
pi::Key::Up => Key::UpArrow,
pi::Key::Down => Key::DownArrow,
pi::Key::F1 => Key::F1,
pi::Key::F2 => Key::F2,
pi::Key::F3 => Key::F3,
pi::Key::F4 => Key::F4,
pi::Key::F5 => Key::F5,
pi::Key::F6 => Key::F6,
pi::Key::F7 => Key::F7,
pi::Key::F8 => Key::F8,
pi::Key::F9 => Key::F9,
pi::Key::F10 => Key::F10,
pi::Key::F11 => Key::F11,
pi::Key::F12 => Key::F12,
_ => {
println!("Unknown piston key {:?}", key);
return None;

View File

@ -36,6 +36,8 @@ impl Angle {
self.normalized_radians().to_degrees()
}
// Logically this returns [-180, 180], but keep in mind when we print this angle, it'll
// normalize to be [0, 360].
pub fn shortest_rotation_towards(self, other: Angle) -> Angle {
// https://math.stackexchange.com/questions/110080/shortest-way-to-achieve-target-angle
Angle::new_degs(

View File

@ -217,14 +217,19 @@ impl PolyLine {
// Things to remember about shifting polylines: the length before and after probably don't
// match up.
pub fn shift_right(&self, width: f64) -> Option<PolyLine> {
let result = self.shift_blindly_right(width);
// TODO check if any non-adjacent line segments intersect
Some(result)
/*let mut result = self.shift_blindly_right(width);
fix_angles(self, &mut result);
check_angles(self, &result);
Some(result)*/
Some(self.shift_blindly_right(width))
}
pub fn shift_left(&self, width: f64) -> Option<PolyLine> {
let result = self.shift_blindly_left(width);
Some(result)
/*let mut result = self.shift_blindly_left(width);
fix_angles(self, &mut result);
check_angles(self, &result);
Some(result)*/
Some(self.shift_blindly_left(width))
}
// Doesn't massage sharp twists into more points. For polygon rendering.
@ -450,3 +455,40 @@ impl fmt::Display for PolyLine {
result.push(first_pt);
result.iter().map(|pair| [pair.0, pair.1]).collect()
}*/
fn fix_angles(orig: &PolyLine, result: &mut PolyLine) {
// Check that the angles roughly match up between the original and shifted line
for (idx, (orig_l, shifted_l)) in orig.lines().iter().zip(result.lines().iter()).enumerate() {
let orig_angle = orig_l.angle();
let shifted_angle = shifted_l.angle();
let rot = orig_angle.shortest_rotation_towards(shifted_angle);
if rot.normalized_degrees() > 10.0 && rot.normalized_degrees() < 359.0 {
// When this happens, the rotation is usually right around 180 -- so try swapping
// the points!
/*println!(
"Points changed angles from {} to {} (rot {})",
orig_angle, shifted_angle, rot
);*/
result.pts.swap(idx, idx + 1);
// TODO recalculate length, to be safe
// Start the fixing over. Make sure we won't infinite loop...
//return fix_angles(orig, result);
}
}
}
fn check_angles(a: &PolyLine, b: &PolyLine) {
for (orig_l, shifted_l) in a.lines().iter().zip(b.lines().iter()) {
let orig_angle = orig_l.angle();
let shifted_angle = shifted_l.angle();
let rot = orig_angle.shortest_rotation_towards(shifted_angle);
if rot.normalized_degrees() > 10.0 && rot.normalized_degrees() < 359.0 {
println!(
"BAD! Points changed angles from {} to {} (rot {})",
orig_angle, shifted_angle, rot
);
}
}
}

View File

@ -132,7 +132,7 @@ pub fn make_half_map(
panic!("{:?} is orphaned!", i);
}
i.polygon = make::intersections::initial_intersection_polygon(i, &mut m.roads);
i.polygon = make::intersections::intersection_polygon(i, &mut m.roads);
}
timer.start_iter("make lane geometry", m.lanes.len());

View File

@ -12,7 +12,7 @@ const DEGENERATE_INTERSECTION_HALF_LENGTH: si::Meter<f64> = si::Meter {
// 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 initial_intersection_polygon(i: &Intersection, roads: &mut Vec<Road>) -> Vec<Pt2D> {
pub fn intersection_polygon(i: &Intersection, roads: &mut Vec<Road>) -> Vec<Pt2D> {
// Turn all of the incident roads into two PolyLines (the "forwards" and "backwards" borders of
// the road, if the roads were oriented to both be incoming to the intersection), both ending
// at the intersection (which may be different points for merged intersections!), and the angle
@ -45,10 +45,31 @@ pub fn initial_intersection_polygon(i: &Intersection, roads: &mut Vec<Road>) ->
// we have to look at all the endpoints and sort by angle from the center of the points?
lines.sort_by_key(|(_, angle, _, _)| angle.normalized_degrees() as i64);
// Special cases for degenerate intersections.
let mut endpoints: Vec<Pt2D> = Vec::new();
if lines.len() == 1 {
// Dead-ends!
let mut endpoints = if lines.len() == 1 {
deadend(roads, i.id, &lines)
} else if lines.len() == 2 {
degenerate_twoway(roads, i.id, &lines)
} else if let Some(pts) = make_new_polygon(roads, i.id, &lines) {
pts
} else {
/*note(format!(
"couldnt make new for {} with {} roads",
i.id,
lines.len()
));*/
make_old_polygon(i.id, &lines)
};
// Close off the polygon
endpoints.push(endpoints[0]);
endpoints
}
fn deadend(
roads: &mut Vec<Road>,
i: IntersectionID,
lines: &Vec<(RoadID, Angle, PolyLine, PolyLine)>,
) -> Vec<Pt2D> {
let (id, _, pl_a, pl_b) = &lines[0];
let pt1 = pl_a
.reversed()
@ -59,15 +80,8 @@ pub fn initial_intersection_polygon(i: &Intersection, roads: &mut Vec<Road>) ->
.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(),
]);
let mut r = &mut roads[id.0];
if r.src_i == i.id {
if r.src_i == i {
r.center_pts = r
.center_pts
.slice(
@ -84,47 +98,51 @@ pub fn initial_intersection_polygon(i: &Intersection, roads: &mut Vec<Road>) ->
)
.0;
}
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()]);
error!(
"{} is a dead-end for {}, which is too short to make degenerate intersection geometry",
i, id
);
vec![pl_a.last_pt(), pl_b.last_pt()]
}
} else if lines.len() == 2 {
}
fn degenerate_twoway(
roads: &mut Vec<Road>,
i: IntersectionID,
lines: &Vec<(RoadID, Angle, PolyLine, PolyLine)>,
) -> Vec<Pt2D> {
let (id1, _, pl1_a, pl1_b) = &lines[0];
let (id2, _, pl2_a, pl2_b) = &lines[1];
if pl1_a.length() >= DEGENERATE_INTERSECTION_HALF_LENGTH
&& pl1_b.length() >= DEGENERATE_INTERSECTION_HALF_LENGTH
&& pl2_a.length() >= DEGENERATE_INTERSECTION_HALF_LENGTH
&& pl2_b.length() >= DEGENERATE_INTERSECTION_HALF_LENGTH
{
// We could also add in the last points of each line, but this doesn't actually look
// great when widths of the two oads are different.
endpoints.extend(vec![
pl1_a
.reversed()
.dist_along(DEGENERATE_INTERSECTION_HALF_LENGTH)
.0,
pl1_b
.reversed()
.dist_along(DEGENERATE_INTERSECTION_HALF_LENGTH)
.0,
pl2_a
.reversed()
.dist_along(DEGENERATE_INTERSECTION_HALF_LENGTH)
.0,
pl2_b
.reversed()
.dist_along(DEGENERATE_INTERSECTION_HALF_LENGTH)
.0,
]);
endpoints.dedup();
if roads[id1.0].center_pts.length() >= DEGENERATE_INTERSECTION_HALF_LENGTH
&& roads[id2.0].center_pts.length() >= DEGENERATE_INTERSECTION_HALF_LENGTH
{
// Why fix center pts and then re-shift out, instead of use the pl1_a and friends? because
// dist_along on shifted polylines is NOT equivalent.
let mut endpoints = Vec::new();
for road_id in &[id1, id2] {
let mut r = &mut roads[road_id.0];
if r.src_i == i.id {
if r.src_i == i {
r.center_pts = r
.center_pts
.slice(DEGENERATE_INTERSECTION_HALF_LENGTH, r.center_pts.length())
.0;
endpoints.push(
r.center_pts
.shift_left(LANE_THICKNESS * (r.children_backwards.len() as f64))
.unwrap()
.first_pt(),
);
endpoints.push(
r.center_pts
.shift_right(LANE_THICKNESS * (r.children_forwards.len() as f64))
.unwrap()
.first_pt(),
);
} else {
r.center_pts = r
.center_pts
@ -133,84 +151,30 @@ pub fn initial_intersection_polygon(i: &Intersection, roads: &mut Vec<Road>) ->
r.center_pts.length() - DEGENERATE_INTERSECTION_HALF_LENGTH,
)
.0;
endpoints.push(
r.center_pts
.shift_right(LANE_THICKNESS * (r.children_forwards.len() as f64))
.unwrap()
.last_pt(),
);
endpoints.push(
r.center_pts
.shift_left(LANE_THICKNESS * (r.children_backwards.len() as f64))
.unwrap()
.last_pt(),
);
}
}
endpoints
} else {
error!("{} has only {} and {}, some of which are too short to make degenerate intersection geometry", i.id, id1, id2);
endpoints.extend(vec![
error!("{} has only {} and {}, some of which are too short to make degenerate intersection geometry", i, id1, id2);
vec![
pl1_a.last_pt(),
pl1_b.last_pt(),
pl2_a.last_pt(),
pl2_b.last_pt(),
]);
]
}
} else {
if let Some(pts) = make_new_polygon(roads, i.id, &lines) {
endpoints.extend(pts);
} else {
/*note(format!(
"couldnt make new for {} with {} roads",
i.id,
lines.len()
));*/
// Look at adjacent pairs of these polylines...
for idx1 in 0..lines.len() as isize {
let idx2 = idx1 + 1;
let (id1, _, _, pl1) = wraparound_get(&lines, idx1);
let (id2, _, pl2, _) = wraparound_get(&lines, idx2);
// 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;
// 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 = 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,
lines.len()
);
}
}
}
}
// Close off the polygon
endpoints.push(endpoints[0]);
endpoints
}
fn make_new_polygon(
@ -337,6 +301,65 @@ fn make_new_polygon(
Some(approx_dedupe(endpoints))
}
fn make_old_polygon(
i: IntersectionID,
lines: &Vec<(RoadID, Angle, PolyLine, PolyLine)>,
) -> Vec<Pt2D> {
let mut endpoints = Vec::new();
// Look at adjacent pairs of these polylines...
for idx1 in 0..lines.len() as isize {
let idx2 = idx1 + 1;
let (id1, _, _, pl1) = wraparound_get(&lines, idx1);
let (id2, _, pl2, _) = wraparound_get(&lines, idx2);
// 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;
// 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 = 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,
lines.len()
);
}
}
endpoints
}
// Temporary until Pt2D has proper resolution.
fn approx_dedupe(pts: Vec<Pt2D>) -> Vec<Pt2D> {
let mut result: Vec<Pt2D> = Vec::new();