Skip to content

Commit

Permalink
Define Tessellation as an alternative to Polygon, and use it for rend…
Browse files Browse the repository at this point in the history
…ering. #951

The goal is for every Polygon to consist of valid Rings, for a simpler
API and better georust integration.

Thanks to @michaelkirk for starting this!
  • Loading branch information
dabreegster committed Aug 17, 2022
1 parent e9ed359 commit 1be8072
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 67 deletions.
4 changes: 2 additions & 2 deletions apps/game/src/sandbox/dashboards/trip_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,10 @@ impl State<App> for TripTable {
self.panel.draw(g);
let mut batch = GeomBatch::new();
if self.panel.has_widget("starts_in") {
if let Some(p) = self.panel.clone_stashed("starts_in") {
if let Some(p) = self.panel.clone_stashed::<Option<Polygon>>("starts_in") {
batch.push(Color::RED.alpha(0.5), p);
}
if let Some(p) = self.panel.clone_stashed("ends_in") {
if let Some(p) = self.panel.clone_stashed::<Option<Polygon>>("ends_in") {
batch.push(Color::BLUE.alpha(0.5), p);
}
}
Expand Down
4 changes: 3 additions & 1 deletion geom/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ pub use crate::find_closest::FindClosest;
pub use crate::gps::LonLat;
pub use crate::line::{InfiniteLine, Line};
pub use crate::percent::Percent;
pub use crate::polygon::{Polygon, Triangle};
pub use crate::polygon::Polygon;
pub use crate::polyline::{ArrowCap, PolyLine};
pub use crate::pt::{HashablePt2D, Pt2D};
pub use crate::ring::Ring;
pub use crate::speed::Speed;
pub use crate::stats::{HgramValue, Histogram, Statistic};
pub use crate::tessellation::{Tessellation, Triangle};
pub use crate::time::Time;

mod angle;
Expand All @@ -38,6 +39,7 @@ mod pt;
mod ring;
mod speed;
mod stats;
mod tessellation;
mod time;

// About 0.4 inches... which is quite tiny on the scale of things. :)
Expand Down
26 changes: 4 additions & 22 deletions geom/src/polygon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ use abstutil::Tags;

use crate::{
Angle, Bounds, CornerRadii, Distance, GPSBounds, HashablePt2D, LonLat, PolyLine, Pt2D, Ring,
Tessellation, Triangle,
};

#[derive(PartialEq, Serialize, Deserialize, Clone, Debug)]
pub struct Polygon {
points: Vec<Pt2D>,
pub(crate) points: Vec<Pt2D>,
/// Groups of three indices make up the triangles
indices: Vec<u16>,
pub(crate) indices: Vec<u16>,

/// If the polygon has holes, explicitly store all the rings (the one outer and all of the
/// inner) so they can later be used to generate outlines and such. If the polygon has no
Expand Down Expand Up @@ -103,19 +104,7 @@ impl Polygon {
}

pub fn triangles(&self) -> Vec<Triangle> {
let mut triangles: Vec<Triangle> = Vec::new();
for slice in self.indices.chunks_exact(3) {
triangles.push(Triangle {
pt1: self.points[slice[0] as usize],
pt2: self.points[slice[1] as usize],
pt3: self.points[slice[2] as usize],
});
}
triangles
}

pub fn raw_for_rendering(&self) -> (&Vec<Pt2D>, &Vec<u16>) {
(&self.points, &self.indices)
Tessellation::from(self.clone()).triangles()
}

/// Does this polygon contain the point in its interior?
Expand Down Expand Up @@ -635,13 +624,6 @@ impl fmt::Display for Polygon {
}
}

#[derive(Clone, Debug)]
pub struct Triangle {
pub pt1: Pt2D,
pub pt2: Pt2D,
pub pt3: Pt2D,
}

// Note that this could crash on invalid rings
impl From<geo::Polygon> for Polygon {
fn from(poly: geo::Polygon) -> Self {
Expand Down
117 changes: 117 additions & 0 deletions geom/src/tessellation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use crate::{Angle, Bounds, Polygon, Pt2D};

// Deliberately not serializable
/// A tessellated polygon, ready for rendering.
#[derive(Clone)]
pub struct Tessellation {
/// These points aren't in any meaningful order. It's not generally possible to reconstruct a
/// `Polygon` from this.
points: Vec<Pt2D>,
/// Groups of three indices make up the triangles
indices: Vec<u16>,
}

#[derive(Clone, Debug)]
pub struct Triangle {
pub pt1: Pt2D,
pub pt2: Pt2D,
pub pt3: Pt2D,
}

impl From<Polygon> for Tessellation {
fn from(polygon: Polygon) -> Self {
Self::new(polygon.points, polygon.indices)
}
}

impl Tessellation {
pub fn new(points: Vec<Pt2D>, indices: Vec<u16>) -> Self {
Tessellation { points, indices }
}

/// Returns (points, indices) for rendering
pub fn consume(self) -> (Vec<Pt2D>, Vec<u16>) {
(self.points, self.indices)
}

pub fn triangles(&self) -> Vec<Triangle> {
let mut triangles: Vec<Triangle> = Vec::new();
for slice in self.indices.chunks_exact(3) {
triangles.push(Triangle {
pt1: self.points[slice[0] as usize],
pt2: self.points[slice[1] as usize],
pt3: self.points[slice[2] as usize],
});
}
triangles
}

pub fn get_bounds(&self) -> Bounds {
Bounds::from(&self.points)
}

fn center(&self) -> Pt2D {
self.get_bounds().center()
}

fn transform<F: Fn(&Pt2D) -> Pt2D>(&mut self, f: F) {
for pt in &mut self.points {
*pt = f(pt);
}
}

pub fn translate(&mut self, dx: f64, dy: f64) {
self.transform(|pt| pt.offset(dx, dy));
}

pub fn scale(&mut self, factor: f64) {
self.transform(|pt| Pt2D::new(pt.x() * factor, pt.y() * factor));
}

pub fn scale_xy(&mut self, x_factor: f64, y_factor: f64) {
self.transform(|pt| Pt2D::new(pt.x() * x_factor, pt.y() * y_factor))
}

pub fn rotate(&mut self, angle: Angle) {
self.rotate_around(angle, self.center())
}

pub fn rotate_around(&mut self, angle: Angle, pivot: Pt2D) {
self.transform(|pt| {
let origin_pt = Pt2D::new(pt.x() - pivot.x(), pt.y() - pivot.y());
let (sin, cos) = angle.normalized_radians().sin_cos();
Pt2D::new(
pivot.x() + origin_pt.x() * cos - origin_pt.y() * sin,
pivot.y() + origin_pt.y() * cos + origin_pt.x() * sin,
)
})
}

/// Equivalent to `self.scale(scale).translate(translate_x, translate_y).rotate_around(rotate,
/// pivot)`, but modifies the polygon in-place and is faster.
pub fn inplace_multi_transform(
&mut self,
scale: f64,
translate_x: f64,
translate_y: f64,
rotate: Angle,
pivot: Pt2D,
) {
let (sin, cos) = rotate.normalized_radians().sin_cos();

for pt in &mut self.points {
// Scale
let x = scale * pt.x();
let y = scale * pt.y();
// Translate
let x = x + translate_x;
let y = y + translate_y;
// Rotate
let origin_pt = Pt2D::new(x - pivot.x(), y - pivot.y());
*pt = Pt2D::new(
pivot.x() + origin_pt.x() * cos - origin_pt.y() * sin,
pivot.y() + origin_pt.y() * cos + origin_pt.x() * sin,
);
}
}
}
14 changes: 7 additions & 7 deletions map_gui/src/render/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
use aabb_quadtree::QuadTree;

use abstutil::Timer;
use geom::{Bounds, Distance, Polygon};
use geom::{Bounds, Distance, Tessellation};
use map_model::{
AreaID, BuildingID, IntersectionID, LaneID, Map, ParkingLotID, Road, RoadID, TransitStopID,
};
Expand Down Expand Up @@ -224,7 +224,7 @@ impl DrawMap {
// We want the outlines slightly above the equivalent layer. z-order is an isize, and f64
// makes sort_by_key annoying, so just multiply the existing z-orders by 10.
let outline_z_offset = 5;
let mut unzoomed_pieces: Vec<(isize, Fill, Polygon)> = Vec::new();
let mut unzoomed_pieces: Vec<(isize, Fill, Tessellation)> = Vec::new();

for r in map.all_roads() {
let width = r.get_width();
Expand All @@ -240,7 +240,7 @@ impl DrawMap {
} else {
cs.unzoomed_road_surface(r.get_rank())
}),
r.center_pts.make_polygons(width),
r.center_pts.make_polygons(width).into(),
));

if cs.road_outlines {
Expand All @@ -261,14 +261,14 @@ impl DrawMap {
unzoomed_pieces.push((
10 * r.zorder + outline_z_offset,
outline_color.into(),
p,
p.into(),
));
}
} else {
unzoomed_pieces.push((
10 * r.zorder + outline_z_offset,
outline_color.into(),
pl.make_polygons(outline_thickness),
pl.make_polygons(outline_thickness).into(),
));
}
}
Expand Down Expand Up @@ -300,14 +300,14 @@ impl DrawMap {
} else {
cs.unzoomed_interesting_intersection
};
unzoomed_pieces.push((zorder, intersection_color.into(), i.polygon.clone()));
unzoomed_pieces.push((zorder, intersection_color.into(), i.polygon.clone().into()));

if cs.road_outlines {
for pl in DrawIntersection::get_unzoomed_outline(i, map) {
unzoomed_pieces.push((
zorder + outline_z_offset,
outline_color.into(),
pl.make_polygons(outline_thickness),
pl.make_polygons(outline_thickness).into(),
));
}
}
Expand Down
6 changes: 3 additions & 3 deletions widgetry/src/backend_glow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,9 @@ impl PrerenderInnards {

for (color, poly, z) in batch.consume() {
let idx_offset = vertices.len() as u32;
let (pts, raw_indices) = poly.raw_for_rendering();
let (pts, raw_indices) = poly.consume();
for pt in pts {
let style = color.shader_style(*pt);
let style = color.shader_style(pt);
vertices.push([
pt.x() as f32,
pt.y() as f32,
Expand All @@ -307,7 +307,7 @@ impl PrerenderInnards {
]);
}
for idx in raw_indices {
indices.push(idx_offset + (*idx as u32));
indices.push(idx_offset + (idx as u32));
}
}

Expand Down
Loading

0 comments on commit 1be8072

Please sign in to comment.