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.
No comments:
Post a Comment