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, 18 May 2014

TikiTown 4 - CGA Creation - Tiki Head

The 'head'of the Tiki contains the main part of the city and as such I wanted this part to look just like a stereotypical city skyline with many tall buildings in different shapes and colours. I wanted to have a bit of a play in designing some basic building shapes, coded so that differences to a floor, or groups of floors would impact the building form.  

Setting up

The first part of the code includes the building of the base but also the setting of window colours. I will apply the window colour much further down in the code but it must be set now, before any floors have been split, otherwise the randomness in the code will assign a different value to each floor of a building. Whilst the randomness coulld be used in a number of other applications, in this case I actually want each floor to be the same.

The section of code 'Head1' includes the setting of two hidden attributes - RegularParcelArea and ParcelRegularity. These two values allow simple analysis of a parcel and some judgement whether the parcel is regular or not. With parcels generated in CityEngine - usually off the back of the Grow Streets tool, the parcels tend to be very regular in shape and so are very easy to deal with. Often, when importing parcels from real world data, there are wildly varying shapes and sizes. Creating code that is able to deal with a wide variety of shapes is very important  and I've found that coding up a simple tool that outputs a value between 0 and 100 provides at least an idea of the regularity. The closer the value to 100 the more regular the parcel shape. The tool works by calculating the regular parcel area for the selected parcel. This takes the bounded length (scope.sx) and multiplies it by the bounded width (scope.sz). If the parcel is 100% regular then the actual length and width will be the same as the bounded (scope) values. Parcel Regularity is then calculated by dividing 100 by the RegularParcelArea, times the actual geometry area. The extremely simple image below shows an example parcel with a regularity value of 86.

Once this value has been calculated for a number of parcels, a better idea of what would be considered an 'easy' parcel to work with can be found.

Building Types

After carrying out this simple check, I use the value to tidy up any slightly irregular blocks by performing an innerRect operation whilst leaving all other parcels as they were (this actually means leaving the most irregular parcels alone)

At this point the buildings are given a type and are then extruded and split in the vertical direction to create a number of floors - carrying the index and total values. The section of code 'Head2' contains the crux of the TikiHead code and gives an idea of the coding necessary to create different building forms based on floor splits. This piece of code contains quite a few stochastic rules, just so that every time the models are generated a different result is generated.

The code contains 6 different building forms:



It is important to see these buildings in terms of individual floors rather than the building as a whole. Constructing buildings in this way allows for interesting and creative results, however, texturing this type of style could prove to be an issue.

I'll run through one of the building types - the Staggered Decreasing Mass. This is a pretty stereotypical building shape and the code produces a nice building form. In this case the bottommost 33% of floors are left as they are, the bottommost 66% (not including the bottommost 33%) are resized so that the scope.sz and scope.sz are reduced to 85% of the original size and centred. The remaining floors (the topmost 33%) are reduced to 65% of the original size and are also centred. This building type could be varied so that different percentages of floors are chosen and/or different values or ways of reducing the size are carried out.

The creativity really is limitless and it comes down to the amount of time you want to spend coding up different designs for the particular output you have in mind. The code I have created here can be built on in other projects so the library of building forms can only be increased. Combinations of the above types would also provide some nice buildings forms...for another time.

Finishing Up

From here its just a case of splitting floors into tiles and applying colours to the building and windows. As previously mentioned, I wanted each building to have a single colour for each the walls and the windows. These colours can not be randomly applied once buildings have been split into floors as each floor would be treated differently, so its important to identify the point at which you wish the randomness to be applied. The wall colour in this case is applied from a constant - and therefore remains the same over the whole model. The final touch to the buildings is the inclusion of a few 'greebles' (look it up in a Google search for some stunning images) representing some little additional texture to buildings as ventilation blocks / stairwells or whatever takes your fancy.

The images below show the TikiHead with the TikiEyes and TikiSpike as an output visualisation created in Microstation. I'm most certainly not a visualisation specialist but even some simple rendering using Luxology produces some pretty awesome results.




##-----------------------------------------##
## Hidden Attributes ##
##-----------------------------------------##

@Hidden
attr EyeWindowColourR = 0

@Hidden
attr EyeWindowColourG = 0

@Hidden
attr EyeWindowColourB = 0

@Hidden
attr RegularParcelArea = 0

@Hidden
attr ParcelRegularity = 0


##-----------------------------------------##
## Functional Attributes ##
##-----------------------------------------##

@Range("A","B","C","D","E","F")
attr BuildingType = "A"


##-----------------------------------------##
## Functions ##
##-----------------------------------------##

getEyeColorR =
      (1/255)*EyeWindowColourR     
     
getEyeColorG =
      (1/255)*EyeWindowColourG
     
getEyeColorB =
      (1/255)*EyeWindowColourB


##-----------------------------------------##
## Constants ##
##-----------------------------------------##

const getRandTile = floor (rand(2,6))

## http://www.fillster.com/colorcodes/colorchart.html ##   
const getWallColour =
            10% : "#F0F8FF" #Alice Blue
            10% : "#CDC0B0" #Antique White 3
            10% : "#E0EEEE" #Azure1
            10% : "#C1CDCD" #Azure2
            10% : "#838B8B" #Azure3
            10% : "#CDC8B1" #Cornsilk2
            10% : "#8B8878" #Cornsilk3
            10% : "#4F4F4F" #Grey31
            10% : "#696969" #Grey41
            else : "#BEBEBE" #Grey
           
##-----------------------------------------##
## Assets ##
##-----------------------------------------##



##-----------------------------------------##
## Rules ##
##-----------------------------------------##

