Tutorial 9: Advanced shape grammar

To access the tutorial projects in ArcGIS CityEngine, open CityEngine and click Help > Download Tutorials and Examples in the main menu. After choosing a tutorial or example, the project is automatically downloaded and added to your CityEngine workspace.

Building with the style keyword Red

In this tutorial, you'll learn how analyze a building facade image and translate the facade components into CGA. You'll explore how to create a set of CGA rules to re-create a building from the real-world image below:

Photograph of building that will be re-created with CGA

You'll notice the complex patterns of the tile and window layout in this example, and you'll use some advanced CGA mechanisms, such as nested repeat splits and parameter passing to create the building.

To learn more about CGA shape grammar, see the Rule-based modeling tutorial, and the Rule-based modeling and CGA modeling help topics.

Explore the building

To start, open the scene with the CGA version of the facade image above by doing the following:

  1. Expand the Tutorial_09_Advnced_Shape_Grammar tutorial folder in the Navigator window.
  2. Open the Complex Patterns.cej scene in the scenes folder.

    Click Yes when asked "Would you like to regenerate these models?" to display the building:

CGA perspective of the generated building

Define the facade layout

When planning a new CGA rule, it's helpful to sketch the rough layout and define some of the shape names before starting to write the rules.

Perform facade analysis

First, when you analyze the facade, you'll see the facade consists mainly of three floor types: top, ground, and upper. The lower floors are made of tiles containing windows, whereas the top floor contains only window elements. Every other lower floor is identical, so you'll pass the index (floorIndex) parameter with the Floor shape to create the correct look (tile and window alignment) for a specific floor.

Due to the pattern of the tiles, you'll define an intermediate DoubleTile shape, which contains two Tile shapes, and is helpful once you encode the floor patterns.

Complex pattern schema

Define subshapes

Next, you'll define the detailed subshapes in a tile. It consists of two main parts: the MilkGlass and Window shapes. The Window shape contains a Blind on the top and an embedded Subwindow shape. The position of these elements depends on the horizontal position of the tile on the floor, so you'll need to store this position index ( tileIndex) as a parameter of the Tile shape to be able to place the subshape structure correctly.

Detailed subshapes in a tile

Create a building volume in CGA

Now that you defined the facade layout, you can start creating the building in CGA.

Add attributes, variables, and assets

The first step is to create a new rule and then define attributes at the beginning of the rule file. These attributes are used through the entire rule set, and they can be modified in the scene, by adjusting the attributes and parameters using the Inspector window. To start creating the rule, do the following:

  1. Click New > CityEngine > CGA Rule File.
  2. Click Next.
  3. Name the rule myComplexPatterns.cga.
  4. Click Finish.

    A new CGA file is created, and the CGA Editor window appears. It's empty except for the version number.

  5. Add the following user attributes and an empty rule annotated as @StartRule at the bottom:

    // User Attribute 
    
    @Group("Building", 1) @Range(min=5, max=40, restricted=false) @Distance
    attr buildingH = 27             // building height
    
    @Group("Facade", 2) @Range(min=3, max=6, restricted=false) @Distance
    attr floorH = 3.5               // floor height
    @Range(min=3, max=6, restricted=false) @Distance
    attr groundfloorH = floorH+1    // groundfloor height
    @Range(min=1, max=4, stepsize=1, restricted=false)
    attr nSymmetries = 2
    @Range(min=0.1, max=1, restricted=false) @Distance
    attr borderwallW = 0.3          // width of border wall stripe
    @Range(min=0.1, max=0.8, restricted=false) @Distance
    attr ledgeH = 0.3               // ledge height
    
    @Group("Window",3) @Range(min=1, max=5, restricted=false) @Distance
    attr windowW = 2.5              // window width
    @Range(min=1, max=5, restricted=false) @Distance
    attr milkGlassW = windowW/2     // milkglass blend width
    @Range(min=0.1, max=2.5, restricted=false) @Distance
    attr blindH = 0.8               // blind height
    @Range(min=0.01, max=0.5, restricted=false) @Distance
    attr frameW = 0.07              // frame width
    
    @Group("Balcony",4) @Range(min=3, max=6, restricted=false) @Distance
    attr balconyDepth = 2
    
    @Group("Colors",5)
    @Color
    attr brightblue = "#86b1c7" 
    @Color
    attr darkblue   = "#33556c"
    @Color
    attr red        = "#5c3f40"
    @Color
    attr grey       ="#6b7785"
    @Color
    attr white      = "#ffffff"
    
    @StartRule
    Lot --> BuildingVolume

  6. Press Ctrl+S to save the rule file.
  7. Select the model and assign the myComplexPatterns.cga rule to it.

    Notice that annotations such as @Group or @Range were added to the attributes to control their appearance in the Inspector window:

    Inspector window with Shape Lot 1011 rules

  8. Add additional variables and assets above @StartRule:

    tileW = windowW + milkGlassW	// total tile width
    const barDiameter = 0.04
    
    // assets
    const cyl_v 	    = "primitives/cylinder.vert.8.notop.tex.obj"
    const cyl_h 	    = "primitives/cylinder.hor.8.notop.tex.obj"
    const window_tex    = "facade/windows/1_glass_2_blue.tif"
    const milkGlass_tex = "facade/windows/blend_tex.png"

Extrude the building

The actual creation of the building starts now. To extrude the building, do the following:

  1. Create the mass model with the extrude operation. The top floor is split from the main part and split again to create the set-back balcony:

    BuildingVolume -->
    	extrude(buildingH)
    	split(y) { ~1     : MainPart
    	         | floorH : UpperPart }
    
    UpperPart -->
    	split(z) { ~1           : TopFloor 
    	         | balconyDepth : Balcony }

  2. Add component splits to apply them to the different volume parts to distinguish the front, side, and top faces. This triggers the Facade, Wall, and Roof rules:

    MainPart -->
    	comp(f) { front : Facade
    	        | side  : Wall
    	        | top   : Roof. } 
    
    TopFloor -->
    	comp(f) { front : Floor(-1)
    	        | side  : Wall
    	        | top   : Roof. }

  3. Use a component split to call the Railing rule to place the railing on the front, left, and right faces:

    Balcony -->
    	s(scope.sx-2*borderwallW, 1.1, scope.sz-borderwallW)
    	center(x)
    	comp(f) { front : Railing 
    	        | left  : Railing 
    	        | right : Railing }

    The dimensions of the balcony are set. Next, you'll add the railing to the building.

  4. Save the rule.

    Ensure the building is selected in the Viewport.

  5. Click Generate Generate (Ctrl+G) to generate the building:

    Rough building shape after volume modeling

Add floors

To subdivide the front facade further, do the following:

  1. Split the facade in to a ground floor and repeated upper floors:

    Facade -->
    	split(y) { ~groundfloorH : Floor( split.index )
    	         | { ~floorH     : Floor( split.index ) }* }

    The first split subdivides the facade into a ground floor part and a set of upper floors with the help of a repeating split {...}*. The tilde sign (~) before the split size height, such as ~groundfloorH, allows a flexible height and ensures matching floors without holes in the facade. By passing the split.index, (which represents the floor index) as a parameter, you can later trigger specific floor features.

  2. Create the narrow wall area on both sides with a simple split in the x-direction for every floor:

    Floor(floorIndex) -->
    	split(x) { borderwallW : Wall
    	         | ~1          : FloorSub(floorIndex)
    	         | borderwallW : Wall }

  3. Add special horizontal elements for every floor with the horizontal split commands using the following requirements:

    • The upper floors only feature a top ledge.
    • The top floor does not have additional elements and triggers the TileRow shape directly.
    • Assign the floor index again as a parameter with the TileRow shape, since you'll use the floor index again in a later rule.

    FloorSub(floorIndex) -->
    	case floorIndex == 0:
    		split(y) { 1      : Wall
    		         | ~1     : TileRow(floorIndex)
    		         | ledgeH : Wall }
    	case floorIndex > 0:
    		split(y) { ~1     : TileRow(floorIndex)
    		         | ledgeH : Ledge }
    	else:
    		TileRow(floorIndex)

  4. Save the rule and generate the building:

    Facade with floor and ledge splits

Add tiles

You'll now split the floors into tiles. For the top floor, there is no special pattern, only repeating window elements. To address these tiles later, you mark them with the -1 parameter.

  1. To create the special repeating pattern for the main floors, create the intermediate DoubleTile shape:

    TileRow(floorIndex) -->
    	case floorIndex == -1:
    		split(x) { ~windowW : Tile(-1) }*
    	else: 
    		split(x) { ~tileW*nSymmetries : DoubleTile(floorIndex, split.index) }*

    To align the window elements correctly in a later step, you'll need the floor and the tile indexes (split.index), which you'll pass as parameters.

  2. Add two rules with repeating splits to obtain different placements of the MilkGlass and Tile shapes:

    DoubleTile(floorIndex, tileIndex) -->
    	case tileIndex%2 + floorIndex%2 == 1:
    		split(x) { ~milkGlassW : MilkGlass 
    		         | ~windowW    : Tile(tileIndex) }*
    	else:
    		split(x) { ~windowW    : Tile(tileIndex)
    		         | ~milkGlassW : MilkGlass }*

    The combination of the floor and tile indexes determines the alignment of the windows within the double tile.

  3. Set up the texture coordinates for the future window texture:

    Tile(tileIndex) -->
    	setupProjection(0, scope.xy, scope.sx, scope.sy)
    	split(x) { frameW : Frame Bracing 
    	         | ~1     : split(y) { frameW : Frame 
    	                             | ~1     : Window(tileIndex)
    	                             | frameW : Frame 
    	                             | blindH : Blind 
    	                             | frameW : Frame } 
    	         | frameW : Frame Bracing }

    The entire Tile shape is then split horizontally into window frames and the center part. The center part is again split, this time vertically, into the frame, window, frame, blind, and frame components.

  4. Save and generate:

    Building split into window frames and vertical center parts forming frame, window, blind, and bracing

Add windows

To add windows to the building, do the following:

  1. For the Window shape, the tile index of the DoubleTile is used to determine the position of the sub-windows:

    Window(tileIndex) -->

  2. Place the right-aligned sub-windows in the left half of the window:

    case tileIndex%nSymmetries >= 1:
    		split(x) { ~1     : Subwindow("right")
    		         | frameW : Frame
    		         | ~1     : Glass }

  3. Place the left-aligned sub-windows in the right half of the window:

    case tileIndex%nSymmetries >= 0:
    		split(x) { ~1     : Glass
    		         | frameW : Frame
    		         | ~1     : Subwindow("left") }

  4. Use the -1 tile index, representing the top floor windows, to create windows without sub-windows:

    else:
    		split(x) { ~1     : Glass
    		         | frameW : Frame
    		         | ~1     : Glass }

  5. Using the left and right parameters, the RedWindow is placed in the correct location:

    Subwindow(align) -->
    	case align == "left": 
    		split(x) { ~3 : RedWindow 
    		         | ~2 : Glass }
    	else: 
    		split(x) { ~2 : Glass
    		         | ~3 : RedWindow }

  6. Add the rule to create the frame and glass elements for the RedWindow shape:

    RedWindow -->
    	split(x) { frameW : RedFrame 
    	         | ~1     : split(y) { frameW  : RedFrame
    	                             | ~1      : RedGlass
    	                             | frameW  : RedFrame }
    	         | frameW : RedFrame }
    
    RedGlass -->
    	split(y) { ~1       : Glass
    	         | frameW/2 : t(0,0,-frameW) Frame
    	         | ~1       : t(0,0,-frameW) Glass }

  7. Save and generate:

    Detailed window geometry for the RedWindow shape

Add materials

