There are many papers and demos about creating building volumes and
their facades. There is not much about how to build their interiors.
Today I want to share a simple technique I found. It allows to break a
space into multiple rooms. This alone is not enough to build interesting
interiors, but it can be successfully combined with halls, staircases
and more dramatic spaces.
This is what we are going for:
The idea is to divide a space into rooms of random sizes. The rooms
should be connected by doors. The outer walls should have windows.
As you will see, the real challenge is to make sure the doors and
windows will appear in the right places. A door should not appear in the
middle of a wall intersection. Windows must properly align with room
sizes.
To show this technique I will use the L-System langue I have developed
for Voxel Studio. I hope the code is simple enough to convey the idea
behind it.
We will start by breaking the space into even rooms:
You can see the output of the "testrooms" module here:
It is a simple, neat idea. The "room" module will divide recursively
while the room size remains bigger than 5 (meters). Once the room cannot
be subdivided anymore, the third definition for "room" kicks in and
instances some walls.
Let's add some variety. Instead of splitting rooms in half we can choose
a more interesting ratio. We can also make the subdivision random.
Rooms could stop dividing even if they were bigger than 5 meters, which
will lead to bigger rooms:
Not much changed since the previous version. The key addition was a
third constraint in the "room" rules, selecting the rule only if (rnd
< 0.8). This means 20% of the time a room, no matter how big, could
stop subdividing.
The results are already more interesting. Also, multiple runs of the
"testrooms" module produce different layouts. Here is one example:
This looks close to what we wanted, it should be just adding doors to
the inner walls and windows to the outer ones. Well, not so fast. While
the rooms are properly identified, there is something going on with the
walls. If we contract each room a little bit the problem becomes
apparent:
As you can see, each room has its own set of four walls. When a large
room appears besides a series of smaller rooms, we get a large wall
running over the smaller walls. If we had doors there, they would be
hidden behind the large wall.
The simplest solution would be just to hide the large wall when we
detect it has been occluded by a smaller wall. This can be done by
adding an "occlusion" conditional to the walls.
Occlusion tests work by intersecting volumes. Even the slightest
intersection will result in a positive occlusion. For this reason, we
will shrink our wall occluders a little bit. This way wall intersections
in the corners will not result in positive occlusion. We will not worry
about the little gaps this will create for now.
Here is the modified code:
There are now two rules for the walls. One for a wall that is occluded,
and a second for a wall that does not intersect any other wall. To spot
which walls were occluded, I chose to render them as shorter, wider
walls. They appear in red below:
As you can see, we could start punching door holes in the remaining
walls and they would not be hidden by a parallel wall coming from a
neighbor cell. Still, finding the proper location for these holes would
be tricky. The hole must not appear in the intersection of two
perpendicular walls.
Instead finding a solution for this, let's look closely at the walls
marked in red. These were the walls removed by the occlusion test.
You will see they never intersect each other. We could open a hole
anywhere in the wall and it would never be in front of another wall.
What if we reverse our logic, that is, we hide the non occluded walls
and use the occluded ones for our rooms?
The following code does that, it also adds a door opening in the middle of the walls:
Here is a render of the new rules:
You will notice the outer walls are gone. This is because they are not
occluded. This actually helps, since we are adding windows there
anyway.
There is another advantage. Remember the gaps we had to create in the
walls so they would not occlude by the corners? Only occluding walls
need to leave gaps, and since now they are not even visible, the gaps
are no longer a problem.
Let's add outer walls with some window openings:
The previous code adds a call to the "outerwalls" module, which will
target the sides of the scope box and repeat a series of windows at
constant intervals. You can see the results here:
The red arrow points to a new problem. Since the windows repeat
regularly, they often appear in the middle of walls. The solution is to
use "snap" planes. Snap planes, just like occlusion rules, are recurrent
techniques in procedural architecture. A snap plane is an imaginary
line that influences the subdivision and repetition of modules.
We can make the repeating windows snap to the walls. This way they will
still repeat, but the repetition intervals will be constrained by the
snap lines.
This is how you would specify them:
The lines that matter are highlighted in red. Starting at the bottom,
the "snap" statement declares a new snap plane and names it "wall". Then
the repeat statement includes the "wall" snap plane. It means it will
repeat every 5 meters or a "wall" snap plane is found, whichever comes
first. And last, the "module" statement to call the "outerwalls" module
was replaced by "defer". Defer makes sure all the other rules have run
before the module is executed. This is necessary because all the walls
need to be in place before adding any windows.
As a result, the repetition now is now properly constrained within the rooms: