mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 03:35:51 +03:00
fix building overlap for isometric view (mostly) (#338)
This commit is contained in:
parent
e1de43b75c
commit
8042ad414f
@ -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)) {
|
||||
|
@ -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
|
||||
];
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user