Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions src/data_processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,7 +1132,7 @@ pub fn generate_world_with_options(
};
let mut signage_ctx: Option<Arc<signage::SignageContext>> = (world_format
== WorldFormat::JavaAnvil)
.then(|| signage::build_context(&elements, args, llbbox, &xzbbox, first_decal_id))
.then(|| signage::build_context(&elements, args, llbbox, first_decal_id))
.flatten()
.map(Arc::new);
if let (Some(t), Some(_)) = (signage_start, signage_ctx.as_ref()) {
Expand Down Expand Up @@ -1321,14 +1321,22 @@ pub fn generate_world_with_options(
// 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.
let road_mask = Arc::new(highways::collect_road_surface_coords(
// Amenity processors use this for O(1) nearest-road-block lookups. The carriageways
// among them come in the same pass: trees keep their trunks out of them, mapped
// street trees included, and leave more headroom over them than over a footway.
let (road_mask, carriageway) = highways::collect_road_and_carriageway_coords(
&elements,
&editor,
&clip_bbox,
args.scale,
args.water.water_detail,
));
);
let road_mask = Arc::new(road_mask);
let carriageway = Arc::new(if carriageway.is_empty() {
CoordinateBitmap::new_empty()
} else {
carriageway
});
let barrier_clearance =
barriers::RoadClearance::build(&elements, &editor, args.scale, Arc::clone(&road_mask));

Expand All @@ -1343,6 +1351,8 @@ pub fn generate_world_with_options(
};
editor.set_sealed_surface(Arc::clone(&sealed_surface));

editor.set_carriageway(Arc::clone(&carriageway));

let mut mapped_trunks = crate::trees::mapped::MappedTrunks::collect(&elements, args.scale);
let crown_r = crate::element_processing::tree::crown_probe_blocks(args.scale);
mapped_trunks.measure_typical_height(|x, z| editor.canopy_top_m(x, z, crown_r).flatten());
Expand Down Expand Up @@ -1873,6 +1883,7 @@ pub fn generate_world_with_options(
tile_editor.set_tree_pack(Arc::clone(tp));
}
tile_editor.set_sealed_surface(Arc::clone(&sealed_surface));
tile_editor.set_carriageway(Arc::clone(&carriageway));
tile_editor.set_mapped_trunks(Arc::clone(&mapped_trunks));
tile_editor.set_mapped_cranes(Arc::clone(&mapped_cranes));
tile_editor.set_clear_corridors(Arc::clone(&clear_corridors));
Expand Down Expand Up @@ -2458,6 +2469,7 @@ pub fn generate_world_with_options(
editor.release_sealed_surface();
}
drop(sealed_surface);
drop(carriageway);
// The barrier clearance shares the road mask, so it goes first.
drop(barrier_clearance);
drop(road_mask);
Expand Down
96 changes: 51 additions & 45 deletions src/element_processing/highways.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3575,48 +3575,16 @@ 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 `get_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.
///
/// `water_detail` drops the water cells of short crossings, which the renderer then skips.
/// The road half of `collect_road_and_carriageway_coords`.
#[cfg(test)]
pub fn collect_road_surface_coords(
elements: &[ProcessedElement],
editor: &WorldEditor,
xzbbox: &XZBBox,
scale: f64,
water_detail: crate::water_detail::WaterDetail,
) -> CoordinateBitmap {
collect_highway_surface_coords(elements, Some(editor), xzbbox, scale, water_detail, |_| {
true
})
}

/// Vehicular carriageways only (no footways, cycleways, paths, steps or pedestrian
/// streets). Signage uses this to keep posts on the sidewalk but off the road.
pub fn collect_carriageway_coords(
elements: &[ProcessedElement],
xzbbox: &XZBBox,
scale: f64,
) -> CoordinateBitmap {
collect_highway_surface_coords(
elements,
None,
xzbbox,
scale,
Default::default(),
is_carriageway,
)
collect_road_and_carriageway_coords(elements, editor, xzbbox, scale, water_detail).0
}

/// True for highway types that carry vehicles rather than people on foot, bike or horse.
Expand Down Expand Up @@ -3681,16 +3649,55 @@ pub(crate) fn tunnel_renders_at_surface(
!renders_as_highway_tunnel(way) || editor.is_some_and(|e| !tunnel_bore_fits(e, way, scale))
}

/// Shared stamping loop for the road-surface bitmaps; `include` filters by highway type.
fn collect_highway_surface_coords(
/// Collect all (x, z) coordinates that are covered by any rendered road or path
/// surface, and in a second bitmap the carriageways among them, in one pass over the
/// roads. The first 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 `get_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.
///
/// `water_detail` drops the water cells of short crossings, which the renderer then skips.
pub fn collect_road_and_carriageway_coords(
elements: &[ProcessedElement],
editor: &WorldEditor,
xzbbox: &XZBBox,
scale: f64,
water_detail: crate::water_detail::WaterDetail,
) -> (CoordinateBitmap, CoordinateBitmap) {
let mut roads = CoordinateBitmap::new(xzbbox);
let mut carriageways = CoordinateBitmap::new(xzbbox);
let editor = Some(editor);
stamp_highway_surfaces(
elements,
editor,
scale,
water_detail,
|carriageway, x, z| {
roads.set(x, z);
if carriageway {
carriageways.set(x, z);
}
},
);
(roads, carriageways)
}

/// Hands `stamp` every road and path surface cell, with whether its way carries vehicles.
fn stamp_highway_surfaces(
elements: &[ProcessedElement],
editor: Option<&WorldEditor>,
xzbbox: &XZBBox,
scale: f64,
water_detail: crate::water_detail::WaterDetail,
include: impl Fn(&str) -> bool,
) -> CoordinateBitmap {
let mut bitmap = CoordinateBitmap::new(xzbbox);
mut stamp: impl FnMut(bool, i32, i32),
) {
// A way that meets a bridge is its ramp or its approach, and keeps its surface.
let drowning = editor.filter(|_| water_detail.drowns_crossing(scale, &HashMap::new()));
let bridge_nodes: HashSet<(i32, i32)> = match drowning {
Expand Down Expand Up @@ -3718,12 +3725,13 @@ fn collect_highway_surface_coords(
let Some(highway_type) = surface_highway_type(way) else {
continue;
};
if !include(highway_type) || !tunnel_renders_at_surface(way, editor, scale) {
if !tunnel_renders_at_surface(way, editor, scale) {
continue;
}

// Use the same block_range the renderer uses for this highway type
let block_range = highway_block_range(highway_type, &way.tags, scale);
let carriageway = is_carriageway(highway_type);
let drowned = drowning.filter(|_| {
water_detail.drowns_crossing(scale, &way.tags)
&& !way.nodes.iter().any(|n| bridge_nodes.contains(&(n.x, n.z)))
Expand Down Expand Up @@ -3753,13 +3761,11 @@ fn collect_highway_surface_coords(
if skip_water && drowned.is_some_and(|e| fills(e, bx + dx, bz + dz)) {
continue;
}
bitmap.set(bx + dx, bz + dz);
stamp(carriageway, bx + dx, bz + dz);
}
}
}
}

bitmap
}

/// Collect all (x, z) coordinates covered by highways tagged
Expand Down
39 changes: 16 additions & 23 deletions src/element_processing/signage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
use crate::args::{Args, SignageLevel};
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::coordinate_system::cartesian::XZBBox;
use crate::coordinate_system::geographic::LLBBox;
use crate::decals::font;
use crate::decals::region::SignRegion;
use crate::decals::{pictograms, DecalKey, DecalRegistry, ShieldStyle, TextStyle, TrafficSign};
use crate::element_processing::building_facade::FacadeAnchor;
use crate::element_processing::buildings::is_underground_building;
use crate::element_processing::highways::{
collect_carriageway_coords, highway_block_range, is_carriageway, surface_highway_type,
highway_block_range, is_carriageway, surface_highway_type,
};
use crate::element_processing::{get_nearest_non_road_block, get_nearest_road_block};
use crate::floodfill_cache::{BuildingFootprintBitmap, RoadMaskBitmap};
Expand Down Expand Up @@ -54,8 +53,6 @@ pub struct SignageContext {
/// Node id -> which way its stop, give-way or crossing sign faces.
pub node_headings: HashMap<u64, NodeSignHeading>,
pub scale: f64,
/// Vehicular road surfaces; posts stand beside these (sidewalks are fine).
pub carriageway: RoadMaskBitmap,
/// Placement tally for the end-of-run summary.
report: Mutex<PlacementReport>,
}
Expand All @@ -77,15 +74,12 @@ impl SignageContext {
intersections: IntersectionIndex::new(),
node_headings: HashMap::new(),
scale: 1.0,
carriageway: RoadMaskBitmap::new_empty(),
report: Mutex::new(PlacementReport::new()),
}
}

/// Frees what only element placement reads: the carriageway mask, a bit per world
/// column, and the node sign headings.
/// Frees what only element placement reads: the node sign headings.
pub fn release_placement_masks(&mut self) {
self.carriageway = RoadMaskBitmap::new_empty();
self.node_headings = HashMap::new();
}

Expand Down Expand Up @@ -989,14 +983,12 @@ pub fn build_context(
elements: &[ProcessedElement],
args: &Args,
llbbox: LLBBox,
xzbbox: &XZBBox,
first_map_id: i32,
) -> Option<SignageContext> {
let level = args.signage;
if !level.enabled() {
return None;
}
let carriageway = collect_carriageway_coords(elements, xzbbox, args.scale);
let lat = (llbbox.min().lat() + llbbox.max().lat()) / 2.0;
let lon = (llbbox.min().lng() + llbbox.max().lng()) / 2.0;
let region = SignRegion::detect(lat, lon);
Expand Down Expand Up @@ -1025,7 +1017,6 @@ pub fn build_context(
intersections,
node_headings,
scale: args.scale,
carriageway,
report: Mutex::new(PlacementReport::new()),
})
}
Expand Down Expand Up @@ -1460,7 +1451,7 @@ pub fn generate_power_signage(
return;
};
let (x, z) = (node.x, node.z);
if ctx.carriageway.contains(x, z) || !editor.owns(x, z) {
if editor.on_carriageway(x, z) || !editor.owns(x, z) {
return;
}
let ground = editor.get_absolute_y(x, 0, z);
Expand Down Expand Up @@ -1525,7 +1516,7 @@ pub fn generate_parking_signage(
return;
};
let (x, z) = (node.x, node.z);
if ctx.carriageway.contains(x, z) || !editor.owns(x, z) {
if editor.on_carriageway(x, z) || !editor.owns(x, z) {
return;
}
let ground = editor.get_absolute_y(x, 0, z);
Expand Down Expand Up @@ -1755,7 +1746,7 @@ fn place_roadside_sign(
)
})
.find_map(|(sx, sz)| {
post_ground(editor, ctx, footprints, sx, sz, POST_SHAFT + 1).map(|g| (sx, sz, g))
post_ground(editor, footprints, sx, sz, POST_SHAFT + 1).map(|g| (sx, sz, g))
})
else {
return;
Expand Down Expand Up @@ -1812,7 +1803,7 @@ fn place_street_name_post(
})
.map(|(dx, dz)| (node.x + dx, node.z + dz))
.find_map(|(px, pz)| {
post_ground(editor, ctx, footprints, px, pz, POST_SHAFT + 2).map(|g| (px, pz, g))
post_ground(editor, footprints, px, pz, POST_SHAFT + 2).map(|g| (px, pz, g))
})
else {
return;
Expand Down Expand Up @@ -1870,8 +1861,11 @@ pub fn generate_highway_node_signage(
let (px, pz) = match kerb {
Some(p) => p,
// The node sits on the carriageway; the post goes to the nearest kerb.
None if ctx.carriageway.contains(x, z) => {
match get_nearest_non_road_block(x, z, 10, &ctx.carriageway) {
None if editor.on_carriageway(x, z) => {
match editor
.carriageway()
.and_then(|roads| get_nearest_non_road_block(x, z, 10, roads))
{
Some(p) => p,
None => return,
}
Expand Down Expand Up @@ -1928,11 +1922,11 @@ fn kerb_beside(
};
let edge = (1..=12).find(|&k| {
let (cx, cz) = cell(k);
!ctx.carriageway.contains(cx, cz)
!editor.on_carriageway(cx, cz)
})?;
(edge..edge + 4)
.map(cell)
.find(|&(cx, cz)| post_ground(editor, ctx, footprints, cx, cz, POST_SHAFT + 1).is_some())
.find(|&(cx, cz)| post_ground(editor, footprints, cx, cz, POST_SHAFT + 1).is_some())
}

/// Unit vector to the kerb that traffic travelling along `dir` keeps to.
Expand All @@ -1948,13 +1942,12 @@ fn kerb_side(region: SignRegion, (dx, dz): (f64, f64)) -> (f64, f64) {
/// carriageway, outside buildings and water, in free cells.
fn post_ground(
editor: &WorldEditor,
ctx: &SignageContext,
footprints: &BuildingFootprintBitmap,
x: i32,
z: i32,
height: i32,
) -> Option<i32> {
if ctx.carriageway.contains(x, z) || footprints.contains(x, z) || editor.is_lc_water(x, z) {
if editor.on_carriageway(x, z) || footprints.contains(x, z) || editor.is_lc_water(x, z) {
return None;
}
let ground = editor.get_absolute_y(x, 0, z);
Expand Down Expand Up @@ -2011,7 +2004,7 @@ pub fn generate_railway_node_signage(
return;
}
// Metro entrance / tram stop: a lit logo on all four faces of a post.
if ctx.carriageway.contains(x, z) {
if editor.on_carriageway(x, z) {
return;
}
let Some(head) = traffic_sign_post(editor, x, ground, z) else {
Expand Down Expand Up @@ -2808,7 +2801,7 @@ mod tests {
carriageway.set(x, z);
}
}
ctx.carriageway = carriageway;
editor.set_carriageway(std::sync::Arc::new(carriageway));
ctx.node_headings.insert(
7,
NodeSignHeading {
Expand Down
Loading
Loading