top of page
Rechercher

Procedural Creation of Cliff Village



This project was created as part of one of my university courses. The objective was to create an environment of choice by mixing the different techniques learned during the course and our personal skills. Being an aspiring FX artist, I challenged myself with a procedural touch to the project. In this text, I will try to briefly explain the organization of my first project of this kind on Houdini.


My teammates, Ariane Tremblay-Lecomte & Max Spiegel, and I set out to create a village that would be suspended from the side of a cliff. The perched houses are random variations of two different houses created in Maya. Each house is divided into modules which can be easily rearranged into a new configuration to have a new general shape.

The two model houses and their modules


Once the modeling is complete. It's time to move into Houdini. The plan is to prepare some Compiled Blocks for each model house. Those Compiled Blocks will take a seed value as input and return a random version of the house based on that seed.


 

The technique is basically the same for both houses. Generating several versions of the house by arranging the predefined modules differently. These modules themselves have undergone a random generation using submodules, such as windows or building extensions. This will bring different levels of randomness in the procedural generation and thus have dozens of unique houses.


To randomly choose the unique position of each module, I took the time to develop a VEX-snippet that allows the user to define an arbitrary number of positions for the geometry and randomly choose one of these positions. The whole thing is linked to a custom interface on the Attribute Wrangle node. This makes it easy to test the positions and define a custom weight distribution in the random selection.

Custom interface for the Attribute Wrangle


On the other hand, this technique only works well to move one module in relation to another. If several modules are moved at the same time, this method does not try to avoid interpenetration. To avoid it while generating more complex assemblies, I had to fall back on a simpler technique: simply rearrange the modules by hand with Transform nodes in several versions. These different versions are chosen using a Switch node which reads the random value of an Attribute Wrangle node. This choice to adopt a "less intelligent" technique was motivated by time constraints.


 

Each module has cubes which are markers for the windows. These markers are replaced with a random version of a window inside a ForEach Named Primitive loop. Each cube finds its direction vector by sampling the normal of the closest module surface in this loop. The position of the cube, combined with the direction vector, generates a 4x4 transformation matrix which is applied to the geometry of one of the window variations.


 

Once a new generation of a home is created. it is moved on to another Compiled Block which takes care of choosing which house is selected (always randomly) and moving the geometry to its final position in the scene. This position is given to the Compiled Block by a point in the scene with a normal vector to indicate the position and orientation. Similar to the windows, a transformation matrix is ​​created and then applied.


To define the position of each of its points, I only used my colleague's blocking. The temporary houses he had placed were converted into these famous reference points. The normal vector comes from the average of the normal vector of the closest 20 points of the cliff. Everything is calculated in an Attribute Wrangle. But, to give more control to the user, I added interface parameters to offset the position, the direction of the normal, and the seed of each point.


Interface to modify the procedural houses






Set of houses created procedurally










 

Once each house in the project is generated, we enter the second section of the project: procedural generation of the suspension bridges. Each bridge must join two houses that are generally at the same height. There is one more difficulty, the string bridges will be simulated with the Vellum module. This means that the bridge must be generated in two stages. First, the structure of the bridge has to be roughly defined in simple lines and points that are easy to simulate. Then, the deformed definition of the bridge.


So, to create the definition of the bridges, markers are required in the modules to indicate the attachment points. These markers are rectangular polygons where each vertex is itself an attachment point for one part of the bridge. Using a Sort node, the point numbers are reordered to be in the same order from marker to marker. Then, a line is created from one marker to another with an Add node, Houdini can just look for the points that have the same number (previously stored in an attribute). The lines created are divided according to the distance between the two markers to have equidistant sections between the different bridges. Finally, each division is iterated over it to connect the different lines together, and thus form the definition of a bridge.

A bridge defined by very basic polylines


 

Before generating the final geometry, the bridges must be simulated. The simulation process is made up of just 2 nodes. The Vellum Configure Hair node and the Vellum Solver node. Hard to make it simpler. The ends of the bridge were previously stored in a group to serve as pins in the simulation. Everything is surrounded by Transform nodes to adapt the size of the simulation according to the rule that 1u = 1m.



Here is a pro-tip that I learned during this project that opened my eyes. During a Vellum simulation, the mass of the object simulated has a major impact on the calculation of stiffness constraints. A mass too low (<1) can make the simulated object awkwardly elastic. In my case, increasing the mass of the bridge greatly reduced the elasticity and thus divided the distance between the lowest point and the initial position by a factor of 2!


 

In any case, once the bridges are simulated, it's time to move on to the second step: creating the final geometry. Each segment of a bridge must be replaced by a different element. The lower transverse lines will become planks, the side transversals will be a special geometry that will serve as a link between the planks and the longitudinal lines that will be simple ropes.


For strings, a simple Polywire node does the job very well. On the other hand, the boards require determining the correct orientation for each of them, in addition to moving them. By relating the position of the various points that surrounds a segment of a board, it is possible to define the orientation vectors N (normal / direction of the board) and Up (orientation on the axis defined by N). Just gotta make the vectors orthogonal to each other to create a transformation matrix using the maketransform() function.


For the connection between long ropes and planks. I am deforming a very simple geometry that will pass through a Polywire to obtain a rope/knot object. To distort the simple geometry, the technique is very similar to planks. But, since it is a geometry that must connect two pieces that move independently of each other, the calculation of the orientation must be carried out for both ends and applied independently.



1. Un-deformed geometry

2. Deformed geometry.

3. With the Polywire node.





 

Once all the elements are together, we get dynamic and procedural bridges! Combined with the modular houses, an entire village can be created.


 

Each element of this village is assigned a material that will be recognized in Maya to have a projected texture. The only elements of this project that required a generation of UVs were the set of strings created using the Polywire node. At the exit of the node, the geometry has UVs. Unfortunately, these UVs take the same space on the UV grid for each cable, regardless of their length. This results in distorted textures across all cables in the scene. This is a problem easily fixed with a UV transformation based on the total length of the cable in another Attribute Wrangle.


Finally, we come to the end of my project. I have deliberately omitted several procedures and technical details in an attempt to shorten this text to the major principles of the project. If I were to explain all of my 357 nodes and 681 lines of VEX (spread over 89 Attribute Wrangle), I would never finish this text in a reasonable time frame. On the other hand, it is an exercise that I will be happy to undertake for another, simpler, project.



Preview of all 357 nodes


bottom of page