Birdoggydog's Builds

Assembling walkable houses with real rooms from a plan

A house plan becomes a standing building in a 64 m cell, with rooms inside, swinging doors, lamps, lit windows and a 21-triangle far version.

A house plan from the building recipe now turns into a standing house you can walk into. I built a cell node that takes any number of plans and builds one 64 m cell: walls, floors and roofs from the art kit’s building members, collision, rooms inside, doors that swing, lamps, lit windows at night and a cheap far version for anything past 320 m. Nothing in the world places these yet, so for now a test makes the cell and adds the plans.

49 seconds in first person on a street of eight houses: a window, the hall and its stair, the short door upstairs, evening falling to full night, then the view from 400 m and the switch back to the full houses at 320 m.
Twelve frames from the film: the street, a window, the hall with its stair, the landing, evening, night with lit windows, and the far view.
The film as twelve frames, in order.

Why build real rooms?

The plans are data, and the buildings had to exist in the game before the world could place them. The open question was whether the shader should paint a room behind each window or whether every house should have rooms that exist. I picked real rooms. A window shows a room you can walk to, and the cost is a lot of geometry, which is where most of the numbers below come from.

How does a plan become a house?

Two pure functions do the laying out, and the node does the building.

The rooms come first. Each storey gets a slab per square. A stair climbs a run of 3 squares from the ground storey to the first, and only there, with its well guarded by rails. Rooms pair up along x, a row at a time, and on the ground floor the stair’s run and the row beside it are the hall. Partitions are short pieces placed back to back. A spanning tree connects the rooms so every one can be reached: on the ground the connections are open passages, and upstairs from the landing each one is a short door 60% of the time, picked from a seeded stream, and an open passage otherwise. Every upper square gets a ceiling and every room gets a sconce.

The assembly then puts down the plan’s walls, roof and chimney, the rooms, the ground door from the door wall’s doorway, and the lamps and signs from the plan’s sockets. Members are found by a query (a short piece is described by its kind, span, height, base, ends and door), never by file name. Where the kit has no member yet, the assembler stands one in and records it. The one hole it can’t cover is the parapet elbow, which is reported as a fault.

How do 12 houses stay cheap enough to draw?

The cell builds one MultiMeshInstance3D per member, per level of detail and per role (shell or room). Every batch shares the cell’s custom_aabb, so they cull together. Each house’s numbers (hue, value, state, seed) go in each instance’s custom data, which means changing a house rewrites data and moves nothing.

Levels of detail are measured from the cell’s middle. The full members go to 64 m, the next level to 180 m, the third to 320 m, and beyond that a far block takes over. Rooms exist only inside 64 m. Shadows come only from the full members. Ground doors show to 64 m and inner doors to 30 m.

Collision is a single StaticBody3D of box shapes, one per member box. A box whose transform is scaled gets a box of its own size and an orthonormalized transform.

Why did the first street get sent back?

A judge went through the film and found three faults, and I had them fixed before it landed.

Every house door had a glowing cyan bar. I had reused the dungeon’s blast door, and that door draws a lit status strip as a child. In fog and in the dark of a doorway only the strip showed, so it floated. I replaced it with a HouseDoor that extends the dungeon door, so the lock, sounds and save keep working. It has stained planks, a street door banded in iron, an inner door painted with timber rails, a brass pull on each side, and nothing lit. Each swings in toward the house’s middle so no leaf goes through a partition. Short upstairs doors (2.60 m) also get a gate that lets the party and any body the door-size rule allows through. A large body is held.

The evening caption said lamps and windows glowed, and the film showed one lamp and no windows. The cause was in the code that writes a look’s numbers. It was keyed on play time only, and the film holds the clock at a chosen hour with play held, so night stayed at zero. It’s now keyed on the time of day as well. Every ground door gets a lantern, and a glass pane glows when that window’s own random draw is under a share set by the house’s condition. That’s about 60% of a peak house’s windows and 34% of a decayed one’s.

The far block popped in color, roof shape and shading at the switch. It’s one mesh per cell with each building’s wings as hulls, a gable or hip roof and its chimneys as boxes, 21 triangles per building. I fixed it three ways. Each face now carries its building’s packed numbers in CUSTOM0, so the shader keeps each house’s color across the switch. The paint comes from the wall and slope members’ lowest level. And the normals are flat like the members, which removed a diagonal light and dark split on a front. When a whole building’s numbers change, the far block is rebuilt into the same mesh on the next frame, because giving the batches a new mesh as their visibility parent broke the engine’s dependency tracking.

The second look agreed. It still saw a cold blue-white streak along the ridges at full night, far roofs that widen at the switch because they have no eaves, and a few plain faces on the interior pieces. All three are on the list, and none is fixed.

What does it cost?

For a cell of 12 houses with rooms, planning takes 1.2 ms per building and building the whole cell takes 300 to 780 ms. That’s 2349 instances in 171 batches, 1543 collision boxes, 740 room pieces and 40 doors. Triangles are 437,000, 240,000 and 43,000 at the three levels, so a house near the camera is about 36,000 triangles against the 5,000 I planned for. The far block is 196 triangles and one draw from 400 m.

Draws near the camera came down from 453 to 304, and I wanted 60 with shadows. That’s not met, so I filed it. The assembly test passes 31 checks headless and 34 in a window, where the read-back of the instance data works.