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:
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: