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, 29 September 2013

Uses and users of CityEngine and CityEngine 2013


A recent article on Computer World outlined the use that had been made of CityEngine across a number of different industries. Recently, according to the article, the entertainment industry has used CityEngine to produce a number of cityscapes for films such as the latest reincarnation of Superman - Man of Steel, Cars 2 and Total Recall. Now, the pipeline for city generation should be made more efficient for this industry with the upcoming release of CityEngine 2013.



CityEngine 2013 sees the additon of an SDK (Software Development Kit) that will allow users of software such as Maya to procedurally generate models inside this software, instead of in CityEngine. The entertainment industry especially has pretty high standards when it comes to the quality of the visual output of CityEngine. Being able to marry up the procedural power of CityEngine and a high end graphics package like Maya we should be looking at some seriously awesome work.

The SDK is just one of a few small hints that Esri have released regarding CityEngine 2013. I have in the last week or so watched a few webinars through Directions Magazine. People such as Geoff Taylor and Eric Wittner have been bringing some of the latest information to the geospatial masses. There seems to be quite a few updates in the upcoming version but I've yet to see a clearly defined list of what's new.

It is obvious that Esri are having to service a number of different industries and so far the integration with the geospatial world has been steady. Obviously the SDK is made to service the entertainment industry. This got me thinking what industries would be the main users of CityEngine:

1. Government - Planning, Engineering, Architecture, Urban Design
2. Entertainment - Films, Advertising, Gaming
3. Military - Simulation

I'm pretty sure that there could be a wide range of other uses too. CityEngine is by name a city generation tool. However, many of the operations could be used to create all kinds of models based on any kind of input geometry. Making the operations work on a project that isn't city based could be a good way of showing the potential of other industries also using the software. I'll have to have a think about that and come up with something that is fun, will teach me some new techniques and that could be applicable to an industry.

Getting back to the Computer World article, Gert van Maren mentions some of the work that Auckland Council have done regarding a land resource document called the Unitary Plan. This is one of the major projects that I've been lucky enough to work on and I'll be reporting on some of the things I learnt in carrying out this project in upcoming posts.


Thursday, 26 September 2013

(Re)Tweet City - A great use of CityEngine from en-topia


Please take a look at the outstanding blog post here from en-topia. They have even been kind enough to provide sample rules and instructions through GitHub

If you're not into the developer world or like me just getting into it, the number of API's seems to increase ten-fold by the day into a dizzying array of wonderful tools that allow access to all kinds of exciting data. One of these API's is the Twitter API. My very shallow knowledge of the API is that it can be used to carry out a search of tweets, providing back a list that, if geotagged, can be mapped.

The guys at en-topia have taken the simple 2D mapping of Twitter data another step forward by applying geotagged tweets to buildings. The 2D building data is plugged into CityEngine and a rule file is applied giving the building an extrusion based on the number of tweets located in or around that building. From what I can take from their article they are able to take live feeds from Twitter (or a search result over a given period of time) and feed this into CityEngine, outputting a time-lapse style video. Of course the tweets can be applied to any 2D data you wish, in another example they show the data applied to a 1km grid instead of buildings. Depending on the scale of the area you wish to show this is a great example of how easily CityEngine can be used to create the same type of output but for different inputs but based on the same attributes.

I guess the next step, which is in the hands of Esri, would be to make it possible to create a 3D Web Scene that itself can handle ongoing updates and show 'live' data that is supplied by a pipeline of (Python?) processes.

This is such a great use of CityEngine, I think anyone who sees this and understands the power of the Twitter API and/or CityEngine instantly sees the potential to make something similar for their area of expertise.

Tuesday, 17 September 2013

CityEngine Starter Project 7 - Lessons learnt and the full rule file


Lessons learnt

As a result of this project I had learnt a number of things regarding rule file creation:

  • How to structure the rule file
  • How to create attributes and give them additional information such as a range
  • How to create functions and loops within the rule file
  • How to create a conditional (case) statement

Within the rule file I also was able to understand and use a number of CityEngine operations including:

  • Split
  • Extrude
  • Comp
  • Align
  • Set
  • Report
  • Setback
  • Roof operations
  • Translate
Looking back at what I intended to achieve a result of the project, I think I covered everything pretty well. I created a basic street and parcel network, however, I think that maybe some of the parcels are either to big and / or I could have added more streets. I created a rule file that included some very basic planning rules, I will of course include more rules in future but this will also require me to understand what the planning rules are, what they intend to allow or not allow to happen on a zone and how this can be translated into a rule file. I also created an output visualisation, this step was by far the easiest and using the Web Scene technology is something that I very much look forward to utilising again in future. 

The full rule file

Below is the full rule file created for this project:


/**

 * File:    StarterProject.cga
 * Created: 01 Jan 2013 01:02:07 GMT
 * Author:  ellawayc
 */

version "2012.1"

