How Arnis Building Interior Generation Works: Rooms, Layouts, and Doorways

The Arnis building interior generation module creates deterministic room layouts by tiling 23×23 character pattern maps across each floor, mapping characters like 'D' to door blocks and 'W' to interior walls, then extending walls and doorframes vertically to the ceiling.

Arnis is an open-source tool that transforms real-world geospatial data into detailed Minecraft worlds. Its building interior generation system, implemented in src/element_processing/subprocessor/buildings_interior.rs, converts empty building shells into furnished structures with realistic room divisions, doorways, and multi-floor layouts.

Preparation and Floor Selection

Before generating rooms, the system validates that a building can support an interior. In generate_building_interior (lines 99-101), the code immediately returns for any structure smaller than 8×8 blocks:

if width < 8 || depth < 8 {
    return;
}

For valid buildings, the system converts the floor footprint into a HashSet<(i32, i32)> for O(1) membership tests (lines 103-105). It then applies a 2-block buffer from the outer walls to ensure furniture never touches the perimeter, calculating interior bounds as interior_min_x = min_x + buffer and interior_max_x = max_x - buffer (lines 107-113).

Pattern-Based Tiling System

The core room generation uses 23×23 character pattern maps that are tiled across each floor. Each floor is processed using two layers: one for the base room layout and a second for upper elements like door tops and tall furniture.

Floor-Specific Pattern Selection

The system selects different patterns based on floor level and building condition (lines 138-152):

  • Ground floor: Uses INTERIOR1_LAYER1 and INTERIOR1_LAYER2
  • Upper floors: Uses INTERIOR2_LAYER1 and INTERIOR2_LAYER2
  • Abandoned buildings: Uses ABANDONED_LAYER1 and ABANDONED_LAYER2

Seamless Tiling with Modulo Arithmetic

To cover arbitrarily large rooms without seams, the code uses modulo arithmetic with the floor index as an offset (lines 170-176):

let pattern_x = ((x - interior_min_x + floor_index as i32) % pattern_width
                + pattern_width) % pattern_width;
let pattern_z = ((z - interior_min_z + floor_index as i32) % pattern_height
                + pattern_height) % pattern_height;

This ensures that the 23×23 motif repeats deterministically across the interior space while shifting slightly per floor to create variety.

Character-to-Block Mapping

Each character in the pattern maps to a specific Minecraft block via get_interior_block (lines 30-74):

  • 'W' → The building's wall block (interior walls match exterior material)
  • 'D' → Dark oak doors; layer 1 creates DARK_OAK_DOOR_LOWER and layer 2 creates DARK_OAK_DOOR_UPPER (lines 53-60)
  • 'U' → Oak fence (often used for table legs or railings)
  • 'S' → Oak stairs (furniture or seating)
  • 'B' → Bookshelf
  • 'C' → Chest
  • ' ' (space) → Air (empty floor space)

Blocks are placed using editor.set_block_absolute at floor_y + y_offset for layer 1 and floor_y + 1 for layer 2 (lines 82-92).

Doorway and Wall Extension Logic

After tiling the floor pattern, the system ensures walls and doorways extend vertically to the ceiling. During the first layer placement, the code records positions:

  • wall_positions stores coordinates where 'W' was placed (lines 92-99)
  • door_positions stores coordinates where 'D' was placed

After processing both layers, a vertical extension loop stacks wall blocks from the floor up to the ceiling (lines 166-170):

for (x, z) in &wall_positions {
    for y in (floor_y + y_offset + 2)..=current_floor_ceiling {
        editor.set_block_absolute(wall_block, *x, y + abs_terrain_offset, *z, None, None);
    }
}

The same logic applies to door positions, ensuring doorframes create clean vertical openings through the ceiling. This guarantees that interior walls properly partition rooms and doorways connect them realistically across multiple floors.

Integration with the Building Pipeline

The interior generator is invoked from src/element_processing/buildings.rs when processing complete building shells. Around line 2534, the pipeline checks user arguments and building size before calling generate_building_interior:

if args.interior && !skip_interior && cached_floor_area.len() > 100 {
    let floor_levels = calculate_floor_levels(start_y_offset, building_height);
    generate_building_interior(
        editor,
        &cached_floor_area,
        bounds.min_x,
        bounds.min_z,
        bounds.max_x,
        bounds.max_z,
        start_y_offset,
        building_height,
        style.wall_block,
        &floor_levels,
        args,
        element,
        abs_terrain_offset,
        is_abandoned_building,
    );
}

This integration ensures that only sufficiently large buildings (over 100 blocks in floor area) with the interior flag enabled receive generated interiors, while preserving the building's external architectural style through the wall_block parameter.

Summary

  • Size Filtering: Interiors are only generated for buildings larger than 8×8 blocks with sufficient floor area, enforced by early returns in generate_building_interior.
  • Pattern Tiling: Room layouts are created by tiling 23×23 character patterns using modulo arithmetic, with different patterns for ground floors, upper floors, and abandoned buildings.
  • Block Mapping: Characters map to specific blocks via get_interior_block, with 'D' creating dark oak door halves and 'W' creating interior walls matching the exterior material.
  • Vertical Extension: Walls and doorframes are extended from the floor tiles up to the ceiling using recorded positions from the tiling phase, ensuring solid room partitions.
  • Pipeline Integration: The system is called from buildings.rs when the interior argument is enabled and the building meets size thresholds.

Frequently Asked Questions

What is the minimum building size for interior generation?

Buildings must be at least 8 blocks wide and 8 blocks deep to receive interiors. The generate_building_interior function in src/element_processing/subprocessor/buildings_interior.rs immediately returns if width < 8 || depth < 8, as smaller structures are too cramped for meaningful room layouts.

How does Arnis ensure interior walls match the building's exterior material?

The get_interior_block function maps the character 'W' to the wall_block parameter passed from the building generator. This parameter contains the block type used for the building's exterior walls (such as brick or concrete), ensuring that interior partition walls visually match the external architecture for a cohesive aesthetic.

Why does the interior generation use a 2-block buffer from the outer walls?

The generator subtracts a 2-block buffer from the building perimeter to create interior_min_x, interior_max_x, etc. This ensures that furniture and interior walls never touch the outer building walls, preventing visual clipping and maintaining proper clearance for windows and structural elements on the building exterior.

Can custom interior patterns be added to Arnis?

Yes, the pattern system uses constant 23×23 character arrays defined in buildings_interior.rs. Developers can define new INTERIOR1_LAYER1, INTERIOR2_LAYER1, or ABANDONED_LAYER1 arrays with custom room layouts, or add new character mappings in get_interior_block to introduce new furniture types and architectural features.

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 →