How Arnis Generates Highways and Maintains Connectivity Information in Minecraft Worlds

Arnis builds a highway connectivity map before generating any roads, then references that map during generation to determine when slopes, bridges, and elevated decks are needed at intersections.

Arnis transforms OpenStreetMap (OSM) data into detailed Minecraft worlds, and the highway generation process depends heavily on accurate connectivity information. This article explains how the louis-e/arnis repository constructs a connectivity map in src/element_processing/highways.rs and uses it to render realistic multi-level road networks with proper elevation handling.

Building the Highway Connectivity Map

The generation process begins with build_highway_connectivity_map, which iterates through every ProcessedElement::Way containing a highway tag. This function constructs a HashMap<(i32, i32), Vec<i32>> that maps ground coordinates to the set of highway layers present at that location.

// src/element_processing/highways.rs
pub fn build_highway_connectivity_map(elements: &[ProcessedElement])
    -> HighwayConnectivityMap {
    let mut connectivity_map = HashMap::new();

    for element in elements {
        if let ProcessedElement::Way(way) = element {
            if way.tags.contains_key("highway") {
                // Parse the OSM "layer" tag – defaults to 0, negative layers become 0.
                let layer_value = way.tags.get("layer")
                    .and_then(|l| l.parse::<i32>().ok())
                    .unwrap_or(0)
                    .max(0);

                // Record the layer for the way's start and end nodes.
                if !way.nodes.is_empty() {
                    let start = (way.nodes[0].x, way.nodes[0].z);
                    let end   = (way.nodes.last().unwrap().x, way.nodes.last().unwrap().z);
                    connectivity_map.entry(start).or_default().push(layer_value);
                    connectivity_map.entry(end).or_default().push(layer_value);
                }
            }
        }
    }
    connectivity_map
}

The function normalizes OSM layer tags by converting negative values to 0, ensuring that underground layers do not complicate the surface generation logic. Each highway way contributes its layer value to the start and end node coordinates, creating a comprehensive intersection registry.

Storing and Passing Connectivity Data

In src/data_processing.rs, the world generation pipeline constructs the connectivity map once before processing any elements. This single construction avoids redundant calculations and ensures consistent reference data throughout the generation phase.

// src/data_processing.rs (excerpt)
let highway_connectivity = highways::build_highway_connectivity_map(&elements);
// …later, for each element…
if way.tags.contains_key("highway") {
    highways::generate_highways(
        &mut editor,
        &element,
        args,
        &highway_connectivity,
        &flood_fill_cache,
    );
}

The highway_connectivity map is passed by reference to generate_highways for every highway element, allowing the generation logic to query intersection complexity without reprocessing the entire OSM dataset.

Generating Highways with Elevation Logic

The generate_highways function in src/element_processing/highways.rs delegates to generate_highways_internal, which orchestrates the 3D road construction. This function uses the connectivity map to determine slope requirements, calculate elevations, and render physical road structures.

Parsing Layer and Bridge Tags

The internal generator first extracts the layer tag, clamping values to a minimum of 0, and checks for bridge and indoor modifiers. The base elevation is computed as layer * 6 blocks, establishing the vertical offset for the highway deck.

Calculating Elevations and Slopes

For each highway endpoint, the generator calls should_add_slope_at_node to query the connectivity map. This helper returns true when the current way is the only highway at its layer for that coordinate, indicating that a ramp is required to connect to other layers or ground level.

// src/element_processing/highways.rs – slope helper
fn should_add_slope_at_node(
    node: &crate::osm_parser::ProcessedNode,
    current_layer: i32,
    highway_connectivity: &HashMap<(i32, i32), Vec<i32>>,
) -> bool {
    let coord = (node.x, node.z);

    // No connectivity data → add a slope whenever we're not on ground level.
    if highway_connectivity.is_empty() {
        return current_layer != 0;
    }

    // Look up other ways that meet this node.
    if let Some(layers) = highway_connectivity.get(&coord) {
        // Count how many ways share the same layer as this one.
        let same_layer = layers.iter().filter(|&&l| l == current_layer).count();
        // If we're the only way at this layer, we need a ramp.
        return same_layer <= 1 && current_layer != 0;
    }

    // No other ways → ramp needed if we're elevated.
    current_layer != 0
}