@startRule
Head -->   
      set(EyeWindowColourR, (ceil(rand(0,51))))
      set(EyeWindowColourG, (ceil(rand(0,25))))
      set(EyeWindowColourB, (ceil(rand(0,175))))
      extrude(2)
      comp(f){top : HeadA HeadBase | all : HeadBaseAll}
     
HeadA -->
      setback(2.5){all : NIL | remainder : Head1}

Head1 -->
      set(RegularParcelArea,scope.sx*scope.sz)
      set (ParcelRegularity,(100/RegularParcelArea)*geometry.area)
      report("ParcelRegularity", ParcelRegularity)
      Head1a
     
Head1a -->
      case ParcelRegularity > 85 : innerRect Head1b
      else : Head1b    

Head1b --> 
      15% : set(BuildingType, "A") Head2
      10% : set(BuildingType, "B") Head2
      15% : set(BuildingType, "C") Head2
      15% : set(BuildingType, "D") Head2
      5% : set(BuildingType, "E") Head2
      else : set(BuildingType, "F") Head2
           
Head2 -->
      85% : extrude(rand(30,60)) split(y){~3 : Head2(split.index,split.total)}*
      else : extrude(rand(45, 85)) split(y){~3 : Head2(split.index,split.total)}*
     
     
Head2(idx,n) --> 
      case BuildingType == "A" : HeadFloors1(idx,n) # Bulk Mass
      case BuildingType == "B" : s((scope.sx*(1-((idx-1)/n)/2)), '1,(scope.sz*(1-((idx-1)/n)/2)))center(xz) HeadFloors1(idx,n) #Decreasing Mass
      case BuildingType == "C" :
            case idx%2 == 1 : s(scope.sx*0.85,'1,scope.sz*0.85) center(xz) HeadFloors1(idx,n)
            else : HeadFloors1(idx,n) # Alternating Mass
      case BuildingType == "D" :
            case idx < n*0.33 : HeadFloors1(idx,n)
            case idx < n*0.66 : s(scope.sx*0.85,'1,scope.sz*0.85) center(xz) HeadFloors1(idx,n)
            else : s(scope.sx*0.65,'1,scope.sz*0.65) center(xz) HeadFloors1(idx,n) # Staggered Decreasing Mass
      case BuildingType == "E" :
            case idx < n*0.80 : HeadFloors1(idx,n)
            else : s((scope.sx*(1-((idx-1)/n)/1.5)), '1,(scope.sz*(1-((idx-1)/n)/1.5)))center(xz) HeadFloors1(idx,n) # Bulk Mass with Tower
      else :
            20% : HeadFloors1(idx,n) # Bulk Towers
            20% : s((scope.sx*(1-((idx-1)/n)/2)), '1,(scope.sz*(1-((idx-1)/n)/2)))center(xz) HeadFloors1(idx,n)
            20% :
                  case idx%2 == 1 : s(scope.sx*0.85,'1,scope.sz*0.85) center(xz) HeadFloors1(idx,n)
                  else : HeadFloors1(idx,n)
            20% :             case idx < n*0.33 : HeadFloors1(idx,n)
                  case idx < n*0.66 : s(scope.sx*0.85,'1,scope.sz*0.85) center(xz) HeadFloors1(idx,n)
                  else : s(scope.sx*0.65,'1,scope.sz*0.65) center(xz) HeadFloors1(idx,n)
            else :                  case idx < n*0.80 : HeadFloors1(idx,n)
                  else : s((scope.sx*(1-((idx-1)/n)/1.5)), '1,(scope.sz*(1-((idx-1)/n)/1.5)))center(xz) HeadFloors1(idx,n)


HeadFloors1(idx,n) -->
      case idx == n-1 : comp(f){side : HeadFacade | top : TopHeadRoof HeadRoof | all : Headkeeper}
      else : comp(f){side : HeadFacade | top : HeadRoof | all : Headkeeper}
     
HeadFacade -->
      case scope.sx > 5 : split(x){~0.5 : HeadWall | {~getRandTile : HeadTiles}* | ~0.5 : HeadWall}
      case scope.sx > 1.5 : HeadTiles1
      else : HeadWall
           
HeadTiles -->
      case scope.sx < 1.5 : HeadWall
      else : HeadTiles1

HeadTiles1 -->
      setback(0.2){all : HeadFrame | remainder : HeadWindow}
     
HeadWall -->
      color(getWallColour)

HeadFrame -->    
      extrude(0.25)
      t(0,-0.25,0)
      color(getWallColour)
     
HeadWindow -->
      t(0,0,-0.25)
      color(getEyeColorR, getEyeColorG, getEyeColorB)
      set(material.opacity, 0.95)

HeadRoof -->
      color(getWallColour)   

TopHeadRoof -->
      color(getWallColour)   
      alignScopeToGeometry(yUp, largest, 0)
      HeadGreeble

HeadGreeble -->
      setback(scope.sz*0.20){all : NIL | remainder : HeadGreeble1}
     
HeadGreeble1 -->
      case scope.sz < 2 : NIL
      else :
            innerRect
            split(x){~rand(2,4) : HeadGreeble2 | 1 : NIL}*
     
HeadGreeble2 -->
      case scope.sz < 2 : NIL
      else : split(z){~rand(2,4) : HeadGreeble3 | 1 : NIL}*
     
HeadGreeble3 -->
      comp(f){all : HeadGreeble4}
           
HeadGreeble4 -->
      50% : extrude(1)
      else : NIL 
     
HeadBase -->     
      setupProjection(0, scope.xy, 2, 2)
      projectUV(0)

      texture("pavement.jpg")









No comments:

Post a Comment