## Hidden Attributes ##
@Hidden
attr ParcelArea = 0
@Hidden
attr FootprintNeeded = 0
@Hidden
attr Length = 0
@Hidden
attr Width = 0

## Attributes ##

@Range("Residential","Commercial")
attr ZONETYPE = "Residential"
@Range(0,5)
attr YARD = 2.5
@Range(25,100)
attr COVERAGE = 30

## Functions ##

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

## Constants ##

## Assets ##

## Rules

@startRule

Lot -->
      alignScopeToGeometry(yUp, auto)
      set(ParcelArea, geometry.area)
      report("A Parcel Area (m2)", ParcelArea)
      Lot1
     
Lot1 -->
      case ZONETYPE == "Residential" : ResidentialLot
      else : CommercialLot

##### Residential Zone ##### 
ResidentialLot -->
      setback(YARD) {street.front : NIL  | remainder : ResidentialLot2}      
     
ResidentialLot2 -->
      innerRect
      shapeL(scope.sz*0.75,scope.sx*0.75) {shape : ResidentialLot3 | remainder : NIL}

ResidentialLot3 -->
      set(FootprintNeeded, (ParcelArea/100)*COVERAGE)
      report("B Footprint Needed (m2)", FootprintNeeded)   
      ResidentialLot4

ResidentialLot4 -->
      case geometry.area > FootprintNeeded : s('0.99,0,'0.99) center(xz) ResidentialLot4
      else : ResidentialLot5
     
ResidentialLot5 -->    
      extrude(world.y, 5)
      split(y){~0.25 : ResidentialLot6 BaseKeeper | 5 : NIL}     
     
ResidentialLot6 -->
      comp(f){top : alignScopeToGeometry(yUp, auto) ResidentialLot7}   

ResidentialLot7 -->    
      extrude(5.5)     
      comp(f){top : Roof | all : ResidentialLot8}    
     
Roof -->
      roofHip(18.5, 0.4)     
     
##### Commercial Zone #####  
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
     
CommercialLot1 -->
      20% : CommercialLot3
            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}
                 
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}             
                        else : CommercialLot3
                 
CommercialLot3 -->
      extrude(world.y, 5)
      split(y){~0.25 : CommercialLot4 BaseKeeper | 5 : NIL}                        
                 
CommercialLot4 -->
      comp(f){top : alignScopeToGeometry(yUp, auto) CommercialLot5}                
     
CommercialLot5 -->
      extrude(getHeight)                 
      split(y){4.5 : GroundFloor | {~4 : UpperFloor(split.index, split.total)}*}         
                 
GroundFloor -->
      comp(f){side: GroundFloor1}              
                 
GroundFloor1 -->
      split(x){0.5 : Wall | ~5 : LargeWindow | 3 : Entrance | {~5 : LargeWindow}* | 0.5 : Wall}
                 
UpperFloor(idx,n) -->              
      case idx == n-1 : TopFloor UpperFloor1
      else : UpperFloor1           

UpperFloor1 -->
      comp(f){side: UpperFloor2}
     
UpperFloor2 -->
      split(x){0.5 : Wall | {~2.5 : Window}* | 0.5 : Wall}                   
     
LargeWindow -->
      split(x){0.25 : SolidWall | ~5 : split(y){0.50 : SolidWall | ~1 : LargeWindow1 | 0.25 : SolidWall} | 0.25 : SolidWall}                 
                 
LargeWindow1 -->
      t(0,0,-0.4)
           
Window -->
      split(x){0.2 : SolidWall | ~2.5 : split(y){0.2 : SolidWall | ~1 : Window1 | 0.2 : SolidWall} | 0.2 : SolidWall}            

Window1 -->
      t(0,0,-0.2)

Entrance -->
      split(x){0.2 : SolidWall | ~2.5 : split(y){~1 : Entrance1 | 0.35 : SolidWall} | 0.2 : SolidWall}
     
Entrance1 -->
      t(0,0,-0.4)
           
SolidWall -->
      extrude(0.4)
      t(0,-0.4,0)            
     
TopFloor -->
      comp(f){top : CommercialRoof}
     
CommercialRoof -->
      extrude(0.15)    
      comp(f){top : CommercialRoof1 RoofKeeper | all : RoofKeeper }    

CommercialRoof1 -->
      setback(0.4){all : CommercialRoof2}
     
CommercialRoof2 -->
      extrude(0.4)        

Sunday, 15 September 2013

CityEngine Starter Project 6 - Additional building detail and completing the scene


Additional building detail

A number of successors had now been created that I could use to continue to add detail to the facade and roof of the commercial buildings. First up was to continue working the ground floor of the building to achieve the 'retail' style frontage.

The LargeWindow successor was split into a number of parts that would create a window and a frame. The split I used to do this adds another level of complexity where a y split is carried out within an x split. This makes the code more efficient and reduces the need to write needlessly long rule files:

