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")