About CityEngine Blog
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.
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}
else : CommercialLot3
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.
Subscribe to:
Posts (Atom)