To apply color and texture to the building, do the following:

  1. Define the colors, extrusion, and translation of the facade elements:

    Wall -->
    	color(darkblue)
    
    Blind -->
    	color(grey)
    
    Frame -->
    	extrude(frameW)
    	color(white)
    
    RedFrame --> 
    	t(0, 0, -frameW)
    	extrude(frameW*4)
    	color(red)

  2. Apply texture coordinates to the current shape geometry and assign texture and color:

    Glass --> 
    	projectUV(0)
    	texture(window_tex)
    	color(white)
    	set(material.specular.r, 0.4)
    	set(material.specular.g, 0.4)
    	set(material.specular.b, 0.4)
    	set(material.shininess, 4)
    	set(material.reflectivity, 0.3)
    	
    MilkGlass --> 
    	s('1, '1, frameW*1.2)
    	primitiveCube
    	color(brightblue)
    	setupProjection(0, scope.xy, scope.sx, scope.sy, 0, 0, 0)
    	texture(milkGlass_tex)
    	projectUV(0)
    	set(material.specular.r, 0.7)
    	set(material.specular.g, 0.7)
    	set(material.specular.b, 0.7)
    	set(material.shininess, 20)
    	set(material.reflectivity, 0.05)

  3. Save and generate:

    Colored and textured model after applying material rules

Add details

Finally, to add details to the elements, do the following:

  1. Refine the floor ledges by adding a back wall element, a cube to give it some depth, and a second thin cube that serves as a cover plate:

    Ledge -->
    	Wall
    	[ s('1, '0.9, 0.2) primitiveCube Wall ]
    	t(0, -0.1, 0.2)
    	s('1, scope.sy+0.1, 0.03)
    	primitiveCube
    	Wall

  2. The vertical bars are evenly distributed with the help of a repeat split with a floating split width:

    Railing --> 
    	[ t(0,scope.sy-barDiameter/2,0) HBar ]
    	set(trim.vertical, false)
    	split(x) { ~tileW/3 : VBar }*

    A horizontal bar is inserted to create the horizontal part of the railing. By disabling vertical trimming, the vertical corner bars are prevented from being cut.

  3. Insert cylinder assets to create the vertical and horizontal bars:

    VBar -->
    	s(barDiameter, '1, barDiameter)
    	t(0, 0, -barDiameter)
    	i(cyl_v)
    	color(white)
    
    HBar -->
    	s('1, barDiameter, barDiameter)
    	t(0, 0, -barDiameter)
    	i(cyl_h)
    	color(white)

  4. The bracing of the windows consists of top and bottom mountings and a vertical bar in the middle. For the mountings, insert a cube and have the VBar trigger the cylinder asset:

    Bracing --> 
    	s(barDiameter, '1, 0.15)
    	center(x) 
    	primitiveCube
    	split(y) { 0.01 : Wall
    	         | ~1   : t(0, 0, 0.15) VBar
    	         | 0.01 : Wall }

  5. Save and generate:

    Final model with detail elements

    The final model appears with detail elements including ledges, window bracings, and railings added:

Now that you have the final model, apply the rule on different lot shapes, or experiment with the user attributes in the Inspector window to modify your facade design.

Add styles

You can use the style keyword to define a new style that redefines some attributes.

  1. At the bottom of the rule file, add a style to the model by redefining the color attributes:

    @Description("A Variation in Red")
    style Red_Color_Theme
    attr brightblue = "#FF8080"
    attr darkblue 	= "#D20000"
    attr grey 	= "#ECCACA"
    attr red 	= "#361B1B"

  2. Save the rule file.
  3. Click the Red_Color_Theme in the Style Manager drop-down menu in the Inspector window:

    Red_Color_Theme applied in Style Manager

    The Red_Color_Theme style is applied:

    Building with the style keyword Red

    To see the final rule file, open the complexpatterns_01.cga rule.

Candler Building

The Candler building is a great example of how CGA can create realistic buildings procedurally. To explore the CGA, do the following:

  1. Open the Candler Building.cej scene.
  2. Double-click the Candler Building.cga file in the Navigator window and explore the CGA rules that create the Candler Building.

Candler Building

Parthenon temple

Finally, to explore the Parthenon temple, do the following:

  1. Open the Parthenon.cej scene.
  2. Again, double-click the parthenon.cga file to see the rules behind the Parthenon Temple:

Parthenon temple

In this tutorial, you learned how to do the following:

  • Take and analyze an image and break it up into CGA components.
  • Add elements, such as windows, tiles, and materials.
  • Explore more complex examples of advanced shape grammar.

To continue your learning with CityEngine, see the complete CityEngine tutorial catalog.