About CityEngine Blog


CityEngine is a great tool that is able to create large scale models, mainly of cityscapes, quickly, and with the ability to make adjustments based on a rule file in a procedural manner.

I hope to show you some of the work I have done with CityEngine creating a variety of models across a range of projects. I have mainly used the software for planning applications but have learnt a great deal of the potential for other applications.

I want to concentrate on the writing of rule files which is the core use of CityEngine. Without rule files no 3D content can be generated and this is very important to understand. I will also strive to bring news and updates regarding CityEngine as well.

I hope you find what I share useful and please feel free to share and contribute your thoughts and experience.


Sunday, 8 September 2013

CityEngine Starter Project 5 - Conditional and stochastic rules, functions and complex splits



Conditional and stochastic rules

The parcel shapes created by the road network were intentionally regular in appearance. This allowed me to apply operations more easily to get the desired outcome. Now I wanted to create building in the commercial zone, with the appearance of office or mixed use.

An innerRect operation was first applied to guarantee that the shape was completely regular, followed by a different type of Align operation that aligns the scope to the longest edge. Hidden Attributes for Length and Width were Set and Reported to give the shape dimensions that I could use to understand more about the geometry.

CommercialLot -->
     innerRect
     alignScopeToGeometry(yUp, 0, longest)
     set(Length, scope.sx)
     set(Width, scope.sz)
     report("B Length (m)", Length)
     report("C Width (m)", Width)
     CommercialLot1

Instead of using a coverage slider to generate a building footprint, I used a Setback operation within a conditional (case) statement that applied a value to either the street left, or right selectors. A stochastic rule is one where it includes an element of randomness. In the example below the setback value is determined by a series of nested conditional statements that include a stochastic element. The code states that 20% of the input geometry should have no Setback and are immediately passed to a successor past even the next successor. If the shape is not part of the randomly chosen 20% then if the parcel area is less than 500(m2) there is also no setback. If the area is greater than 500 but the width less than 15 then 50% have a setback applied to the street.left and 50% to the street.right. If the area is greater than 500 and has a width of more than 15 then 50% have a Setback applied to the street.left with a value of the scope length * 0.2 and the remainder have the same but applied to the street.right.

Whilst this may sound a little confusing at first, once it had been coded it could easily be modified to change elements. Note the indentation levels of the nested case statements and the use of ‘else’:

CommercialLot1 -->
     20% : Commercial3
          else :
              case ParcelArea < 500 : CommercialLot3
              case Width < 15 :
                   50% : setback(4){street.left : NIL | remainder : CommercialLot2}
                   else :    setback(4){street.right : NIL | remainder : CommercialLot2}
              else :
                   50% : setback(scope.sz*0.2){street.left : NIL | remainder : CommercialLot2}
                   else :    setback(scope.sz*0.2){street.right : NIL | remainder : CommercialLot2}

Stochastic rules offer variation each time models are regenerated and provides an easy way to have controlled randomness. The power of CityEngine is using this type of rule so that many different types of model are created in a procedural way.

To continue creating the footprints I also added a conditional and stochastic piece of code that applied a street.back Setback operation, similar to the one above.

CommercialLot2 -->
     20% : CommercialLot3
          else :
              case Length > 30 : setback(scope.sx*0.3){street.back : NIL | remainder : CommercialLot3}
              else :
                   50% : setback(4){street.back : NIL | remainder : CommercialLot3}            
                   elseCommercialLot3



I was happy with the footprints that had now been created, so I now applied the same code that generated the bases for the residential shapes. Often, buildings within a business or commercial zone can be quite large and so creating a base can be problematic due to the size of the area the footprint covers and the variation of the terrain across this wide space. Again, in this example I have chosen a fairly flat area on purpose but in reality earthworks would more than likely need to be carried out to achieve an optimal result. Coding this type of platform would require further thought.

