[043] How to Build a 5-Story Procedural Building in Esri CityEngine

Integrating extrude, split, comp, and index commands to build complex architectural forms.

This tutorial focuses on a practical exercise to implement varying floor heights, facade protrusions based on even/odd indices, and side-wall component divisions within a single CGA rule workflow. By following these steps, you will learn how to handle multi-level logic and conditional modeling in CityEngine.

So far, we have covered extrude, split, comp, and index.

There are many valid ways to implement this logic, so treat this workflow as just one example.

Building Requirements and Modeling Goals

Let's assume there is a building with the following specifications:

  1. The building footprint is a rectangle with a width (front) of 40m and a depth (side) of 20m.
  2. The height is 20m.
  3. It is a 5-story building in total.
    1. The 1st floor height is 5m, and there is a 4m wide entrance in the center of the front facade.
    2. The 2nd to 4th floors have a height of 3m each, and the front of each floor is divided into 10 segments.
    3. The even-numbered segments on the 2nd floor front protrude outward by 1m.
    4. The odd-numbered segments on the 3rd floor front protrude outward by 0.5m.
    5. The 4th floor front segments are split vertically into two equal parts.
    6. The 5th floor height is 6m; the front is not divided, but the left and right sides are each split into three segments of equal width.

Let's proceed through the coding step by step.

Step 1: Create the Base Building Mass

  1. Draw the footprint.
Drawing the footprint in CityEngine
img 1 : Initial footprint rectangle

2. Assign a height of 20m.

Extruding the building height
img 2 : Extruding the volume

Step 2: Split the Building into Floors

Using split(y) to Define Floor Heights

3. Split it into 5 floors: the 1st floor is a single 5m split, the 2nd to 4th floors are 3m repeating splits, and the 5th floor is a single 6m split.

Splitting floors vertically
img 3 : Vertical floor split logic

3.1 For the 1st floor, use split(x) to create a fixed 4m wide entrance in the center.

Splitting the ground floor entrance
img 4 : Ground floor entrance split

3.2 Identify each floor by referring to the index value of the split(y) from line 7.

Floor identification by index
img 5 : Identifying floors via index

Step 3: Create the Second-Floor Facade Pattern

3.3 - Now, let's process them one by one starting from the 2nd floor.

Separating Building Faces with comp(f)

3.3.1 - Isolate the front facade of the 2nd floor.

Component split for the second floor front
img 6 : Isolating the 2F front facade

3.3.2 - Divide the isolated front into 10 segments. Since the width is 40m, split it at approximately 4m intervals.

Dividing the facade into 10 segments
img 7 : Facade partitioning

Creating Repetitive Architectural Elements

3.3.3 - Protrude the even-numbered segments by 1m by referring to the index value generated during the split.

Protruding even segments on the 2nd floor
img 8 : 2F even segment protrusion

Step 4: Create Alternating Protrusions on the Third Floor

3.4 - Now for the 3rd floor. The process is the same as the 2nd floor, but only the odd-numbered segments are protruded by 0.5m.

Protruding odd segments on the 3rd floor
img 9 : 3F odd segment protrusion

Step 5: Add Vertical Subdivisions to the Fourth Floor

3.5 - Moving on to the 4th floor. It is also divided into 10 segments, but each segment is split vertically into two parts.

Vertical split on the 4th floor
img 10 : 4F vertical window split

Step 6: Split the Side Facades of the Top Floor

3.6 - For the 5th floor, leave the front facade as it is and split only the two side facades into three equal widths. This requires using different selectors in comp().

Splitting side facades on the 5th floor
img 11 : 5F side facade partitioning

The final result is now complete. Pretty straightforward, right?

Complete CGA Code

The full code is as follows:

Lot --> extrude(20) A

A --> 
	split(y)
		{5 : ground_floor | 
		# 1st Floor 5m
		
		~1 : split(y){3 : mid_floor}* | 
		# Set middle floors as one area and repeat 3m splits within it
		
		6 : top_floor} 
		# 5th floor is a single 6m split

 ground_floor --> 
 	split(x){~1 : wall. | 4 : door. | ~1 : wall.}
 	# Entrance is fixed at 4m width, others distributed equally as approximations

 mid_floor -->
 	case split.index==0 :
 		second_floor
 	# Since the split(y) on line 9 starts from the bottom, 
 	# index 0 corresponds to the 2nd floor
 	
 	case split.index==1 :
 	# index 1 corresponds to the 3rd floor
 		third_floor
 	else : 
 	# The rest is processed as the 4th floor
 		fourth_floor

 second_floor -->
 	comp(f){front : second_front_wall | all : X.}
 	# Isolate only the front facade of the 2nd floor
 	
 second_front_wall -->
 	split(x){~4 : second_floor_window}*
 	# Repeatedly split the isolated 2nd-floor facade at ~4m intervals
 	# The ~ can be used or omitted

 second_floor_window -->
 	case split.index%2==1 : extrude(1) X.
 	# When the index from the split on line 38 is divided by 2, 
 	# Since split indexing starts at 0,
      # a remainder of 1 corresponds to the 2nd, 4th, 6th... segments
 	
 	else : X.
 	# Keep the remaining segments unchanged

