diff --git a/editor/src/experimental.rs b/editor/src/experimental.rs index 577b84205e..6fa7b93382 100644 --- a/editor/src/experimental.rs +++ b/editor/src/experimental.rs @@ -1,8 +1,7 @@ use ezgui::canvas::Canvas; use ezgui::input::UserInput; use ezgui::GfxCtx; -use geom; -use geom::{PolyLine, Pt2D}; +use geom::{PolyLine, Polygon, Pt2D}; use graphics; use graphics::types::Color; use gui; @@ -307,7 +306,7 @@ impl UI { Pt2D::new(947.7612927256201, 765.1100512564725), // 5 ]; //draw_polyline(g, &PolyLine::new(pts.clone()), 0.25, RED); - for tri in geom::triangulate(&pts).iter() { + for tri in Polygon::new(&pts).for_drawing().iter() { g.draw_polygon(BLUE, tri); } diff --git a/editor/src/plugins/geom_validation.rs b/editor/src/plugins/geom_validation.rs index d43cf7fe7b..863d6ab096 100644 --- a/editor/src/plugins/geom_validation.rs +++ b/editor/src/plugins/geom_validation.rs @@ -3,7 +3,7 @@ use ezgui::input::UserInput; use generator; use geo; use geo::prelude::Intersects; -use geom::Pt2D; +use geom::{Polygon, Pt2D}; use graphics::math::Vec2d; use map_model::{geometry, BuildingID, IntersectionID, LaneID, Map, ParcelID}; use piston::input::Key; @@ -42,13 +42,13 @@ impl Validator { )); } for i in &draw_map.intersections { - objects.push((ID::Intersection(i.id), vec![make_poly(&i.polygon)])); + objects.push((ID::Intersection(i.id), vec![make_new_poly(&i.polygon)])); } for b in &draw_map.buildings { - objects.push((ID::Building(b.id), vec![make_poly(&b.fill_polygon)])); + objects.push((ID::Building(b.id), vec![make_new_poly(&b.fill_polygon)])); } for p in &draw_map.parcels { - objects.push((ID::Parcel(p.id), vec![make_poly(&p.fill_polygon)])); + objects.push((ID::Parcel(p.id), vec![make_new_poly(&p.fill_polygon)])); } println!( @@ -139,6 +139,14 @@ fn make_poly(points: &Vec) -> geo::Polygon { geo::Polygon::new(exterior.into(), Vec::new()) } +fn make_new_poly(p: &Polygon) -> geo::Polygon { + let exterior: Vec> = p.pts + .iter() + .map(|pt| geo::Point::new(pt.x(), pt.y())) + .collect(); + geo::Polygon::new(exterior.into(), Vec::new()) +} + // TODO duplicated with warp. generic handling of object types? fn get_pt(map: &Map, id: ID) -> Pt2D { match id { diff --git a/editor/src/render/building.rs b/editor/src/render/building.rs index ec0835a1fc..0b107a4f49 100644 --- a/editor/src/render/building.rs +++ b/editor/src/render/building.rs @@ -2,15 +2,12 @@ use aabb_quadtree::geom::Rect; use ezgui::GfxCtx; -use geom; -use geom::PolyLine; +use geom::{PolyLine, Polygon, Pt2D}; use graphics; use graphics::math::Vec2d; use graphics::types::Color; -use map_model; -use map_model::geometry; -use map_model::{BuildingID, Map}; -use render::PARCEL_BOUNDARY_THICKNESS; +use map_model::{Building, BuildingID, Map}; +use render::{get_bbox, PARCEL_BOUNDARY_THICKNESS}; use std::f64; #[derive(Debug)] @@ -18,22 +15,19 @@ pub struct DrawBuilding { pub id: BuildingID, // TODO should just have one. use graphics::Line for now. boundary_polygons: Vec>, - // TODO rm the other one - pub fill_polygon: Vec, - fill_triangles: Vec>, + pub fill_polygon: Polygon, front_path: Option<[f64; 4]>, } impl DrawBuilding { - pub fn new(bldg: &map_model::Building) -> DrawBuilding { + pub fn new(bldg: &Building) -> DrawBuilding { DrawBuilding { id: bldg.id, - fill_polygon: bldg.points.iter().map(|pt| [pt.x(), pt.y()]).collect(), // TODO ideally start the path on a side of the building front_path: bldg.front_path .as_ref() .map(|l| [l.pt1().x(), l.pt1().y(), l.pt2().x(), l.pt2().y()]), - fill_triangles: geom::triangulate(&bldg.points), + fill_polygon: Polygon::new(&bldg.points), boundary_polygons: PolyLine::new(bldg.points.clone()) .make_polygons_blindly(PARCEL_BOUNDARY_THICKNESS), } @@ -54,13 +48,13 @@ impl DrawBuilding { for p in &self.boundary_polygons { g.draw_polygon(boundary_color, p); } - for p in &self.fill_triangles { + for p in &self.fill_polygon.for_drawing() { g.draw_polygon(fill_color, p); } } pub fn contains_pt(&self, x: f64, y: f64) -> bool { - geometry::point_in_polygon(x, y, &self.fill_polygon) + self.fill_polygon.contains_pt(Pt2D::new(x, y)) } pub fn tooltip_lines(&self, map: &Map) -> Vec { @@ -76,10 +70,11 @@ impl DrawBuilding { } pub fn get_bbox(&self) -> Rect { - let mut polygons = vec![self.fill_polygon.clone()]; + let mut b = self.fill_polygon.get_bounds(); if let Some(line) = self.front_path { - polygons.push(vec![[line[0], line[1]], [line[2], line[3]]]); + b.update(line[0], line[1]); + b.update(line[2], line[3]); } - geometry::get_bbox_for_polygons(&polygons) + get_bbox(&b) } } diff --git a/editor/src/render/intersection.rs b/editor/src/render/intersection.rs index 60fc7b3f1f..e0456e0b4c 100644 --- a/editor/src/render/intersection.rs +++ b/editor/src/render/intersection.rs @@ -4,19 +4,19 @@ use aabb_quadtree::geom::Rect; use colors::{ColorScheme, Colors}; use dimensioned::si; use ezgui::GfxCtx; -use geom::{Line, Pt2D}; +use geom::{Line, Polygon, Pt2D}; use graphics; use graphics::math::Vec2d; use graphics::types::Color; use map_model; use map_model::geometry; -use render::DrawLane; +use render::{get_bbox, DrawLane}; use std::f64; #[derive(Debug)] pub struct DrawIntersection { pub id: map_model::IntersectionID, - pub polygon: Vec, + pub polygon: Polygon, crosswalks: Vec>, center: Pt2D, has_traffic_signal: bool, @@ -28,39 +28,37 @@ impl DrawIntersection { map: &map_model::Map, lanes: &Vec, ) -> DrawIntersection { - let mut pts: Vec = Vec::new(); + let mut pts: Vec = Vec::new(); for l in &inter.incoming_lanes { - let (pt1, pt2) = lanes[l.0].get_end_crossing(); - pts.push(pt1); - pts.push(pt2); + let line = lanes[l.0].get_end_crossing(); + pts.push(line.pt1()); + pts.push(line.pt2()); } for l in &inter.outgoing_lanes { - let (pt1, pt2) = lanes[l.0].get_start_crossing(); - pts.push(pt1); - pts.push(pt2); + let line = lanes[l.0].get_start_crossing(); + pts.push(line.pt1()); + pts.push(line.pt2()); } - let center = geometry::center(&pts.iter().map(|pt| Pt2D::new(pt[0], pt[1])).collect()); + let center = geometry::center(&pts); // Sort points by angle from the center - pts.sort_by_key(|pt| { - center - .angle_to(Pt2D::new(pt[0], pt[1])) - .normalized_degrees() as i64 - }); + pts.sort_by_key(|pt| center.angle_to(*pt).normalized_degrees() as i64); let first_pt = pts[0].clone(); pts.push(first_pt); DrawIntersection { center, id: inter.id, - polygon: pts, + polygon: Polygon::new(&pts), crosswalks: calculate_crosswalks(inter, map), has_traffic_signal: inter.has_traffic_signal, } } pub fn draw(&self, g: &mut GfxCtx, color: Color, cs: &ColorScheme) { - g.draw_polygon(color, &self.polygon); + for p in &self.polygon.for_drawing() { + g.draw_polygon(color, p); + } let crosswalk_marking = graphics::Line::new( cs.get(Colors::Crosswalk), @@ -84,11 +82,11 @@ impl DrawIntersection { } pub fn contains_pt(&self, x: f64, y: f64) -> bool { - geometry::point_in_polygon(x, y, &self.polygon) + self.polygon.contains_pt(Pt2D::new(x, y)) } pub fn get_bbox(&self) -> Rect { - geometry::get_bbox_for_polygons(&[self.polygon.clone()]) + get_bbox(&self.polygon.get_bounds()) } fn draw_stop_sign(&self, g: &mut GfxCtx, cs: &ColorScheme) { diff --git a/editor/src/render/lane.rs b/editor/src/render/lane.rs index 334ce1a055..03be7fa4f7 100644 --- a/editor/src/render/lane.rs +++ b/editor/src/render/lane.rs @@ -25,8 +25,8 @@ struct Marking { pub struct DrawLane { pub id: LaneID, pub polygons: Vec>, - start_crossing: (Vec2d, Vec2d), - end_crossing: (Vec2d, Vec2d), + start_crossing: Line, + end_crossing: Line, markings: Vec, // TODO pretty temporary @@ -36,9 +36,8 @@ pub struct DrawLane { impl DrawLane { pub fn new(lane: &map_model::Lane, map: &map_model::Map) -> DrawLane { let road = map.get_r(lane.parent); - let start = perp_line(lane.first_line(), geometry::LANE_THICKNESS); - let end = perp_line(lane.last_line().reverse(), geometry::LANE_THICKNESS); - + let start = new_perp_line(lane.first_line(), geometry::LANE_THICKNESS); + let end = new_perp_line(lane.last_line().reverse(), geometry::LANE_THICKNESS); let polygons = lane.lane_center_pts .make_polygons_blindly(geometry::LANE_THICKNESS); @@ -76,8 +75,8 @@ impl DrawLane { id: lane.id, polygons, markings, - start_crossing: ([start[0], start[1]], [start[2], start[3]]), - end_crossing: ([end[0], end[1]], [end[2], end[3]]), + start_crossing: start, + end_crossing: end, draw_id_at: calculate_id_positions(lane).unwrap_or(Vec::new()), } } @@ -161,12 +160,12 @@ impl DrawLane { } // Get the line marking the end of the lane, perpendicular to the direction of the lane - pub(crate) fn get_end_crossing(&self) -> (Vec2d, Vec2d) { - self.end_crossing + pub(crate) fn get_end_crossing(&self) -> &Line { + &self.end_crossing } - pub(crate) fn get_start_crossing(&self) -> (Vec2d, Vec2d) { - self.start_crossing + pub(crate) fn get_start_crossing(&self) -> &Line { + &self.start_crossing } } @@ -178,6 +177,12 @@ fn perp_line(l: Line, length: f64) -> [f64; 4] { [pt1.x(), pt1.y(), pt2.x(), pt2.y()] } +fn new_perp_line(l: Line, length: f64) -> Line { + let pt1 = l.shift(length / 2.0).pt1(); + let pt2 = l.reverse().shift(length / 2.0).pt2(); + Line::new(pt1, pt2) +} + fn calculate_sidewalk_lines(lane: &map_model::Lane) -> Marking { let tile_every = geometry::LANE_THICKNESS * si::M; diff --git a/editor/src/render/mod.rs b/editor/src/render/mod.rs index c4078c2342..98c2c76727 100644 --- a/editor/src/render/mod.rs +++ b/editor/src/render/mod.rs @@ -7,6 +7,8 @@ mod map; mod parcel; mod turn; +use aabb_quadtree::geom::{Point, Rect}; +use geom::Bounds; use map_model::geometry; pub use render::lane::DrawLane; pub use render::map::DrawMap; @@ -20,3 +22,16 @@ const TURN_ICON_ARROW_THICKNESS: f64 = geometry::BIG_ARROW_THICKNESS / 3.0; const BIG_ARROW_TIP_LENGTH: f64 = 1.0; const TURN_ICON_ARROW_TIP_LENGTH: f64 = BIG_ARROW_TIP_LENGTH * 0.8; const TURN_ICON_ARROW_LENGTH: f64 = 2.0; + +pub fn get_bbox(b: &Bounds) -> Rect { + Rect { + top_left: Point { + x: b.min_x as f32, + y: b.min_y as f32, + }, + bottom_right: Point { + x: b.max_x as f32, + y: b.max_y as f32, + }, + } +} diff --git a/editor/src/render/parcel.rs b/editor/src/render/parcel.rs index 602750c59a..690c2958d8 100644 --- a/editor/src/render/parcel.rs +++ b/editor/src/render/parcel.rs @@ -2,22 +2,18 @@ use aabb_quadtree::geom::Rect; use ezgui::GfxCtx; -use geom; -use geom::PolyLine; +use geom::{PolyLine, Polygon}; use graphics::math::Vec2d; use graphics::types::Color; use map_model; -use map_model::geometry; -use render::PARCEL_BOUNDARY_THICKNESS; +use render::{get_bbox, PARCEL_BOUNDARY_THICKNESS}; #[derive(Debug)] pub struct DrawParcel { pub id: map_model::ParcelID, // TODO should just have one. use graphics::Line for now. boundary_polygons: Vec>, - // TODO clean this up - pub fill_polygon: Vec, - fill_triangles: Vec>, + pub fill_polygon: Polygon, } impl DrawParcel { @@ -26,8 +22,7 @@ impl DrawParcel { id: p.id, boundary_polygons: PolyLine::new(p.points.clone()) .make_polygons_blindly(PARCEL_BOUNDARY_THICKNESS), - fill_polygon: p.points.iter().map(|pt| [pt.x(), pt.y()]).collect(), - fill_triangles: geom::triangulate(&p.points), + fill_polygon: Polygon::new(&p.points), } } @@ -35,7 +30,7 @@ impl DrawParcel { for p in &self.boundary_polygons { g.draw_polygon(boundary_color, p); } - for p in &self.fill_triangles { + for p in &self.fill_polygon.for_drawing() { g.draw_polygon(fill_color, p); } } @@ -43,6 +38,6 @@ impl DrawParcel { //pub fn contains_pt(&self, x: f64, y: f64) -> bool {} pub fn get_bbox(&self) -> Rect { - geometry::get_bbox_for_polygons(&vec![self.fill_polygon.clone()]) + get_bbox(&self.fill_polygon.get_bounds()) } } diff --git a/geom/src/lib.rs b/geom/src/lib.rs index 341e0a8472..4dac9773ac 100644 --- a/geom/src/lib.rs +++ b/geom/src/lib.rs @@ -19,7 +19,7 @@ pub use bounds::Bounds; use dimensioned::si; pub use gps::LonLat; pub use line::Line; -pub use polygon::triangulate; +pub use polygon::Polygon; pub use polyline::PolyLine; pub use pt::{HashablePt2D, Pt2D}; use std::marker; diff --git a/geom/src/polygon.rs b/geom/src/polygon.rs index 31babd0b00..304d4df20e 100644 --- a/geom/src/polygon.rs +++ b/geom/src/polygon.rs @@ -1,97 +1,144 @@ use graphics::math::Vec2d; -use Pt2D; +use {Bounds, Pt2D}; -// Adapted from https://crates.io/crates/polygon2; couldn't use the crate directly because it -// depends on nightly. +#[derive(Debug)] +pub struct Polygon { + // TODO urgh, just storing for geom validation. :P + pub pts: Vec, -pub fn triangulate(pts: &Vec) -> Vec> { - assert!(pts.len() >= 3); + // This could be stored more efficiently, but worry about it later when switching to gfx-rs. + triangles: Vec, +} - let mut tgs = Vec::new(); - let mut avl = Vec::with_capacity(pts.len()); - for i in 0..pts.len() { - avl.push(i); - } +impl Polygon { + // Adapted from https://crates.io/crates/polygon2; couldn't use the crate directly because it + // depends on nightly. + pub fn new(pts: &Vec) -> Polygon { + assert!(pts.len() >= 3); - let mut i = 0; - let mut al = pts.len(); - while al > 3 { - let i0 = avl[i % al]; - let i1 = avl[(i + 1) % al]; - let i2 = avl[(i + 2) % al]; + let mut tgs = Vec::new(); + let mut avl = Vec::with_capacity(pts.len()); + for i in 0..pts.len() { + avl.push(i); + } - let a = pts[i0]; - let b = pts[i1]; - let c = pts[i2]; + let mut i = 0; + let mut al = pts.len(); + while al > 3 { + let i0 = avl[i % al]; + let i1 = avl[(i + 1) % al]; + let i2 = avl[(i + 2) % al]; - let mut ear_found = false; - if is_triangle_convex(a, b, c) { - ear_found = true; + let a = pts[i0]; + let b = pts[i1]; + let c = pts[i2]; + let tri = Triangle::new(a, b, c); - for j in 0..al { - let vi = avl[j]; + let mut ear_found = false; + if tri.is_convex() { + ear_found = true; - if vi != i0 && vi != i1 && vi != i2 { - if point_in_triangle(pts[vi], a, b, c) { - ear_found = false; - break; + for j in 0..al { + let vi = avl[j]; + + if vi != i0 && vi != i1 && vi != i2 { + if tri.contains_pt(pts[vi]) { + ear_found = false; + break; + } } } } + + if ear_found { + tgs.push(i0); + tgs.push(i1); + tgs.push(i2); + avl.remove((i + 1) % al); + al -= 1; + i = 0; + } else if i > 3 * al { + break; + } else { + i += 1; + } } - if ear_found { - tgs.push(i0); - tgs.push(i1); - tgs.push(i2); - avl.remove((i + 1) % al); - al -= 1; - i = 0; - } else if i > 3 * al { - break; - } else { - i += 1; + tgs.push(avl[0]); + tgs.push(avl[1]); + tgs.push(avl[2]); + + let mut triangles = Vec::new(); + assert!(tgs.len() % 3 == 0); + for tri in tgs.chunks(3) { + triangles.push(Triangle::new(pts[tri[0]], pts[tri[1]], pts[tri[2]])); + } + Polygon { + pts: pts.clone(), + triangles, } } - tgs.push(avl[0]); - tgs.push(avl[1]); - tgs.push(avl[2]); - - let mut result = Vec::new(); - assert!(tgs.len() % 3 == 0); - for tri in tgs.chunks(3) { - result.push(vec![ - pts[tri[0]].to_vec(), - pts[tri[1]].to_vec(), - pts[tri[2]].to_vec(), - ]); + pub fn for_drawing(&self) -> Vec> { + self.triangles + .iter() + .map(|tri| vec![tri.pt1.to_vec(), tri.pt2.to_vec(), tri.pt3.to_vec()]) + .collect() } - result + pub fn contains_pt(&self, pt: Pt2D) -> bool { + self.triangles + .iter() + .find(|tri| tri.contains_pt(pt)) + .is_some() + } + + pub fn get_bounds(&self) -> Bounds { + let mut b = Bounds::new(); + for tri in &self.triangles { + b.update_pt(&tri.pt1); + b.update_pt(&tri.pt2); + b.update_pt(&tri.pt3); + } + b + } } -fn is_triangle_convex(a: Pt2D, b: Pt2D, c: Pt2D) -> bool { - ((a.y() - b.y()) * (c.x() - b.x()) + (b.x() - a.x()) * (c.y() - b.y())) >= 0.0 +#[derive(Debug)] +struct Triangle { + pt1: Pt2D, + pt2: Pt2D, + pt3: Pt2D, } -fn point_in_triangle(p: Pt2D, a: Pt2D, b: Pt2D, c: Pt2D) -> bool { - let v0x = c.x() - a.x(); - let v0y = c.y() - a.y(); - let v1x = b.x() - a.x(); - let v1y = b.y() - a.y(); - let v2x = p.x() - a.x(); - let v2y = p.y() - a.y(); +impl Triangle { + fn new(pt1: Pt2D, pt2: Pt2D, pt3: Pt2D) -> Triangle { + Triangle { pt1, pt2, pt3 } + } - let dot00 = v0x * v0x + v0y * v0y; - let dot01 = v0x * v1x + v0y * v1y; - let dot02 = v0x * v2x + v0y * v2y; - let dot11 = v1x * v1x + v1y * v1y; - let dot12 = v1x * v2x + v1y * v2y; + fn is_convex(&self) -> bool { + ((self.pt1.y() - self.pt2.y()) * (self.pt3.x() - self.pt2.x()) + + (self.pt2.x() - self.pt1.x()) * (self.pt3.y() - self.pt2.y())) >= 0.0 + } - let denom = dot00 * dot11 - dot01 * dot01; - let u = (dot11 * dot02 - dot01 * dot12) / denom; - let v = (dot00 * dot12 - dot01 * dot02) / denom; + fn contains_pt(&self, pt: Pt2D) -> bool { + let v0x = self.pt3.x() - self.pt1.x(); + let v0y = self.pt3.y() - self.pt1.y(); + let v1x = self.pt2.x() - self.pt1.x(); + let v1y = self.pt2.y() - self.pt1.y(); + let v2x = pt.x() - self.pt1.x(); + let v2y = pt.y() - self.pt1.y(); - (u >= 0.0) && (v >= 0.0) && (u + v < 1.0) + let dot00 = v0x * v0x + v0y * v0y; + let dot01 = v0x * v1x + v0y * v1y; + let dot02 = v0x * v2x + v0y * v2y; + let dot11 = v1x * v1x + v1y * v1y; + let dot12 = v1x * v2x + v1y * v2y; + + let denom = dot00 * dot11 - dot01 * dot01; + let u = (dot11 * dot02 - dot01 * dot12) / denom; + let v = (dot00 * dot12 - dot01 * dot02) / denom; + + (u >= 0.0) && (v >= 0.0) && (u + v < 1.0) + } }