diff --git a/src/element_processing/railways.rs b/src/element_processing/railways.rs index eb2736249..141ab797b 100644 --- a/src/element_processing/railways.rs +++ b/src/element_processing/railways.rs @@ -3,6 +3,9 @@ use crate::bresenham::bresenham_line; use crate::osm_parser::ProcessedWay; use crate::world_editor::WorldEditor; +/// Number of blocks per OSM layer level (matches highway elevation step). +const LAYER_HEIGHT_STEP: i32 = 6; + pub fn generate_railways(editor: &mut WorldEditor, element: &ProcessedWay) { if let Some(railway_type) = element.tags.get("railway") { if [ @@ -30,6 +33,17 @@ pub fn generate_railways(editor: &mut WorldEditor, element: &ProcessedWay) { } } + // Respect the OSM `layer` tag so elevated railways (bridges, overpasses) + // are placed above ground level instead of clipping through it. + let layer_value: i32 = element + .tags + .get("layer") + .and_then(|l| l.parse::().ok()) + .unwrap_or(0) + .max(0); // underground (<0) is handled by the tunnel check above + + let layer_offset = layer_value * LAYER_HEIGHT_STEP; + for i in 1..element.nodes.len() { let prev_node = element.nodes[i - 1].xz(); let cur_node = element.nodes[i].xz(); @@ -40,35 +54,121 @@ pub fn generate_railways(editor: &mut WorldEditor, element: &ProcessedWay) { for j in 0..smoothed_points.len() { let (bx, _, bz) = smoothed_points[j]; - editor.set_block(GRAVEL, bx, 0, bz, None, None); + // Base offsets from ground level. + // When layer_offset > 0 the rail floats above terrain (bridge/overpass). + let gravel_y = layer_offset; + let rail_y = layer_offset + 1; + + // --- Terrain-slope detection (only for at-grade railways) --- + // When terrain is enabled and layer == 0 we try to place ascending + // rail variants so consecutive blocks stay visually connected even + // when the ground rises or falls by one block per step. + let prev_ground = if j > 0 { + let (px, _, pz) = smoothed_points[j - 1]; + editor.get_ground_level(px, pz) + } else { + editor.get_ground_level(bx, bz) + }; - let prev = if j > 0 { - Some(smoothed_points[j - 1]) + let next_ground = if j + 1 < smoothed_points.len() { + let (nx, _, nz) = smoothed_points[j + 1]; + editor.get_ground_level(nx, nz) + } else { + editor.get_ground_level(bx, bz) + }; + + let current_ground = editor.get_ground_level(bx, bz); + + // Fill the vertical gap under the rail when terrain rises steeply + // so there is always a solid gravel block supporting the track. + if layer_offset == 0 && prev_ground < current_ground { + for fill_y in prev_ground..current_ground { + editor.set_block_absolute(GRAVEL, bx, fill_y, bz, None, None); + } + } + + editor.set_block(GRAVEL, bx, gravel_y, bz, None, None); + + let prev_xz = if j > 0 { + let (px, _, pz) = smoothed_points[j - 1]; + Some((px, pz)) } else { None }; - let next = if j < smoothed_points.len() - 1 { - Some(smoothed_points[j + 1]) + let next_xz = if j + 1 < smoothed_points.len() { + let (nx, _, nz) = smoothed_points[j + 1]; + Some((nx, nz)) } else { None }; - let rail_block = determine_rail_direction( - (bx, bz), - prev.map(|(x, _, z)| (x, z)), - next.map(|(x, _, z)| (x, z)), - ); + let rail_block = if layer_offset == 0 { + determine_rail_with_slope( + (bx, bz), + prev_xz, + next_xz, + prev_ground, + current_ground, + next_ground, + ) + } else { + determine_rail_direction((bx, bz), prev_xz, next_xz) + }; - editor.set_block(rail_block, bx, 1, bz, None, None); + editor.set_block(rail_block, bx, rail_y, bz, None, None); if bx % 4 == 0 { - editor.set_block(OAK_LOG, bx, 0, bz, None, None); + editor.set_block(OAK_LOG, bx, gravel_y, bz, None, None); } } } } } +/// Choose between a flat or ascending rail block based on the ground-level +/// difference between the previous, current, and next track points. +fn determine_rail_with_slope( + current: (i32, i32), + prev: Option<(i32, i32)>, + next: Option<(i32, i32)>, + prev_ground: i32, + current_ground: i32, + next_ground: i32, +) -> Block { + // Ascending toward the *higher* neighbour. + if next_ground > current_ground { + if let Some((nx, nz)) = next { + return ascending_toward(current, (nx, nz)); + } + } + if prev_ground > current_ground { + if let Some((px, pz)) = prev { + return ascending_toward(current, (px, pz)); + } + } + // Flat section – fall back to standard direction logic. + determine_rail_direction(current, prev, next) +} + +/// Return the ascending rail variant that climbs from `from` toward `to`. +fn ascending_toward(from: (i32, i32), to: (i32, i32)) -> Block { + let (fx, fz) = from; + let (tx, tz) = to; + let dx = tx - fx; + let dz = tz - fz; + if dx.abs() >= dz.abs() { + if dx > 0 { + RAIL_ASCENDING_EAST + } else { + RAIL_ASCENDING_WEST + } + } else if dz < 0 { + RAIL_ASCENDING_NORTH + } else { + RAIL_ASCENDING_SOUTH + } +} + fn smooth_diagonal_rails(points: &[(i32, i32, i32)]) -> Vec<(i32, i32, i32)> { let mut smoothed = Vec::new();