moving polyline to map_model, using pt2d

This commit is contained in:
Dustin Carlino 2018-06-24 16:55:19 -07:00
parent 3e33162779
commit 2b9eb66ba1
4 changed files with 167 additions and 162 deletions

View File

@ -5,6 +5,7 @@ use ezgui::input::UserInput;
use graphics;
use graphics::types::Color;
use gui;
use map_model::{polygons_for_polyline, shift_polyline, Pt2D};
use piston::input::Key;
use piston::window::Size;
use std::f64;
@ -67,7 +68,7 @@ impl gui::GUI for UI {
graphics::clear(WHITE, g.gfx);
g.ctx = self.canvas.get_transformed_context(&g.orig_ctx);
let mut labels: Vec<((f64, f64), String)> = Vec::new();
let mut labels: Vec<(Pt2D, String)> = Vec::new();
macro_rules! point {
($pt_name:ident, $value:expr) => {
@ -92,10 +93,13 @@ impl gui::GUI for UI {
// TODO bezier curves could be ideal for both drawing and car paths, but no easy way to
// try them out in piston
point!(p1, (100.0, 100.0));
point!(p2, (110.0, 200.0));
point!(p3, (p1.0 + self.p3_offset.0, p1.1 + self.p3_offset.1));
point!(p4, (500.0, 120.0));
point!(p1, Pt2D::new(100.0, 100.0));
point!(p2, Pt2D::new(110.0, 200.0));
point!(
p3,
Pt2D::new(p1.x() + self.p3_offset.0, p1.y() + self.p3_offset.1)
);
point!(p4, Pt2D::new(500.0, 120.0));
draw_polyline(g, vec![p1, p2, p3, p4], thick, RED);
@ -108,7 +112,6 @@ impl gui::GUI for UI {
draw_polygon(g, p, BLACK);
}
/*
// Two lanes on one side of the road
let l1_pts = shift_polyline(shift_away, &vec![p1, p2, p3, p4]);
for (idx, pt) in l1_pts.iter().enumerate() {
@ -128,7 +131,6 @@ impl gui::GUI for UI {
labels.push((*pt, format!("l3_p{}", idx + 1)));
}
draw_polyline(g, l3_pts, thin, BLUE);
*/
// Manual approach for more debugging
/*points!(p1_e, p2_e, shift_line(shift_away, p3, p2));
@ -151,123 +153,37 @@ impl gui::GUI for UI {
}
impl UI {
fn label(&self, g: &mut GfxCtx, pt: (f64, f64), text: &str) {
fn label(&self, g: &mut GfxCtx, pt: Pt2D, text: &str) {
self.canvas
.draw_text_at(g, &vec![text.to_string()], pt.0, pt.1);
.draw_text_at(g, &vec![text.to_string()], pt.x(), pt.y());
}
}
fn draw_line(g: &mut GfxCtx, pt1: (f64, f64), pt2: (f64, f64), thickness: f64, color: Color) {
fn draw_line(g: &mut GfxCtx, pt1: Pt2D, pt2: Pt2D, thickness: f64, color: Color) {
let l = graphics::Line::new(color, thickness);
l.draw(
[pt1.0, pt1.1, pt2.0, pt2.1],
[pt1.x(), pt1.y(), pt2.x(), pt2.y()],
&g.ctx.draw_state,
g.ctx.transform,
g.gfx,
);
}
fn draw_polyline(g: &mut GfxCtx, pts: Vec<(f64, f64)>, thickness: f64, color: Color) {
fn draw_polyline(g: &mut GfxCtx, pts: Vec<Pt2D>, thickness: f64, color: Color) {
assert!(pts.len() >= 2);
for pair in pts.windows(2) {
draw_line(g, pair[0], pair[1], thickness, color);
}
}
fn draw_polygon(g: &mut GfxCtx, pts: Vec<[f64; 2]>, color: Color) {
graphics::Polygon::new(color).draw(&pts, &g.ctx.draw_state, g.ctx.transform, g.gfx);
fn draw_polygon(g: &mut GfxCtx, pts: Vec<Pt2D>, color: Color) {
let tuples: Vec<[f64; 2]> = pts.iter().map(|pt| [pt.x(), pt.y()]).collect();
graphics::Polygon::new(color).draw(&tuples, &g.ctx.draw_state, g.ctx.transform, g.gfx);
}
fn shift_line(width: f64, pt1: (f64, f64), pt2: (f64, f64)) -> ((f64, f64), (f64, f64)) {
let x1 = pt1.0;
let y1 = pt1.1;
let x2 = pt2.0;
let y2 = pt2.1;
let half_pi = f64::consts::PI / 2.0;
let angle = (y2 - y1).atan2(x2 - x1) + half_pi;
let shifted1 = (x1 + width * angle.cos(), y1 + width * angle.sin());
let shifted2 = (x2 + width * angle.cos(), y2 + width * angle.sin());
(shifted1, shifted2)
}
// TODO unsure why this doesn't work. maybe see if mouse is inside polygon to check it out?
/*fn polygon_for_polyline(center_pts: &Vec<(f64, f64)>, width: f64) -> Vec<[f64; 2]> {
let mut result = shift_polyline(width / 2.0, center_pts);
let mut reversed_center_pts = center_pts.clone();
reversed_center_pts.reverse();
result.extend(shift_polyline(width / 2.0, &reversed_center_pts));
// TODO unclear if piston needs last point to match the first or not
let first_pt = result[0];
result.push(first_pt);
result.iter().map(|pair| [pair.0, pair.1]).collect()
}*/
// TODO why do we need a bunch of triangles? why doesn't the single polygon triangulate correctly?
// TODO ideally, detect when the polygon overlaps itself due to sharp lines and too much width
fn polygons_for_polyline(center_pts: &Vec<(f64, f64)>, width: f64) -> Vec<Vec<[f64; 2]>> {
let side1 = shift_polyline(width / 2.0, center_pts);
let mut reversed_center_pts = center_pts.clone();
reversed_center_pts.reverse();
let mut side2 = shift_polyline(width / 2.0, &reversed_center_pts);
side2.reverse();
let mut result: Vec<Vec<(f64, f64)>> = Vec::new();
for high_idx in 1..center_pts.len() {
result.push(vec![
side1[high_idx],
side1[high_idx - 1],
side2[high_idx - 1],
]);
result.push(vec![side2[high_idx], side2[high_idx - 1], side1[high_idx]]);
}
println!("{} triangles", result.len());
result
.iter()
.map(|tri| tri.iter().map(|pair| [pair.0, pair.1]).collect())
.collect()
}
fn shift_polyline(width: f64, pts: &Vec<(f64, f64)>) -> Vec<(f64, f64)> {
assert!(pts.len() >= 2);
if pts.len() == 2 {
let (pt1_shift, pt2_shift) = shift_line(width, pts[0], pts[1]);
return vec![pt1_shift, pt2_shift];
}
let mut result: Vec<(f64, f64)> = Vec::new();
let mut pt3_idx = 2;
let mut pt1_raw = pts[0];
let mut pt2_raw = pts[1];
loop {
let pt3_raw = pts[pt3_idx];
let (pt1_shift, pt2_shift_1st) = shift_line(width, pt1_raw, pt2_raw);
let (pt2_shift_2nd, pt3_shift) = shift_line(width, pt2_raw, pt3_raw);
let pt2_shift = line_intersection((pt1_shift, pt2_shift_1st), (pt2_shift_2nd, pt3_shift));
if pt3_idx == 2 {
result.push(pt1_shift);
}
result.push(pt2_shift);
if pt3_idx == pts.len() - 1 {
result.push(pt3_shift);
break;
}
pt1_raw = pt2_raw;
pt2_raw = pt3_raw;
pt3_idx += 1;
}
assert!(result.len() == pts.len());
result
}
fn angle_degrees(from: (f64, f64), to: (f64, f64)) -> f64 {
fn angle_degrees(from: Pt2D, to: Pt2D) -> f64 {
// Y inversion necessary because of drawing
let theta_rads = (from.1 - to.1).atan2(to.0 - from.0);
let theta_rads = (from.y() - to.y()).atan2(to.x() - from.x());
let theta_degs = theta_rads * 360.0 / (2.0 * f64::consts::PI);
// Normalize
if theta_degs < 0.0 {
@ -276,60 +192,3 @@ fn angle_degrees(from: (f64, f64), to: (f64, f64)) -> f64 {
theta_degs
}
}
// NOT segment. ignores parallel lines.
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
fn line_intersection(l1: ((f64, f64), (f64, f64)), l2: ((f64, f64), (f64, f64))) -> (f64, f64) {
let x1 = (l1.0).0;
let y1 = (l1.0).1;
let x2 = (l1.1).0;
let y2 = (l1.1).1;
let x3 = (l2.0).0;
let y3 = (l2.0).1;
let x4 = (l2.1).0;
let y4 = (l2.1).1;
let numer_x = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
let numer_y = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
(numer_x / denom, numer_y / denom)
}
#[test]
fn shift_polyline_equivalence() {
use rand;
let scale = 1000.0;
let pt1 = (rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let pt2 = (rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let pt3 = (rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let pt4 = (rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let pt5 = (rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let width = 50.0;
let (pt1_s, _) = shift_line(width, pt1, pt2);
let pt2_s = line_intersection(shift_line(width, pt1, pt2), shift_line(width, pt2, pt3));
let pt3_s = line_intersection(shift_line(width, pt2, pt3), shift_line(width, pt3, pt4));
let pt4_s = line_intersection(shift_line(width, pt3, pt4), shift_line(width, pt4, pt5));
let (_, pt5_s) = shift_line(width, pt4, pt5);
assert_eq!(
shift_polyline(width, &vec![pt1, pt2, pt3, pt4, pt5]),
vec![pt1_s, pt2_s, pt3_s, pt4_s, pt5_s]
);
}
#[test]
fn shift_short_polyline_equivalence() {
use rand;
let scale = 1000.0;
let pt1 = (rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let pt2 = (rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let width = 50.0;
let (pt1_s, pt2_s) = shift_line(width, pt1, pt2);
assert_eq!(shift_polyline(width, &vec![pt1, pt2]), vec![pt1_s, pt2_s]);
}

View File

@ -6,5 +6,6 @@ authors = ["Dustin Carlino <dabreegster@gmail.com>"]
[dependencies]
ordered-float = "0.5.0"
protobuf = "1.4"
rand = "0.5.1"
serde = "1.0"
serde_derive = "1.0"

View File

@ -2,19 +2,22 @@
extern crate ordered_float;
extern crate protobuf;
extern crate rand;
extern crate serde;
#[macro_use]
extern crate serde_derive;
pub mod pb;
mod polyline;
use ordered_float::NotNaN;
pub use polyline::{polygons_for_polyline, shift_polyline};
use protobuf::error::ProtobufError;
use protobuf::{CodedInputStream, CodedOutputStream, Message};
use std::collections::HashMap;
use std::f64;
use std::fs::File;
pub mod pb;
pub fn write_pb(map: &pb::Map, path: &str) -> Result<(), ProtobufError> {
let mut file = File::create(path)?;
let mut cos = CodedOutputStream::new(&mut file);

142
map_model/src/polyline.rs Normal file
View File

@ -0,0 +1,142 @@
use Pt2D;
use std::f64;
// TODO unsure why this doesn't work. maybe see if mouse is inside polygon to check it out?
/*fn polygon_for_polyline(center_pts: &Vec<(f64, f64)>, width: f64) -> Vec<[f64; 2]> {
let mut result = shift_polyline(width / 2.0, center_pts);
let mut reversed_center_pts = center_pts.clone();
reversed_center_pts.reverse();
result.extend(shift_polyline(width / 2.0, &reversed_center_pts));
// TODO unclear if piston needs last point to match the first or not
let first_pt = result[0];
result.push(first_pt);
result.iter().map(|pair| [pair.0, pair.1]).collect()
}*/
// TODO why do we need a bunch of triangles? why doesn't the single polygon triangulate correctly?
// TODO ideally, detect when the polygon overlaps itself due to sharp lines and too much width
pub fn polygons_for_polyline(center_pts: &Vec<Pt2D>, width: f64) -> Vec<Vec<Pt2D>> {
let side1 = shift_polyline(width / 2.0, center_pts);
let mut reversed_center_pts = center_pts.clone();
reversed_center_pts.reverse();
let mut side2 = shift_polyline(width / 2.0, &reversed_center_pts);
side2.reverse();
let mut result: Vec<Vec<Pt2D>> = Vec::new();
for high_idx in 1..center_pts.len() {
result.push(vec![
side1[high_idx],
side1[high_idx - 1],
side2[high_idx - 1],
]);
result.push(vec![side2[high_idx], side2[high_idx - 1], side1[high_idx]]);
}
result
}
pub fn shift_polyline(width: f64, pts: &Vec<Pt2D>) -> Vec<Pt2D> {
assert!(pts.len() >= 2);
if pts.len() == 2 {
let (pt1_shift, pt2_shift) = shift_line(width, pts[0], pts[1]);
return vec![pt1_shift, pt2_shift];
}
let mut result: Vec<Pt2D> = Vec::new();
let mut pt3_idx = 2;
let mut pt1_raw = pts[0];
let mut pt2_raw = pts[1];
loop {
let pt3_raw = pts[pt3_idx];
let (pt1_shift, pt2_shift_1st) = shift_line(width, pt1_raw, pt2_raw);
let (pt2_shift_2nd, pt3_shift) = shift_line(width, pt2_raw, pt3_raw);
let pt2_shift = line_intersection((pt1_shift, pt2_shift_1st), (pt2_shift_2nd, pt3_shift));
if pt3_idx == 2 {
result.push(pt1_shift);
}
result.push(pt2_shift);
if pt3_idx == pts.len() - 1 {
result.push(pt3_shift);
break;
}
pt1_raw = pt2_raw;
pt2_raw = pt3_raw;
pt3_idx += 1;
}
assert!(result.len() == pts.len());
result
}
fn shift_line(width: f64, pt1: Pt2D, pt2: Pt2D) -> (Pt2D, Pt2D) {
let x1 = pt1.x();
let y1 = pt1.y();
let x2 = pt2.x();
let y2 = pt2.y();
let half_pi = f64::consts::PI / 2.0;
let angle = (y2 - y1).atan2(x2 - x1) + half_pi;
let shifted1 = Pt2D::new(x1 + width * angle.cos(), y1 + width * angle.sin());
let shifted2 = Pt2D::new(x2 + width * angle.cos(), y2 + width * angle.sin());
(shifted1, shifted2)
}
// NOT segment. ignores parallel lines.
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line
fn line_intersection(l1: (Pt2D, Pt2D), l2: (Pt2D, Pt2D)) -> Pt2D {
let x1 = l1.0.x();
let y1 = l1.0.y();
let x2 = l1.1.x();
let y2 = l1.1.y();
let x3 = l2.0.x();
let y3 = l2.0.y();
let x4 = l2.1.x();
let y4 = l2.1.y();
let numer_x = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
let numer_y = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
Pt2D::new(numer_x / denom, numer_y / denom)
}
#[test]
fn shift_polyline_equivalence() {
use rand;
let scale = 1000.0;
let pt1 = Pt2D::new(rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let pt2 = Pt2D::new(rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let pt3 = Pt2D::new(rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let pt4 = Pt2D::new(rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let pt5 = Pt2D::new(rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let width = 50.0;
let (pt1_s, _) = shift_line(width, pt1, pt2);
let pt2_s = line_intersection(shift_line(width, pt1, pt2), shift_line(width, pt2, pt3));
let pt3_s = line_intersection(shift_line(width, pt2, pt3), shift_line(width, pt3, pt4));
let pt4_s = line_intersection(shift_line(width, pt3, pt4), shift_line(width, pt4, pt5));
let (_, pt5_s) = shift_line(width, pt4, pt5);
assert_eq!(
shift_polyline(width, &vec![pt1, pt2, pt3, pt4, pt5]),
vec![pt1_s, pt2_s, pt3_s, pt4_s, pt5_s]
);
}
#[test]
fn shift_short_polyline_equivalence() {
use rand;
let scale = 1000.0;
let pt1 = Pt2D::new(rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let pt2 = Pt2D::new(rand::random::<f64>() * scale, rand::random::<f64>() * scale);
let width = 50.0;
let (pt1_s, pt2_s) = shift_line(width, pt1, pt2);
assert_eq!(shift_polyline(width, &vec![pt1, pt2]), vec![pt1_s, pt2_s]);
}