LargeWindow -->
     split(x){0.25 : SolidWall | ~5 : split(y){0.50 : SolidWall | ~1 : LargeWindow1 | 0.25 : SolidWall} | 0.25 : SolidWall}

LargeWindow1 is now subject to a -0.4m translation in the z direction. This moves the LargeWindow1 successor into the building creating a window that is inset compared to the Wall and SolidWall successors:

LargeWindow1 -->
     t(0,0,-0.4)


The Window from the upper floors and the Entrance successors were also subject to similar types of splits and translations:

Window -->
     split(x){0.2 : SolidWall | ~2.5 : split(y){0.2 : SolidWall | ~1 : Window1 | 0.2 : SolidWall} | 0.2 : SolidWall}            

Window1 -->
     t(0,0,-0.2)
Entrance -->
     split(x){0.2 : SolidWall | ~2.5 : split(y){~1 : Entrance1 | 0.35 : SolidWall} | 0.2 : SolidWall}
    
Entrance1 -->
     t(0,0,-0.4)

The final stage of the facade creation was fairly critical. As all windows and the entrance had been translated by -0.4m this left a gap between the wall. To fill this space a 'solid' block needed to be created so that it would appear that the building has frames around both:

SolidWall -->
     extrude(0.4)
     t(0,-0.4,0)  

The last addition to the building was the inclusion of a roof. A Comp(f) operation was used to select the top face of the TopFloor successor. This was extruded by 0.15m to create a solid roof base and another comp(f) operation was used to again select the top, whilst keeping the other faces (RoofKeeper). A setback operation with a value of 0.4m to all edges and a final extrude operation to the edge created a simple raised edge roof:

TopFloor -->
     comp(f){top : CentreRoof}   
    
CentreRoof -->
     extrude(0.15)
     comp(f){top : CentreRoof1 RoofKeeper | all : RoofKeeper }

CentreRoof1 -->
     setback(0.4){all : CentreRoof2}  
    
CentreRoof2 -->
     extrude(0.4)

The commercial buildings and the code for the starter project was now complete.

Of course I could now go through and add extra code to create different types of zone, additional building detail such as colours and/or textures, add further planning based rules such as height to boundary and density, and add extra randomness to create more varied types of building styles. I however, found this to be a great start and found it really very useful to begin to understand how CityEngine operations can be used together to produce an interesting output.

To complete the scene I applied a slightly modified version of the streets.cga code that the guys at Esri had produced. The scene was exported as a CityEngine Web Scene and can be found on arcgis.com here.

The Web Scene functionality is something that I'll look at in other posts but quickly I'll say that it is an excellent tool to deliver data and I hope that further upgrades will continue. 




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.  

Saturday, 7 September 2013

CityEngine Starter Project 4 - Building on a terrain and basic building detail



Building on a terrain

A section of code was now required so that the buildings take into account the terrain on which it sits. Presently the shapes sit on top of the terrain, sloping in a way which would create buildings that are not vertical. Essentially this section of code created a flat base on which the building can sit. On steep terrain it is very hard to deal with building model shapes in a simple way and would most likely need further operations to get an optimal output. However, I had chosen a fairly flat area so that it would not be an issue.

The next successor (ResidentialLot5) was extruded using the Extrude operation to a value (height) of 5m. The extruded block was then split in the vertical (y direction) as below:


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


A base had now been created on which the building could sit and be extruded. Note that I create two identical successors - ResidentialLot6 and BaseKeeper. This is so we can continue to build the residential house, whilst keeping the base as a separate piece. There are a few different ways to create a base and this one worked for me in this simple example. Some of the tutorial and example codes provided by Ersi show different ways to carry out a similar function and I'd advise having a look at these as well.




Another very commonly used operation now had to be used to select just the top surface of the base to continue building upon. A Comp(f) split operation allowed me to make this selection which also required another align the scope operation too. During Comp operations the previous alignment is lost and so I needed to reapply it.


ResidentialLot6 -->
     comp(f){top : alignScopeToGeometry(yUp, auto) ResidentialLot7}


Basic model detail

The ground work for the residential buildings was now complete. All I needed to do was extrude the footprint by a height value and carry out another Comp operation to select the top face for the roof. The final stage code for the residential buildings looked as follows:

ResidentialLot7 -->
     extrude(5.5) 
     comp(f){top : Roof | all : ResidentialLot8}
    
Roof -->
     roofHip(18.5, 0.4)

The roofHip operation created the roof geometry, the values for pitch and overhang could also  have been made into sliders. The residential buildings are now complete and the scene now looked like the image below:



The residential buildings were now complete. I had added a simple roof geometry that gave the buildings a more realistic look but really I could add a great deal more detail. The amount of detail that I add to models is very much scale and need dependant. The more models being generated generally the lower the amount of detail. Of course the level of detail (LOD) could be controlled by a slider, generating higher detail models when needed.

The next post will look at the commercial zone and how I used conditional and stochastic rules to give the models a more random feel.  I will also show how a function can be used in the code and run through some more complex split operations.