CommercialLot3 -->
     extrude(world.y, 5)
     split(y){~0.25 : CentreLot4 BaseKeeper | 5 : NIL}

CentreLot4 -->
     comp(f){top : alignScopeToGeometry(yUp, auto) CentreLot5}


Functions and complex splits

To apply a building height to the footprints, I also wanted to add a degree of controlled randomness. In the example here I generated a function (under the Function heading) called getHeight. A function generally provides a value that can be used later, either through a calculation or in this case as a stochastic value. The function states that the value for getHeight in 25% of the time will be 8.5m, 50% of the time it will be 12.5m and then in the remaining 25% (else) it will be 16.5m.

## Functions ##

getHeight =
     25% : 8.5
     50% : 12.5
     else : 16.5

Functions can be used several time in the code. The idea being that they generate a value that is evaluated every call and can be utilised within the code without having to re-write code over and over.

Using the getHeight value the footprints were extruded and I now wanted to add more building detail. After the Extrude operation I used a Split operation to cut the building in the ‘y’ (vertical) direction creating a number of floors:

CommercialLot5 -->
     extrude(getHeight)          
     split(y){4.5 : GroundFloor | {~4 : UpperFloor(split.index, split.total)}*}    
           
The Split takes the bottom 4.5m and provides a successor called GroundFloor. The reminder of the shape is split into UpperFloor successors that are approximately 4m (note the ~ approx and * repeat characters). I also included a special feature of the Split operation by passing the split.index and split.total values to the UpperFloor successor. These values allowed me to evaluate how many UpperFloor successors were created and what sequence they were generated in. As an example, if the building was extruded to 12.5m then the ground  floor would take 4.5m away and then the remaining 8m would be split into 2 floors. The total number of UpperFloors would be 2 and the top floor would have an index of 1 (index starts at 0).



I wanted to make the commercial building appear more realistic and decided that I would generate a simple façade based of the addition of modelling operations rather than adding a texture. This required coding a number of fairly complex Split Operations. The GroundFloor would be made to look more like a retail frontage and the UpperFloors like office space.

First the GroundFloor successor had a Comp(f) operation applied that selected just the side:

GroundFloor -->
     comp(f){side: GroundFloor1}

As I wanted the GroundFloor to look like a retail frontage I generated a Split operation that would produce a series of large windows and an entrance. GroundFloor is split in the longest length (x) direction into a series of successors, 0.5m for a wall, approx 5m for a large window, 3m for an entrance then a repeating approx 5m large window and finally a 0.5m wall:

GroundFloor1 -->
     split(x){0.5 : Wall | ~5 : LargeWindow | 3 : Entrance | {~5 : LargeWindow}* | 0.5 : Wall}




The UpperFloor now also needed some similar detailing but in this case I also wanted to take the top floor to be able to generate a roof. Using the split.index and split.total values within a conditional statement allowed me to find what is considered the top floor. In the example the statement asks is the index (idx) equals the total (n) minus 1. Following the example earlier(and shown above), the 2 UpperFloors have an index of 0 and 1. The total is 2, so the total minus 1 is 1. Therefore the floor with the index of 1 is the upper floor. The power of this statement is that it would work on a building with any number of floors. A similar statement could potentially be used to also find a wide range of returns e.g. the top two floors, the top 20% of floors, alternating floors etc etc.

UpperFloor(idx,n) -->            
     case idx == n-1 : TopFloor UpperFloor1
     else : UpperFloor1

The top floor generates two successors, one that will continue to have a façade modelled and one that will be used to create a roof.


The side face needed to be selected and then a simple Split operation was carried out to generate a façade as shown below:

UpperFloor1 -->
     comp(f){side: UpperFloor2}
    
UpperFloor2 -->
     split(x){0.5 : Wall | {~2.5 : Window}* | 0.5 : Wall}


That concluded the set up of the building façades. The next post will show how I added the additional building detail to finish the façades and the completion of the project.  

No comments:

Post a Comment