From 69f88d33c0f1cc018fa8192f8cc77ce964fd4cb5 Mon Sep 17 00:00:00 2001 From: Paris DOUADY Date: Thu, 4 Jan 2024 11:56:37 +0100 Subject: [PATCH] add heightmap override system --- .github/workflows/rust-build.yml | 2 +- Cargo.lock | 1 + engine/src/drawables/terrain.rs | 14 +- engine_demo/src/terrain.rs | 4 +- geom/src/aabb.rs | 22 +++ geom/src/aabb3.rs | 8 + geom/src/circle.rs | 6 + geom/src/heightmap.rs | 181 ++++++++++++++---- geom/src/polyline3.rs | 27 ++- geom/src/v2.rs | 8 + .../src/rendering/map_rendering/terrain.rs | 6 +- simulation/Cargo.toml | 1 + simulation/src/init.rs | 1 + simulation/src/map/change_detection.rs | 14 +- simulation/src/map/height_override.rs | 147 ++++++++++++++ simulation/src/map/map.rs | 57 +++++- simulation/src/map/mod.rs | 1 + simulation/src/map/serializing.rs | 2 +- simulation/src/map/terrain.rs | 17 +- 19 files changed, 455 insertions(+), 64 deletions(-) create mode 100644 simulation/src/map/height_override.rs diff --git a/.github/workflows/rust-build.yml b/.github/workflows/rust-build.yml index 098a2fe4a..3ab61164b 100644 --- a/.github/workflows/rust-build.yml +++ b/.github/workflows/rust-build.yml @@ -3,7 +3,7 @@ name: rust-build on: push: branches: - - master + - '*' tags-ignore: - v* # don't run on tags since release does that pull_request: diff --git a/Cargo.lock b/Cargo.lock index 6828ab9f5..9993debc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2802,6 +2802,7 @@ name = "simulation" version = "0.1.0" dependencies = [ "arc-swap", + "bitflags 2.4.1", "common", "derive_more", "easybench", diff --git a/engine/src/drawables/terrain.rs b/engine/src/drawables/terrain.rs index 1a7f1d4a2..2ee684bbd 100644 --- a/engine/src/drawables/terrain.rs +++ b/engine/src/drawables/terrain.rs @@ -7,7 +7,7 @@ use wgpu::{ VertexAttribute, VertexBufferLayout, }; -use geom::{vec2, vec3, Camera, Intersect3, Matrix4, Vec2, AABB3}; +use geom::{vec2, vec3, Camera, HeightmapChunk, Intersect3, Matrix4, Vec2, AABB3}; use crate::{ bg_layout_litmesh, pbuffer::PBuffer, CompiledModule, Drawable, FrameContext, GfxContext, @@ -26,7 +26,7 @@ pub struct TerrainChunk { /// CSIZE is the size of a chunk in meters /// CRESOLUTION is the resolution of a chunk, in vertices, at the chunk data level (not LOD0 since we upsample) -pub struct TerrainRender { +pub struct TerrainRender { terrain_tex: Arc, normal_tex: Arc, @@ -48,7 +48,7 @@ pub struct TerrainPrepared { instances: [(PBuffer, u32); LOD], } -impl TerrainRender { +impl TerrainRender { const LOD0_RESOLUTION: usize = CRESOLUTION * (1 << UPSCALE_LOD); pub fn new(gfx: &mut GfxContext, w: u32, h: u32) -> Self { @@ -161,7 +161,7 @@ impl TerrainRender, ) { fn pack(height: f32) -> [u8; 2] { let h_encoded = ((height.clamp(MIN_HEIGHT, MAX_HEIGHT) - MIN_HEIGHT) @@ -172,9 +172,9 @@ impl TerrainRender AABB { + let v = AABB { + ll: self.ll.max(other.ll), + ur: self.ur.min(other.ur), + }; + if v.ll.x > v.ur.x || v.ll.y > v.ur.y { + AABB::zero() + } else { + v + } + } + + #[inline] + pub fn offset(self, offset: Vec2) -> AABB { + AABB { + ll: self.ll + offset, + ur: self.ur + offset, + } + } + + #[inline] pub fn size(&self) -> Vec2 { self.ur - self.ll } diff --git a/geom/src/aabb3.rs b/geom/src/aabb3.rs index 35413072d..01121b74e 100644 --- a/geom/src/aabb3.rs +++ b/geom/src/aabb3.rs @@ -49,6 +49,14 @@ impl AABB3 { } } + #[inline] + pub fn flatten(self) -> AABB { + AABB { + ll: self.ll.xy(), + ur: self.ur.xy(), + } + } + #[inline] pub fn w(&self) -> f32 { self.ur.x - self.ll.x diff --git a/geom/src/circle.rs b/geom/src/circle.rs index ffc187c68..c1344cc32 100644 --- a/geom/src/circle.rs +++ b/geom/src/circle.rs @@ -7,6 +7,12 @@ pub struct Circle { pub radius: f32, } +impl Circle { + pub fn contains(&self, p: Vec2) -> bool { + self.center.is_close(p, self.radius) + } +} + impl Circle { #[inline] pub fn new(center: Vec2, radius: f32) -> Self { diff --git a/geom/src/heightmap.rs b/geom/src/heightmap.rs index 573183305..851398360 100644 --- a/geom/src/heightmap.rs +++ b/geom/src/heightmap.rs @@ -1,6 +1,7 @@ use crate::{vec2, vec3, Ray3, Vec2, Vec3, AABB, AABB3}; +use serde::de::{SeqAccess, Visitor}; use serde::ser::SerializeSeq; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, Serializer}; pub type HeightmapChunkID = (u16, u16); @@ -8,9 +9,13 @@ const MAX_HEIGHT_DIFF: f32 = 2048.0; const MIN_HEIGHT: f32 = -40.0007; const MAX_HEIGHT: f32 = MAX_HEIGHT_DIFF - MIN_HEIGHT; +/// Special value for heights_override to indicate that the height is not overridden +pub const NO_OVERRIDE: f32 = -40.0; + #[derive(Clone)] pub struct HeightmapChunk { heights: [[f32; RESOLUTION]; RESOLUTION], // TODO: change to RESOLUTION * RESOLUTION when generic_const_exprs is stabilized + heights_override: [[f32; RESOLUTION]; RESOLUTION], max_height: f32, } @@ -18,6 +23,7 @@ impl Default for HeightmapChunk Self { Self { heights: [[0.0; RESOLUTION]; RESOLUTION], + heights_override: [[NO_OVERRIDE; RESOLUTION]; RESOLUTION], max_height: 0.0, } } @@ -25,17 +31,13 @@ impl Default for HeightmapChunk HeightmapChunk { pub fn new(heights: [[f32; RESOLUTION]; RESOLUTION]) -> Self { - let mut max_height = heights[0][0]; - for row in &heights { - for height in row { - max_height = max_height.max(*height); - } - } - - Self { + let mut me = Self { heights, - max_height, - } + heights_override: [[NO_OVERRIDE; RESOLUTION]; RESOLUTION], + max_height: 0.0, + }; + me.update_max_height(); + me } #[inline] @@ -66,19 +68,42 @@ impl HeightmapChunk ) } + fn update_max_height(&mut self) { + for y in 0..RESOLUTION { + for x in 0..RESOLUTION { + let h = unsafe { self.height_idx(x, y).unwrap_unchecked() }; + self.max_height = self.max_height.max(h); + } + } + } + /// assume p is in chunk-space and in-bounds #[inline] pub fn height_unchecked(&self, p: Vec2) -> f32 { let v = p / SIZE as f32; let v = v * RESOLUTION as f32; - self.heights[v.y as usize][v.x as usize] + self.height_idx(v.x as usize, v.y as usize).unwrap() } #[inline] pub fn height(&self, p: Vec2) -> Option { let v = p / SIZE as f32; let v = v * RESOLUTION as f32; - self.heights.get(v.y as usize)?.get(v.x as usize).copied() + self.height_idx(v.x as usize, v.y as usize) + } + + pub fn height_idx(&self, x: usize, y: usize) -> Option { + let height_override = *self.heights_override.get(y)?.get(x)?; + let h = unsafe { *self.heights.get_unchecked(y).get_unchecked(x) }; + if height_override != NO_OVERRIDE && height_override < h { + return Some(height_override); + } + Some(h) + } + + /// Always returns the normal heightmap height + fn height_idx_mut(&mut self, x: usize, y: usize) -> Option<&mut f32> { + self.heights.get_mut(y)?.get_mut(x) } #[inline] @@ -141,6 +166,19 @@ impl Heightmap { unsafe { Some(self.chunks.get_unchecked((id.0 + id.1 * self.w) as usize)) } } + pub fn set_override( + &mut self, + id: HeightmapChunkID, + override_heights: [[f32; RESOLUTION]; RESOLUTION], + ) { + if !self.check_valid(id) { + return; + } + let chunk = self.get_chunk_mut(id).unwrap(); + chunk.heights_override = override_heights; + chunk.update_max_height(); + } + fn get_chunk_mut( &mut self, id: HeightmapChunkID, @@ -261,23 +299,26 @@ impl Heightmap { .map(move |(i, chunk)| ((i as u16 % self.w, i as u16 / self.w), chunk)) } + pub fn covered_chunks(&self, bounds: AABB) -> impl Iterator { + let ll = bounds.ll / SIZE as f32; + let ur = bounds.ur / SIZE as f32; + let ll = vec2(ll.x.floor(), ll.y.floor()).max(Vec2::ZERO); + let ur = vec2(ur.x.ceil(), ur.y.ceil()).min(vec2(self.w as f32, self.h as f32)); + + (ll.y as u16..ur.y as u16) + .flat_map(move |y| (ll.x as u16..ur.x as u16).map(move |x| (x, y))) + } + + /// Returns height at any point using the cell to the bottom left of the point pub fn height_nearest(&self, p: Vec2) -> Option { let cell = HeightmapChunk::::id(p); - self.get_chunk(cell).and_then(|chunk| { - let v = p / SIZE as f32 - vec2(cell.0 as f32, cell.1 as f32); - let v = v * RESOLUTION as f32; - chunk - .heights - .get(v.y as usize) - .and_then(|x| x.get(v.x as usize)) - .copied() - }) + self.get_chunk(cell) + .and_then(|chunk| chunk.height(p - vec2(cell.0 as f32, cell.1 as f32) * SIZE as f32)) } /// get height by actual cell position - #[inline] - pub fn height_idx(&self, x: usize, y: usize) -> Option { + fn height_idx(&self, x: usize, y: usize) -> Option { let chunkx = x / RESOLUTION; let chunky = y / RESOLUTION; @@ -285,10 +326,13 @@ impl Heightmap { let celly = y % RESOLUTION; let chunk = self.get_chunk((chunkx as u16, chunky as u16))?; - Some(chunk.heights[celly][cellx]) + + // Safety: modulo RESOLUTION + unsafe { Some(chunk.height_idx(cellx, celly).unwrap_unchecked()) } } - pub fn height_idx_mut(&mut self, x: usize, y: usize) -> Option<&mut f32> { + /// Always returns the normal heightmap height + fn height_idx_mut(&mut self, x: usize, y: usize) -> Option<&mut f32> { let chunkx = x / RESOLUTION; let chunky = y / RESOLUTION; @@ -296,7 +340,9 @@ impl Heightmap { let celly = y % RESOLUTION; let chunk = self.get_chunk_mut((chunkx as u16, chunky as u16))?; - Some(&mut chunk.heights[celly][cellx]) + + // Safety: modulo RESOLUTION + unsafe { Some(chunk.height_idx_mut(cellx, celly).unwrap_unchecked()) } } /// Returns height at any point using bilinear interpolation @@ -469,19 +515,38 @@ fn unpack_height(height: u16) -> f32 { } impl Serialize for HeightmapChunk { - fn serialize(&self, serializer: S) -> Result { - let mut seq = serializer.serialize_seq(Some(1 + RESOLUTION * RESOLUTION))?; + fn serialize(&self, serializer: S) -> Result { + let mut seq = serializer.serialize_seq(Some(1 + RESOLUTION * RESOLUTION * 2))?; seq.serialize_element(&self.max_height)?; + Self::encode_delta(&mut seq, &self.heights, false)?; + Self::encode_delta(&mut seq, &self.heights_override, true)?; + seq.end() + } +} + +impl HeightmapChunk { + fn encode_delta( + seq: &mut S, + heights: &[[f32; RESOLUTION]; RESOLUTION], + handle_override: bool, + ) -> Result<(), S::Error> { let mut last = 0; - for row in &self.heights { + for row in heights { for &height in row { - let packed = pack_height(height); + let mut packed = pack_height(height); + if handle_override { + if height == NO_OVERRIDE { + packed = 0; + } else if packed == 0 { + packed = 1; + } + } let delta = packed.wrapping_sub(last); seq.serialize_element(&delta)?; last = packed; } } - seq.end() + Ok(()) } } @@ -489,10 +554,11 @@ impl<'de, const RESOLUTION: usize, const SIZE: u32> Deserialize<'de> for HeightmapChunk { fn deserialize>(deserializer: D) -> Result { - let (heights, max_height) = + let (heights, heights_override, max_height) = deserializer.deserialize_seq(HeightmapChunkVisitor::)?; Ok(Self { heights, + heights_override, max_height, }) } @@ -500,34 +566,63 @@ impl<'de, const RESOLUTION: usize, const SIZE: u32> Deserialize<'de> struct HeightmapChunkVisitor; -impl<'de, const RESOLUTION: usize> serde::de::Visitor<'de> for HeightmapChunkVisitor { - type Value = ([[f32; RESOLUTION]; RESOLUTION], f32); +impl<'de, const RESOLUTION: usize> Visitor<'de> for HeightmapChunkVisitor { + type Value = ( + [[f32; RESOLUTION]; RESOLUTION], + [[f32; RESOLUTION]; RESOLUTION], + f32, + ); fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("a sequence of floats") } - fn visit_seq>(self, mut seq: A) -> Result { - let len = seq.size_hint().unwrap_or(1 + RESOLUTION * RESOLUTION); - if len != 1 + RESOLUTION * RESOLUTION { - return Err(serde::de::Error::invalid_length(len, &"")); + fn visit_seq>(self, mut seq: A) -> Result { + let len = seq.size_hint().unwrap_or(1 + RESOLUTION * RESOLUTION * 2); + if len != 1 + RESOLUTION * RESOLUTION * 2 { + return Err(serde::de::Error::invalid_length( + len, + &"not enough elements to decode map chunk", + )); } let max_height = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(0, &""))?; let mut heights = [[0.0; RESOLUTION]; RESOLUTION]; + let mut heights_override = [[0.0; RESOLUTION]; RESOLUTION]; + + Self::decode_delta(&mut seq, &mut heights, false)?; + Self::decode_delta(&mut seq, &mut heights_override, true)?; + + Ok((heights, heights_override, max_height)) + } +} + +impl HeightmapChunkVisitor { + fn decode_delta<'de, A: SeqAccess<'de>>( + seq: &mut A, + heights: &mut [[f32; RESOLUTION]; RESOLUTION], + handle_override: bool, + ) -> Result<(), A::Error> { let mut last = 0; - for row in &mut heights { + for row in heights { for height in row { let delta: u16 = seq .next_element()? .ok_or_else(|| serde::de::Error::invalid_length(0, &""))?; let packed = delta.wrapping_add(last); + + if handle_override && packed == 0 { + *height = NO_OVERRIDE; + last = packed; + continue; + } + *height = unpack_height(packed); last = packed; } } - Ok((heights, max_height)) + Ok(()) } } @@ -708,6 +803,10 @@ mod erosion { } } + for c in &changed { + self.get_chunk_mut(*c).map(|c| c.update_max_height()); + } + changed.into_iter().collect() } } diff --git a/geom/src/polyline3.rs b/geom/src/polyline3.rs index 117b6047d..54298d08c 100644 --- a/geom/src/polyline3.rs +++ b/geom/src/polyline3.rs @@ -1,4 +1,4 @@ -use crate::{vec3, PolyLine, Radians, Segment3, Vec3, AABB3}; +use crate::{vec3, PolyLine, Radians, Segment, Segment3, Vec2, Vec3, AABB3}; use ordered_float::OrderedFloat; use serde::{Deserialize, Serialize}; use std::hint::unreachable_unchecked; @@ -241,6 +241,31 @@ impl PolyLine3 { } } + /// Returns the id of the point right after the projection along with the projection + /// None if polyline is empty + pub fn project_segment_2d(&self, p: Vec2) -> (Vec3, usize) { + match *self.points { + [] => unsafe { unreachable_unchecked() }, + [p] => (p, 0), + _ => self + .array_windows::<2>() + .enumerate() + .map(|(i, &[a, b])| { + let seg = Segment { + src: a.xy(), + dst: b.xy(), + }; + (a + (b - a) * seg.project_t(p), i + 1) + }) + .min_by_key(|&(proj, _)| OrderedFloat((p - proj.xy()).mag())) + .unwrap(), // Unwrap ok: n_points > 2 + } + } + + pub fn project_2d(&self, p: Vec2) -> Vec3 { + self.project_segment_2d(p).0 + } + pub fn segment_vec(&self, id: usize) -> Option { Some(self.get(id + 1)? - self.get(id)?) } diff --git a/geom/src/v2.rs b/geom/src/v2.rs index 9008702ab..7c7740beb 100644 --- a/geom/src/v2.rs +++ b/geom/src/v2.rs @@ -354,6 +354,14 @@ impl Vec2 { } } + #[inline] + pub fn ceil(self) -> Self { + Self { + x: self.x.ceil(), + y: self.y.ceil(), + } + } + #[inline] pub fn fract(self) -> Self { Self { diff --git a/native_app/src/rendering/map_rendering/terrain.rs b/native_app/src/rendering/map_rendering/terrain.rs index c6c545b5e..0ed653510 100644 --- a/native_app/src/rendering/map_rendering/terrain.rs +++ b/native_app/src/rendering/map_rendering/terrain.rs @@ -4,7 +4,7 @@ use geom::Camera; use simulation::map::{Map, MapSubscriber, UpdateType}; use simulation::Simulation; -const CSIZE: usize = simulation::map::Heightmap::SIZE as usize; +const CSIZE: u32 = simulation::map::Heightmap::SIZE; const CRESO: usize = simulation::map::Heightmap::RESOLUTION; pub struct TerrainRender { @@ -36,7 +36,7 @@ impl TerrainRender { self.terrain.update_chunk( &mut ctx.gfx, (chunk_id.0 as u32, chunk_id.1 as u32), - chunk.heights(), + chunk, ); } @@ -53,7 +53,7 @@ impl TerrainRender { self.terrain.update_chunk( &mut ctx.gfx, (chunkid.0 as u32, chunkid.1 as u32), - chunk.heights(), + chunk, ); } changed = true; diff --git a/simulation/Cargo.toml b/simulation/Cargo.toml index b876bc30e..a6b7cbaaa 100644 --- a/simulation/Cargo.toml +++ b/simulation/Cargo.toml @@ -23,6 +23,7 @@ serde-big-array = "0.5.0" lazy_static = "1.4.0" arc-swap = "1.3.0" derive_more = { workspace = true } +bitflags = "2.4.1" [dev-dependencies] easybench = "1.1.0" diff --git a/simulation/src/init.rs b/simulation/src/init.rs index 43e678aa0..f0bb9c756 100644 --- a/simulation/src/init.rs +++ b/simulation/src/init.rs @@ -44,6 +44,7 @@ pub fn init() { register_system("train_reservations_update", train_reservations_update); register_system("freight_station", freight_station_system); register_system("random_vehicles", random_vehicles_update); + register_system("update_map", |_, res| res.write::().update()); register_system_sim("add_souls_to_empty_buildings", add_souls_to_empty_buildings); diff --git a/simulation/src/map/change_detection.rs b/simulation/src/map/change_detection.rs index 517cd65fc..79b60444d 100644 --- a/simulation/src/map/change_detection.rs +++ b/simulation/src/map/change_detection.rs @@ -16,11 +16,13 @@ pub trait CanonicalPosition { pub type SubscriberChunkID = ChunkID<5>; -#[derive(Eq, PartialEq, Hash, Copy, Clone)] -pub enum UpdateType { - Road, - Building, - Terrain, +bitflags::bitflags! { + #[derive(Debug, Copy, Clone)] + pub struct UpdateType: u8 { + const Road = 1; + const Building = 1 << 1; + const Terrain = 1 << 2; + } } #[derive(Default)] @@ -115,7 +117,7 @@ impl MapSubscriber { } pub fn dispatch(&mut self, update_type: UpdateType, chunk_id: SubscriberChunkID) { - if update_type != self.filter { + if !self.filter.intersects(update_type) { return; } let mut inner = self.inner.lock().unwrap(); diff --git a/simulation/src/map/height_override.rs b/simulation/src/map/height_override.rs new file mode 100644 index 000000000..e1b5dc8f1 --- /dev/null +++ b/simulation/src/map/height_override.rs @@ -0,0 +1,147 @@ +use std::collections::BTreeSet; + +use geom::{Shape, Vec2, AABB, NO_OVERRIDE}; + +use crate::map::terrain::CELL_SIZE; +use crate::map::{ + Map, ProjectFilter, ProjectKind, SubscriberChunkID, TerrainChunkID, UpdateType, ROAD_Z_OFFSET, + TERRAIN_CHUNK_RESOLUTION, +}; + +struct OverrideSetter { + chunk: TerrainChunkID, + overrides: [[f32; TERRAIN_CHUNK_RESOLUTION]; TERRAIN_CHUNK_RESOLUTION], + chunk_bound: AABB, +} + +impl OverrideSetter { + fn new(chunk: TerrainChunkID) -> Self { + let chunk_bound = chunk.bbox(); + + let overrides = [[NO_OVERRIDE; TERRAIN_CHUNK_RESOLUTION]; TERRAIN_CHUNK_RESOLUTION]; + + Self { + chunk, + overrides, + chunk_bound, + } + } + + fn set_override(&mut self, obj_bounds: AABB, filter: impl Fn(Vec2) -> Option) { + let b = obj_bounds.intersection(self.chunk_bound); + if b.size() == Vec2::ZERO { + return; + } + + let b = b.offset(-self.chunk_bound.ll); + + let start = (b.ll / CELL_SIZE).floor(); + let end = (b.ur / CELL_SIZE).ceil(); + + for y in start.y as usize..end.y as usize { + for x in start.x as usize..end.x as usize { + let pos = Vec2::new(x as f32, y as f32) * CELL_SIZE + self.chunk_bound.ll; + let Some(h) = filter(pos) else { + continue; + }; + let v = self.overrides[y][x]; + self.overrides[y][x] = if v == NO_OVERRIDE { h } else { v.min(h) } + } + } + } + + fn finish(self, map: &mut Map) { + map.environment.set_overrides(self.chunk, self.overrides); + map.subscribers + .dispatch_chunk(UpdateType::Terrain, self.chunk); + } +} + +/// Updates the overrides on the map. +/// Proceeds in 3 steps: +/// - Find all objects that could have changed (from chunk) +/// - Find all terrain chunks that can be affected +/// - Update each chunk individually +pub fn find_overrides(map: &mut Map, chunk: SubscriberChunkID) { + let mut terrain_affected = BTreeSet::new(); + + for obj in map.spatial_map.query( + chunk.bbox(), + ProjectFilter::ROAD | ProjectFilter::INTER | ProjectFilter::BUILDING, + ) { + let bbox = match obj { + ProjectKind::Inter(i) => { + let i = map.get(i).unwrap(); + + let mut bounds = i.bcircle(&map.roads); + bounds.radius *= 2.0; + + bounds.bbox() + } + ProjectKind::Road(r) => { + let r = map.get(r).unwrap(); + + let expand = 10.0 + r.width * 3.0; + + r.points.bbox().flatten().expand(expand + 3.0) + } + ProjectKind::Building(b) => { + let b = map.get(b).unwrap(); + + let obb = b.obb.expand(25.0); + + obb.bbox() + } + _ => continue, + }; + + for chunk in map.environment.covered_chunks(bbox) { + terrain_affected.insert(chunk); + } + } + + for chunk in terrain_affected { + let mut setter = OverrideSetter::new(chunk); + + for obj in map.spatial_map.query( + chunk.bbox(), + ProjectFilter::ROAD | ProjectFilter::INTER | ProjectFilter::BUILDING, + ) { + match obj { + ProjectKind::Inter(i) => { + let i = map.get(i).unwrap(); + + let mut bounds = i.bcircle(&map.roads); + bounds.radius *= 2.0; + + setter + .set_override(bounds.bbox(), |pos| bounds.contains(pos).then_some(i.pos.z)); + } + ProjectKind::Road(r) => { + let r = map.get(r).unwrap(); + + let expand = 10.0 + r.width * 3.0; + + setter.set_override(r.points.bbox().flatten().expand(expand + 3.0), |pos| { + let proj = r.points.project_2d(pos); + proj.xy() + .is_close(pos, expand) + .then_some(proj.z - ROAD_Z_OFFSET) + }) + } + ProjectKind::Building(b) => { + let b = map.get(b).unwrap(); + + let obb = b.obb.expand(25.0); + + setter.set_override(obb.bbox(), |pos| { + obb.contains(pos).then_some(b.height - 0.3) + }); + } + _ => {} + } + } + + setter.finish(map); + } +} diff --git a/simulation/src/map/map.rs b/simulation/src/map/map.rs index 5bf3d076c..523102b85 100644 --- a/simulation/src/map/map.rs +++ b/simulation/src/map/map.rs @@ -1,3 +1,4 @@ +use crate::map::height_override::find_overrides; use crate::map::serializing::SerializedMap; use crate::map::{ Building, BuildingID, BuildingKind, Environment, Intersection, IntersectionID, Lane, LaneID, @@ -37,6 +38,7 @@ pub struct Map { pub environment: Environment, pub parking: ParkingSpots, pub subscribers: MapSubscribers, + pub(crate) override_suscriber: MapSubscriber, } defer_serialize!(Map, SerializedMap); @@ -50,6 +52,7 @@ impl Default for Map { impl Map { // Public API pub fn empty() -> Self { + let subscribers = MapSubscribers::default(); Self { roads: Roads::default(), lanes: Lanes::default(), @@ -60,7 +63,15 @@ impl Map { environment: Environment::default(), spatial_map: SpatialMap::default(), bkinds: Default::default(), - subscribers: Default::default(), + override_suscriber: subscribers.subscribe(UpdateType::Road | UpdateType::Building), + subscribers, + } + } + + /// Perform cleanups potentially required every frame + pub fn update(&mut self) { + for chunk in self.override_suscriber.take_updated_chunks() { + find_overrides(self, chunk); } } @@ -629,6 +640,10 @@ impl Map { mk_proj(ProjectKind::Ground) } + pub fn get(&self, id: T) -> Option<&T::Obj> { + MapObj::get(id, self) + } + pub fn is_empty(&self) -> bool { self.roads.is_empty() && self.lanes.is_empty() && self.intersections.is_empty() } @@ -897,3 +912,43 @@ impl MapProject { matches!(self.kind, ProjectKind::Ground) } } + +pub trait MapObj { + type Obj; + fn get(self, map: &Map) -> Option<&Self::Obj>; +} + +impl MapObj for RoadID { + type Obj = Road; + fn get(self, map: &Map) -> Option<&Road> { + map.roads.get(self) + } +} + +impl MapObj for LaneID { + type Obj = Lane; + fn get(self, map: &Map) -> Option<&Lane> { + map.lanes.get(self) + } +} + +impl MapObj for IntersectionID { + type Obj = Intersection; + fn get(self, map: &Map) -> Option<&Intersection> { + map.intersections.get(self) + } +} + +impl MapObj for BuildingID { + type Obj = Building; + fn get(self, map: &Map) -> Option<&Building> { + map.buildings.get(self) + } +} + +impl MapObj for LotID { + type Obj = Lot; + fn get(self, map: &Map) -> Option<&Lot> { + map.lots.get(self) + } +} diff --git a/simulation/src/map/mod.rs b/simulation/src/map/mod.rs index c4b2127ae..4546350e8 100644 --- a/simulation/src/map/mod.rs +++ b/simulation/src/map/mod.rs @@ -28,6 +28,7 @@ pub mod procgen { } mod change_detection; +mod height_override; mod light_policy; #[allow(clippy::module_inception)] mod map; diff --git a/simulation/src/map/serializing.rs b/simulation/src/map/serializing.rs index 9ae96cecb..4c90589a8 100644 --- a/simulation/src/map/serializing.rs +++ b/simulation/src/map/serializing.rs @@ -46,7 +46,7 @@ impl From for Map { parking: sel.parking, environment: sel.environment, bkinds: sel.bkinds, - subscribers: Default::default(), + ..Self::empty() } } } diff --git a/simulation/src/map/terrain.rs b/simulation/src/map/terrain.rs index c0f0d754f..8caf575ee 100644 --- a/simulation/src/map/terrain.rs +++ b/simulation/src/map/terrain.rs @@ -13,7 +13,7 @@ pub type TerrainChunkID = common::ChunkID<5>; pub const TERRAIN_CHUNK_RESOLUTION: usize = 32; -const CELL_SIZE: f32 = TerrainChunkID::SIZE_F32 / TERRAIN_CHUNK_RESOLUTION as f32; +pub(super) const CELL_SIZE: f32 = TerrainChunkID::SIZE_F32 / TERRAIN_CHUNK_RESOLUTION as f32; const TREE_GRID_SIZE: usize = 256; @@ -124,10 +124,25 @@ impl Environment { .map(|((x, y), c)| (TerrainChunkID::new_i16(x as i16, y as i16), c)) } + pub fn covered_chunks(&self, bounds: AABB) -> impl Iterator { + self.heightmap + .covered_chunks(bounds) + .map(|(x, y)| TerrainChunkID::new_i16(x as i16, y as i16)) + } + pub fn raycast(&self, ray: Ray3) -> Option<(Vec3, Vec3)> { self.heightmap.raycast(ray) } + pub fn set_overrides( + &mut self, + chunk: TerrainChunkID, + overrides: [[f32; TERRAIN_CHUNK_RESOLUTION]; TERRAIN_CHUNK_RESOLUTION], + ) { + self.heightmap + .set_override((chunk.0 as u16, chunk.1 as u16), overrides); + } + /// Applies a function to the heightmap /// Returns the chunks that were modified pub fn terrain_apply(