Skip to content
Merged
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
124 changes: 112 additions & 12 deletions src/element_processing/railways.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand Down Expand Up @@ -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::<i32>().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();
Expand All @@ -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();

Expand Down
Loading