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:
Really cool stuff mate. Thanks.
ReplyDeleteI think its a nice foundation to start with. However I get the feeling that from this point on it becomes exponentially more difficult to produce higher quality results. For example, for a procedural interior to be interesting one would probably need to look at introducing diagonal architecture and maybe even multi-level features such as balconies, etc.
Do you plan to expand into those territories? Your opinion on what one would tackle next to improve the architectural quality?
It is not necessarily more difficult than this. The same techniques shown here can be used to produce complex structures spanning multiple floors, also with angled components.
ReplyDeleteThis post is mostly about how inverting the meaning of occlusion queries can help you write simpler grammars. The simplicity of the example may make you think anything else would be a lot more difficult, but it is not really the case.
Also about what makes architecture interesting, it is hard to know. If you look at ancient architecture you will see the floorplans are rather simple, still they may be very beautiful and interesting buildings.
Maybe it is just me, but your L-System grammar is completely non-obvious.
ReplyDeleteWhat are the reserved keywords? What are the scope of the variables? Why are there three "room" functions in the first example? Are they functions? What are the arguments to the "scale" function? What does "divide z [depth*0.5] room [depth*0.5] room" even mean? What does the "divide" function take 5 arguments?
The article is nice, but it would be even better if someone could follow the code. And this is coming from a computer scientist. I can make lots of (probably incorrect) assumptions about what I think your programming language is doing, but it would be better if I didn't have to...
Yes I see your point. I have an earlier post that may be able to help: http://procworld.blogspot.ca/2011/03/little-blue-house-in-prairie.html
DeleteI cannot get into a full description of this language here, but I can cover the basics.
L-System languages are different than conventional general purpose languages like C or Java. You need to look at them from a different perspective, otherwise you will be confused. There are no functions, no arguments. The core idea is you have a collection of rules, each rule may be defined using other rules.
The three "room" constructs you see are three rules. They have different activation conditions, which come right after the ":". Since they will activate based on different criteria, any other rule that includes "room" may get any of the three possible definitions at execution time.
The rules take care of the execution model. Still you want these programs to do some real work. Old-school L-Systems use Turtle graphics to produce a visual output. With architecture L-Systems it is pretty much the same, but the turtle has evolved into a different animal.
We now call it the "Scope". You can think of it as a 3D box. In turtle-based L-Systems you would use the turtle to draw lines. In an architectural L-System you can instance geometry elements like a brick or a window using the scope as a reference.
The language includes a few statements to modify the scope's position and size. The most used are "move", "scale" and "rotate". These statements take parameters that make sense to the operation. For instance "move" takes a 3D vector. If you do "move [0, 1, 0]" it means you want the scope to move one meter in the Y direction.
It is really about the scope, hence there are many assumptions based on this. When you see a percentage, it means a percentage of the same parameter for the scope. If you do "scale [100%, 50%, 100%]" it means you a reducing the scope Y dimension by half. There are some reserved words for the scope parameters as well, like "width", "height" and "depth". They all refer to the dimensions of the scope. So "scale [100%, 50%, 100%]" is the same as "scale [width, height/2, depth]"
And last, there is an entirely different set of operations you can do with the scope which will break it into smaller scopes. They also allow you to specify which rule will run inside the new scopes. You see three of these statements here: "divide", "repeat" and "select". Divide will break the scope into a sequence of smaller scopes. The sequence is made of pairs: the size of the sub-scope and a rule for it. For instance:
divide y [3] foundation [5] mainlevel [4] roof
Means you will divide along the Y axis. The first division will be 3 meters (it is a 1D vector, hence the square brackets) and it will run a matching "foundation" rule, the second one 5 meters, then 4 meters. In theory you could have hundreds of entries in a single divide sequence.
Regarding variables and their lifespan (I won't use the term scope to avoid any confusions with the 3D Scope), it is straightforward. Once declared, a variable or constant will be seen by all nested rules.
What I have explained here covers around 80% of the language. There are some special keywords like "rnd", a random number between 0 and 1, or "occludded" which will return positive if the scope is occluded.
I hope this will help you revisit the post and maybe undestand the code.
only one `rnd`? what if I want have two independent random numbers?
DeleteEvery time "rnd" is evaluated it produces a different number. This rule for instance will be picked 50% of the time:
Deleterule : rnd > 0.5 {
// something...
}
MCeperoG, thanks for the in-depth explanation. I see now that your rules are basically context-sensitive grammars. Makes more sense. Also, the scope explanation was particularly illuminating. I would have been lost without that.
Delete-- Grandparent Anonymous Commenter
You got it, they are context sensitive grammars.
DeleteThis is really neat, seeing inside your mind as you troubleshoot and create different algorithms for this awesome project! Keep it up!
ReplyDeleteThis is like magical. I suppose its pretty hard to read once it gets really complicated but I definitely seen an advantage of the set logic that its using to draw. If I think about it long it enough there maybe a way to make a game out of it.
ReplyDeleteThis is amazingly interesting!
ReplyDeleteHow do you plan to set meaning to these floorplans? A building generally isn't a bunch of rooms squished together so much as it's a bunch of needed functions squished together, with room wrapping around them.
The reason I say this is that every map generator I've seen, and a good chunk of procedurally-generated buildings I've seen in video games, are immersion breaking as soon as we look inside.
I am looking at many floor-plans right now, mainly castles, monasteries and temples. Also multi-level villas. I think they are doable. I will post about this in the future, as soon as I have something to show.
DeleteI worked with this subdivision technique for an older project. I like your results! One trick you should know is that if instead of dividing into two, you divide into three, with the center room very thin, you'll get a nice structure of halls.
ReplyDeleteThanks for the tip, I will give it a try.
DeleteWhile technically these are interesting and cool, atm as actual houses these would be quite confusing and pain to live in :D
ReplyDeleteI know this isn't anything finished, but I'll put my two cents in:
I haven't seen a house with all rooms having doors on every wall. In most houses I've visited, there's usually only one or two routes to go from point A to B. Think about having some kind of hub room/rooms (living room, passages, occasionally kitchen etc.) and other rooms accessible from there. Most bedrooms have only 1 or 2 doors, one to the "hub" and one for another bedroom.
Doors don't have to be in the middle of a wall. In fact, having a door in the middle of a wall limits the decorating choices.
This kind of approach (dividing) leaves no option for other than rectangular rooms. I haven't tested it, but maybe first divide house into unit rooms (like you did the first step), then remove walls by spesific criteria. Simply randomly deleting walls would result in kind of absurd design, but maybe delete rectangular areas of walls would result something more sensible. It's hard to explain what I mean by that so hopefully you understand ;)
Again, keep in mind this is about occlusion rules and snap planes. It does not mean this is how you build realistic interiors. Doors were put in the middle as an example, also they don't have to be on every wall.
DeleteRemember, what I am showing here is a brick. I have built a simple structure with it to show how the brick can be used. You don't need to over-analyze the structure, see what's wrong with it or how can it be improved, it does not mean anything.
Okay, maybe I overanalyzed a bit... I have this bad habit I always analyze everything and try to find what's wrong even for things which aren't meant to be improved.
DeleteThank you for showing more of the Voxel Studio, and what can be achieved with it :)
First I have to say this is an absolutly astonishing project. You're terrain generation looks amazing. Regarding buildings I liked the rustic house in your post from 25th february.
ReplyDeleteWhen I look at the floorplan and facade of your building in the post above, I'm asking myself if using more real-life architectural rules will produce more believable buildings. Looking at renaissance villas (or many other old age public buildings) one can see regulary distributed windows with arched or squared shapes according to floors.