fix building overlap for isometric view (mostly) (#338)

This commit is contained in:
Michael Kirk 2020-09-21 11:54:39 -07:00 committed by GitHub
parent e1de43b75c
commit 8042ad414f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 116 additions and 31 deletions

View File

@ -74,8 +74,57 @@ impl DrawBuilding {
};
// TODO For now, blindly guess the building height
let max_height = 15.0;
let mut rng = XorShiftRng::seed_from_u64(bldg.id.0 as u64);
let height = Distance::meters(rng.gen_range(1.0, 15.0));
let height = Distance::meters(rng.gen_range(1.0, max_height));
let map_bounds = map.get_gps_bounds().to_bounds();
let (map_width, map_height) = (map_bounds.width(), map_bounds.height());
let map_length = map_width.hypot(map_height);
let distance = |pt: &Pt2D| {
// some normalization so we can compute the distance to the corner of the
// screen from which the orthographic projection is based.
let projection_origin = match x {
CameraAngle::IsometricNE => Pt2D::new(0.0, map_height),
CameraAngle::IsometricNW => Pt2D::new(map_width, map_height),
CameraAngle::IsometricSE => Pt2D::new(0.0, 0.0),
CameraAngle::IsometricSW => Pt2D::new(map_width, 0.0),
CameraAngle::TopDown => unreachable!(),
};
let abs_pt = Pt2D::new(
(pt.x() - projection_origin.x()).abs(),
(pt.y() - projection_origin.y()).abs(),
);
let a = f64::hypot(abs_pt.x(), abs_pt.y());
let theta = f64::atan(abs_pt.y() / abs_pt.x());
let distance = a * f64::sin(theta + std::f64::consts::PI / 4.0);
Distance::meters(distance)
};
// Things closer to the isometric axis should appear in front of things farther
// away, so we give them a higher z-index.
//
// Naively, we compute the entire building's distance as the distance from it's
// closest point. This is simple and usually works, but will likely fail on more
// complex building arrangements, e.g. if a building were tightly encircled by a
// large building.
let closest_pt = bldg
.polygon
.points()
.into_iter()
.min_by(|a, b| distance(a).cmp(&distance(b)));
let distance_from_projection_axis = closest_pt
.map(|pt| distance(pt).inner_meters())
.unwrap_or(0.0);
// smaller z renders above larger
let scale_factor = map_length + max_height;
let groundfloor_z = (distance_from_projection_axis) / scale_factor - 1.0;
let roof_z = groundfloor_z - height.inner_meters() / scale_factor;
// TODO Some buildings have holes in them
if let Ok(roof) = Ring::new(
@ -89,13 +138,19 @@ impl DrawBuilding {
bldg_batch.push(Color::BLACK, p);
}
// In actuality, the z of the walls should start at groundfloor_z and end at
// roof_z, but since we aren't dealing with actual 3d geometries, we have to
// pick one value. Anecdotally, picking a value between the two seems to
// usually looks right, but probably breaks down in certain overlap scenarios.
let wall_z = (groundfloor_z + roof_z) / 2.0;
let mut wall_beams = Vec::new();
for (low, high) in bldg.polygon.points().iter().zip(roof.points().iter()) {
wall_beams.push(Line::must_new(*low, *high));
}
let wall_color = Color::hex("#BBBEC3");
for (wall1, wall2) in wall_beams.iter().zip(wall_beams.iter().skip(1)) {
bldg_batch.push(
bldg_batch.push_with_z(
wall_color,
Ring::must_new(vec![
wall1.pt1(),
@ -105,14 +160,23 @@ impl DrawBuilding {
wall1.pt1(),
])
.to_polygon(),
wall_z,
);
}
for wall in wall_beams {
bldg_batch.push(Color::BLACK, wall.make_polygons(Distance::meters(0.1)));
bldg_batch.push_with_z(
Color::BLACK,
wall.make_polygons(Distance::meters(0.1)),
wall_z,
);
}
bldg_batch.push(bldg_color, roof.clone().to_polygon());
bldg_batch.push(Color::BLACK, roof.to_outline(Distance::meters(0.3)));
bldg_batch.push_with_z(bldg_color, roof.clone().to_polygon(), roof_z);
bldg_batch.push_with_z(
Color::BLACK,
roof.to_outline(Distance::meters(0.3)),
roof_z,
);
} else {
bldg_batch.push(bldg_color, bldg.polygon.clone());
if let Ok(p) = bldg.polygon.to_outline(Distance::meters(0.1)) {

View File

@ -213,10 +213,10 @@ impl PrerenderInnards {
}
pub fn actually_upload(&self, permanent: bool, batch: GeomBatch) -> Drawable {
let mut vertices: Vec<[f32; 7]> = Vec::new();
let mut vertices: Vec<[f32; 8]> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
for (color, poly) in batch.consume() {
for (color, poly, z) in batch.consume() {
let idx_offset = vertices.len() as u32;
let (pts, raw_indices) = poly.raw_for_rendering();
for pt in pts {
@ -224,6 +224,7 @@ impl PrerenderInnards {
vertices.push([
pt.x() as f32,
pt.y() as f32,
z as f32,
style[0],
style[1],
style[2],
@ -261,7 +262,7 @@ impl PrerenderInnards {
);
let vertex_attributes: [i32; 3] = [
2, // position is vec2
3, // position is vec2
4, // color is vec4
1, // texture_id is float
];

View File

@ -6,10 +6,15 @@ use crate::{
use geom::{Bounds, Polygon, Pt2D};
use std::cell::{Cell, RefCell};
// Lower is more on top
const MAPSPACE_Z: f32 = 1.0;
const SCREENSPACE_Z: f32 = 0.5;
const TOOLTIP_Z: f32 = 0.0;
// We organize major layers of the app with whole number z values, with lower values being more on
// top.
//
// Within each layer, we must only adjust the z-offset of individual polygons within (-1, 0] to
// avoid traversing layers.
pub(crate) const MAPSPACE_Z: f32 = 1.0;
pub(crate) const SCREENSPACE_Z: f32 = 0.0;
pub(crate) const MENU_Z: f32 = -1.0;
pub(crate) const TOOLTIP_Z: f32 = -2.0;
#[derive(Debug)]
pub struct Uniforms {

View File

@ -7,7 +7,9 @@ use geom::{Angle, Bounds, Polygon, Pt2D};
/// A mutable builder for a group of colored polygons.
#[derive(Clone)]
pub struct GeomBatch {
pub(crate) list: Vec<(Fill, Polygon)>,
// f64 is the z-value offset. This must be in (-1, 0], with values closer to -1.0
// rendering above values closer to 0.0.
pub(crate) list: Vec<(Fill, Polygon, f64)>,
pub autocrop_dims: bool,
}
@ -22,14 +24,23 @@ impl GeomBatch {
// Adds a single polygon, painted according to `Fill`
pub fn push<F: Into<Fill>>(&mut self, fill: F, p: Polygon) {
self.list.push((fill.into(), p));
self.push_with_z(fill, p, 0.0);
}
/// Offset z value to render above/below other polygons.
/// z must be in (-1, 0] to ensure we don't traverse layers of the UI - to make
/// sure we don't inadvertently render something *above* a tooltip, etc.
pub fn push_with_z<F: Into<Fill>>(&mut self, fill: F, p: Polygon, z_offset: f64) {
debug_assert!(z_offset > -1.0);
debug_assert!(z_offset <= 0.0);
self.list.push((fill.into(), p, z_offset));
}
/// Applies one Fill to many polygons.
pub fn extend<F: Into<Fill>>(&mut self, fill: F, polys: Vec<Polygon>) {
let fill = fill.into();
for p in polys {
self.list.push((fill.clone(), p));
self.list.push((fill.clone(), p, 0.0));
}
}
@ -39,7 +50,7 @@ impl GeomBatch {
}
/// Returns the colored polygons in this batch, destroying the batch.
pub fn consume(self) -> Vec<(Fill, Polygon)> {
pub fn consume(self) -> Vec<(Fill, Polygon, f64)> {
self.list
}
@ -72,7 +83,7 @@ impl GeomBatch {
/// Compute the bounds of all polygons in this batch.
pub fn get_bounds(&self) -> Bounds {
let mut bounds = Bounds::new();
for (_, poly) in &self.list {
for (_, poly, _) in &self.list {
bounds.union(poly.get_bounds());
}
if !self.autocrop_dims {
@ -87,7 +98,7 @@ impl GeomBatch {
if bounds.min_x == 0.0 && bounds.min_y == 0.0 {
return self;
}
for (_, poly) in &mut self.list {
for (_, poly, _) in &mut self.list {
*poly = poly.translate(-bounds.min_x, -bounds.min_y);
}
self
@ -96,7 +107,7 @@ impl GeomBatch {
/// Builds a single polygon covering everything in this batch. Use to create a hitbox.
pub fn unioned_polygon(&self) -> Polygon {
let mut result = self.list[0].1.clone();
for (_, p) in &self.list[1..] {
for (_, p, _) in &self.list[1..] {
result = result.union(p.clone());
}
result
@ -133,7 +144,7 @@ impl GeomBatch {
/// Transforms all colors in a batch.
pub fn color(mut self, transformation: RewriteColor) -> GeomBatch {
for (fancy, _) in &mut self.list {
for (fancy, _, _) in &mut self.list {
if let Fill::Color(ref mut c) = fancy {
*c = transformation.apply(*c);
}
@ -151,7 +162,7 @@ impl GeomBatch {
/// Translates the batch by some offset.
pub fn translate(mut self, dx: f64, dy: f64) -> GeomBatch {
for (_, poly) in &mut self.list {
for (_, poly, _) in &mut self.list {
*poly = poly.translate(dx, dy);
}
self
@ -159,7 +170,7 @@ impl GeomBatch {
/// Rotates each polygon in the batch relative to the center of that polygon.
pub fn rotate(mut self, angle: Angle) -> GeomBatch {
for (_, poly) in &mut self.list {
for (_, poly, _) in &mut self.list {
*poly = poly.rotate(angle);
}
self
@ -168,7 +179,7 @@ impl GeomBatch {
/// Rotates each polygon in the batch relative to the center of the entire batch.
pub fn rotate_around_batch_center(mut self, angle: Angle) -> GeomBatch {
let center = self.get_bounds().center();
for (_, poly) in &mut self.list {
for (_, poly, _) in &mut self.list {
*poly = poly.rotate_around(angle, center);
}
self
@ -179,7 +190,7 @@ impl GeomBatch {
if factor == 1.0 {
return self;
}
for (_, poly) in &mut self.list {
for (_, poly, _) in &mut self.list {
// strip_rings first -- sometimes when scaling down, the original rings collapse. Since
// this polygon is part of a GeomBatch anyway, not calling to_outline on it.
*poly = poly.strip_rings().scale(factor);
@ -192,7 +203,7 @@ impl<F: Into<Fill>> From<Vec<(F, Polygon)>> for GeomBatch {
/// Creates a batch of filled polygons.
fn from(list: Vec<(F, Polygon)>) -> GeomBatch {
GeomBatch {
list: list.into_iter().map(|(c, p)| (c.into(), p)).collect(),
list: list.into_iter().map(|(c, p)| (c.into(), p, 0.0)).collect(),
autocrop_dims: true,
}
}

View File

@ -7,7 +7,7 @@ uniform vec3 window;
// textures grid
uniform sampler2DArray textures;
layout (location = 0) in vec2 position;
layout (location = 0) in vec3 position;
layout (location = 1) in vec4 color;
layout (location = 2) in float texture_index;
@ -25,9 +25,10 @@ void main() {
// Translate that to normalized device coordinates (NDC)
float x = (screen_x / window[0] * 2.0) - 1.0;
float y = (screen_y / window[1] * 2.0) - 1.0;
float z = position[2] + window[2];
// Note the y inversion
gl_Position = vec4(x, -y, window[2], 1.0);
gl_Position = vec4(x, -y, z, 1.0);
// An arbitrary factor to scale the textures we're using.
//

View File

@ -146,8 +146,7 @@ impl<T: 'static + Clone> WidgetImpl for Dropdown<T> {
Pt2D::new(0.0, 0.0),
ScreenPt::new(m.top_left.x - pad, m.top_left.y - pad),
1.0,
// Between SCREENSPACE_Z and TOOLTIP_Z
Some(0.1),
Some(crate::drawing::MENU_Z),
);
g.redraw(&draw_bg);
g.unfork();

View File

@ -160,8 +160,12 @@ impl<T: 'static> WidgetImpl for Menu<T> {
}
let draw = g.upload(self.calculate_txt(g.style()).render_g(g));
// In between tooltip and normal screenspace
g.fork(Pt2D::new(0.0, 0.0), self.top_left, 1.0, Some(0.1));
g.fork(
Pt2D::new(0.0, 0.0),
self.top_left,
1.0,
Some(crate::drawing::MENU_Z),
);
g.redraw(&draw);
g.unfork();