The calculate_point_elevation function interpolates height across the way length, applying slope transitions at start and end points when required. Slope length defaults to 35% of the total way length, clamped between 15 and 50 blocks to prevent excessive gradients.

Rendering Road Surfaces and Support Structures

The generator uses Bresenham line algorithms to rasterize the road surface, adding lane stripes, outlines, and zebra crossings where appropriate. For elevated sections, it detects valley bridges by sampling terrain between endpoints; if the terrain dips more than VALLEY_BRIDGE_THRESHOLD (7 blocks), the generator uses a flat bridge deck rather than following terrain contours.

Support pillars are placed periodically under elevated sections using add_highway_support_pillar or add_highway_support_pillar_absolute, ensuring physical plausibility for multi-level road networks. For area highways marked with area=yes, the generator fills the entire polygon using the pre-computed FloodFillCache.

Helper Functions for Connectivity Queries

The highway module provides several utilities that leverage the connectivity map:

  • should_add_slope_at_node – Determines ramp necessity by checking if the current way is isolated at its layer for a given coordinate.
  • calculate_way_length – Computes Euclidean distance between consecutive nodes, informing slope length calculations and bridge valley heuristics.
  • calculate_point_elevation – Interpolates elevation across way points, applying slope transitions at endpoints when connectivity analysis indicates they are needed.

These helpers ensure that every highway segment integrates correctly with the surrounding road network, maintaining vertical consistency at intersections.

Summary

  • Arnis pre-computes a connectivity map using build_highway_connectivity_map in src/element_processing/highways.rs, recording which highway layers exist at each (x, z) coordinate.
  • The map is built once in src/data_processing.rs and passed by reference to all highway generation calls, ensuring consistent intersection handling.
  • Slope and elevation decisions depend on should_add_slope_at_node, which queries the connectivity map to determine if a way is the only one at its layer for a given node.
  • Elevation calculation uses layer * 6 as a base, with slopes covering 35% of way length (clamped 15–50 blocks), and valley bridges triggered when terrain dips exceed 7 blocks.
  • Rendering includes Bresenham line rasterization, support pillars for elevated sections, and flood-fill for area highways using the cached connectivity data.

Frequently Asked Questions

How does Arnis determine where to place highway slopes?

Arnis checks the connectivity map using should_add_slope_at_node to see if the current highway way is the only one at its specific layer for a given coordinate. If the way is isolated at an elevated layer (not ground level), the function returns true and the generator creates a slope at that endpoint to connect with other layers or the ground.

What is the purpose of the highway connectivity map in Arnis?

The connectivity map serves as a lookup table that records which highway layers exist at every (x, z) coordinate where highways intersect or terminate. Built once before generation begins in src/data_processing.rs, this HashMap<(i32, i32), Vec<i32>> allows the renderer to make informed decisions about elevation changes, slope placement, and bridge construction without re-parsing the entire OSM dataset for each highway segment.

How does Arnis handle elevation calculations for multi-level highways?

Arnis calculates base elevation as layer * 6 blocks, where the layer value is extracted from OSM tags and clamped to a minimum of 0. For slope transitions, the system uses calculate_point_elevation to interpolate heights across the way length, applying gradients at start and end points when the connectivity map indicates they are needed. Slope lengths default to 35% of the total way length but are clamped between 15 and 50 blocks to maintain realistic gradients.

What triggers the creation of valley bridges versus standard elevated highways?

During generation, Arnis samples the terrain between highway endpoints to detect significant elevation drops. If the terrain dips more than VALLEY_BRIDGE_THRESHOLD (defined as 7 blocks) below the highway deck level, the generator switches from terrain-following mode to a flat bridge deck construction. This creates realistic valley bridges that span deep terrain cuts without following the valley floor, while support pillars are placed periodically underneath to maintain physical plausibility.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →