Speed up the bench rotation search by generating a mask of roads and paths, and searching that instead of the actual blocks.

This commit is contained in:
Luigi
2026-04-12 13:44:06 +02:00
parent 505724a5e1
commit 9be36112e2
4 changed files with 105 additions and 16 deletions

View File

@@ -72,6 +72,14 @@ pub fn generate_world_with_options(
let building_passages =
highways::collect_building_passage_coords(&elements, &xzbbox, args.scale);
// Pre-build a bitmap of every (x, z) block coordinate covered by a rendered
// road or path surface. Uses the same Bresenham + block_range geometry as
// generate_highways_internal, so the bitmap is a 1:1 match of what gets placed.
// Amenity processors use this for O(1) nearest-road-block lookups.
/// TODO Use this data to create overhanging traffic signals.
let road_mask =
highways::collect_road_surface_coords(&elements, &xzbbox, args.scale);
// Process all elements (no longer need to partition boundaries)
let elements_count: usize = elements.len();
let process_pb: ProgressBar = ProgressBar::new(elements_count as u64);
@@ -171,7 +179,7 @@ pub fn generate_world_with_options(
&building_footprints,
);
} else if way.tags.contains_key("amenity") {
amenities::generate_amenities(&mut editor, &element, args, &flood_fill_cache);
amenities::generate_amenities(&mut editor, &element, args, &flood_fill_cache, &road_mask);
} else if way.tags.contains_key("leisure") {
leisure::generate_leisure(
&mut editor,
@@ -226,7 +234,7 @@ pub fn generate_world_with_options(
&building_footprints,
);
} else if node.tags.contains_key("amenity") {
amenities::generate_amenities(&mut editor, &element, args, &flood_fill_cache);
amenities::generate_amenities(&mut editor, &element, args, &flood_fill_cache, &road_mask);
} else if node.tags.contains_key("barrier") {
barriers::generate_barrier_nodes(&mut editor, node);
} else if node.tags.contains_key("highway") {
@@ -312,6 +320,7 @@ pub fn generate_world_with_options(
// Drop remaining caches
drop(highway_connectivity);
drop(flood_fill_cache);
drop(road_mask);
// Generate ground layer (surface blocks, vegetation, shorelines, underground fill)
ground_generation::generate_ground_layer(

View File

@@ -3,7 +3,7 @@ use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::coordinate_system::cartesian::XZPoint;
use crate::deterministic_rng::element_rng;
use crate::floodfill_cache::FloodFillCache;
use crate::floodfill_cache::{FloodFillCache, RoadMaskBitmap};
use crate::osm_parser::ProcessedElement;
use crate::world_editor::WorldEditor;
use fastnbt::Value;
@@ -17,26 +17,22 @@ use std::collections::{HashMap, HashSet};
/// up to max_radius blocks away, and returns the (x, z) position of
/// the nearest road node found.
///
/// Returns None if no road node exists within range.
/// Callers can use the returned position to derive a facing direction,
/// compute a distance, or do anything else they need.
fn nearest_road(x: i32, z: i32, max_radius: i32, editor: &WorldEditor) -> Option<(i32, i32)> {
let road_blocks = [
GRAY_CONCRETE_POWDER,
CYAN_TERRACOTTA,
DIRT_PATH,
GRAY_CONCRETE,
BLACK_CONCRETE,
STONE_BRICKS,
];
fn get_nearest_road_block(
x: i32,
z: i32,
max_radius: i32,
road_mask: &RoadMaskBitmap,
) -> Option<(i32, i32)> {
for dist in 1..=max_radius {
// Cross pattern: North, South, West, East
let candidates = [(x, z - dist), (x, z + dist), (x - dist, z), (x + dist, z)];
for (cx, cz) in candidates {
let surface_y = editor.get_ground_level(cx, cz);
if editor.check_for_block_absolute(cx, surface_y, cz, Some(&road_blocks), None) {
if road_mask.contains(cx, cz) {
return Some((cx, cz));
}
}
@@ -50,6 +46,7 @@ pub fn generate_amenities(
element: &ProcessedElement,
args: &Args,
flood_fill_cache: &FloodFillCache,
road_mask: &RoadMaskBitmap,
) {
// Skip if 'layer' or 'level' is negative in the tags
if let Some(layer) = element.tags().get("layer") {
@@ -164,7 +161,7 @@ pub fn generate_amenities(
// Place a bench
if let Some(pt) = first_node {
let mut rng = element_rng(element.id());
let road_pos = nearest_road(pt.x, pt.z, 5, editor);
let road_pos = get_nearest_road_block(pt.x, pt.z, 4, road_mask);
let use_east_west = if let Some((rx, rz)) = road_pos {
let dx = (rx - pt.x).abs();

View File

@@ -1010,6 +1010,83 @@ pub(crate) fn highway_block_range(
block_range
}
/// Collect all (x, z) coordinates that are covered by any rendered road or path
/// surface. The returned bitmap has 1 for every block that the highway renderer
/// places as a road/path surface and 0 everywhere else.
///
/// Geometry is computed identically to `generate_highways_internal`:
/// - Bresenham line between each consecutive pair of OSM nodes
/// - Expanded by `block_range` in both axes (same value as the renderer uses)
/// - `area=yes` ways, indoor ways, negative-level ways, and pure node types
/// (street_lamp, crossing, bus_stop) are excluded, matching the renderer's
/// early-return guards.
///
/// This lets `find_nearest_road_block` in `amenities.rs` or other processors do a single O(1) bitmap lookup
/// instead of live `get_ground_level` + `check_for_block_absolute` world scans.
pub fn collect_road_surface_coords(
elements: &[ProcessedElement],
xzbbox: &XZBBox,
scale: f64,
) -> CoordinateBitmap {
let mut bitmap = CoordinateBitmap::new(xzbbox);
for element in elements {
let ProcessedElement::Way(way) = element else {
continue;
};
let Some(highway_type) = way.tags.get("highway") else {
continue;
};
// Exclude non-surface node-only highway types
match highway_type.as_str() {
"street_lamp" | "crossing" | "bus_stop" => continue,
_ => {}
}
// Exclude area highways (pedestrian plazas etc.) — flood-filled separately
if way.tags.get("area").is_some_and(|v| v == "yes") {
continue;
}
// Exclude indoor ways (same guard as generate_highways_internal)
if way.tags.get("indoor").is_some_and(|v| v == "yes") {
continue;
}
// Exclude negative-level ways (indoor mapping)
if way
.tags
.get("level")
.and_then(|l| l.parse::<i32>().ok())
.is_some_and(|l| l < 0)
{
continue;
}
// Use the same block_range the renderer uses for this highway type
let block_range = highway_block_range(highway_type, &way.tags, scale);
for i in 1..way.nodes.len() {
let prev = way.nodes[i - 1].xz();
let cur = way.nodes[i].xz();
let points = bresenham_line(prev.x, 0, prev.z, cur.x, 0, cur.z);
for (bx, _, bz) in &points {
for dx in -block_range..=block_range {
for dz in -block_range..=block_range {
bitmap.set(bx + dx, bz + dz);
}
}
}
}
}
bitmap
}
/// Collect all (x, z) coordinates covered by highways tagged
/// `tunnel=building_passage`. The returned bitmap can be passed into building
/// generation to cut ground-level openings through walls and floors.

View File

@@ -237,6 +237,12 @@ impl CoordinateBitmap {
/// Type alias for building footprint bitmap (for backwards compatibility).
pub type BuildingFootprintBitmap = CoordinateBitmap;
/// Type alias for the road surface bitmap used by amenity processors.
/// Built by `highways::collect_road_surface_coords` using the same Bresenham +
/// block_range geometry as the renderer, so every placed road/path block coordinate
/// is marked as 1 and everything else is 0.
pub type RoadMaskBitmap = CoordinateBitmap;
/// A cache of pre-computed flood fill results, keyed by element ID.
pub struct FloodFillCache {
/// Cached results: element_id -> filled coordinates