third_floor -->
 	comp(f){front : third_front_wall | all : X.}
 	# Isolate the front facade of the 3rd floor separately
      # Since split indexing starts at 0,       # a remainder of 0 corresponds to the 1st, 3rd, 5th... segments third_front_wall --> split(x){~4 : third_floor_window}* # Repeatedly split the isolated 2nd-floor facade at ~4m intervals third_floor_window --> case split.index%2==0 : extrude(0.5) X. # When the index from the split on line 55 is divided by 2, # a remainder of 0 indicates an odd segment, so extrude 0.5m else : X. fourth_floor --> comp(f){front : fourth_front_wall | all : X.} # Isolate the 4th floor front fourth_front_wall --> split(x){~4 : fourth_floor_window}* # Repeatedly split the isolated 2nd-floor facade at ~4m intervals fourth_floor_window --> split(y){~1 : X. | ~1 : X.} # Split each partitioned window again vertically top_floor --> comp(f){ left : top_floor_window | right : top_floor_window | all : X.} # Isolate the left and right sides of the 5th floor top_floor_window --> split(x){~1 : X. | ~1 : X. | ~1 : X.} # Evenly split the isolated sides into a 1:1:1 ratio

Detailed CGA Code Breakdown and Logic

To help beginners fully grasp the procedural structure used in the complete code above, here is a detailed breakdown of the core logic and parameters implemented in each rule:

  • Vertical Floor Splitting (Lines 5-11): The split(y) command breaks down the 20-meter total building height into three distinct zones. The ground floor is locked at a fixed 5 meters, the top floor is assigned 6 meters, and the remaining middle section utilizes a repeating 3-meter split ({3 : mid_floor}*). This repeating asterisk (*) syntax is highly efficient as it automatically loops the rule for the 2nd, 3rd, and 4th floors based on the total remaining mass height.
  • Floor Identification via Split Index (Lines 16-25): Inside the mid_floor rule, CityEngine evaluates the conditional case split.index statement. Since vertical split indexing always starts from 0 at the baseline, index == 0 accurately isolates the 2nd floor, index == 1 targets the 3rd floor, and the else fallback catch-all block handles the 4th floor seamlessly.
  • Even and Odd Facade Protrusions (Lines 38-60): To create the alternating architectural patterns, we apply a mathematical modulo operation to the horizontal split indices. On the 2nd floor, case split.index % 2 == 1 filters out even-numbered segments (since indexing starts at 0, the 2nd, 4th, and 6th visual segments return a remainder of 1), triggering an extrude(1). On the 3rd floor, the inverse logic case split.index % 2 == 0 catches the odd-numbered segments, applying a subtle extrude(0.5) protrusion.
  • Nested Subdivisions & Multi-Selector Component Splits (Lines 66-83): The 4th floor grid is enhanced by nesting a vertical Y-axis split directly inside each horizontal window segment, dividing them into equal halves to mimic modern window panes. Finally, the 5th-floor rule showcases multi-selector efficiency by combining left and right into a single line, allowing both side walls to simultaneously inherit a clean 1:1:1 proportional horizontal grid subdivision.

I have built this by separating it into individual rules step by step, using only the content we have learned so far.

There is no single correct answer in coding. Just think of it as "Oh, you can code it this way too."

Once you learn how to pass parameters to rules, use functions, use variables, and import rules, your code will become much more concise.

From the next lesson, we will cover scaling, moving, and rotating.

"Healing does not mean becoming free from pain or difficulty.
It means learning to live through them without forgetting who you are."
— Gary Holz, Secrets of Aboriginal Healing
Continue Learning
Explore complete tutorial collections for CityEngine and Global Mapper.

Comments

Popular posts from this blog

029] How to Split Polygon Areas with Intersecting Lines in Global Mapper

[008] How to Understand CGA Rule Structure in CityEngine (Beginner Guide)

046] How to Create Parallel Offset Lines in Global Mapper