Tutorial 8: Mass modeling

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.

View of the generated buildings showing textured facades from the side

In this tutorial, you'll learn about mass modeling using L and U-shaped buildings, how to apply height and setback variation to parcels, and finally, generate a diverse looking scene including textures.

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

Add L and U-shapes

First, you will create L and U mass models of buildings using CGA, which are common typologies for buildings in cities.

Create a rule file

To create a rule file, do the following:

  1. Expand the Tutorial_08_Mass_Modeling tutorial folder in the Navigator window.
  2. Open the 01_MassModeling.cej scene in the scenes folder.

    Click No when asked "Would you like to regenerate these models?"

    This is because you will apply rules and generate models later in the workflow.

  3. Click New > CityEngine > CGA Rule File.
  4. Click Next.
  5. Name the rule myMass_01.cga.
  6. Click Finish.

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

Add a L-shape

You'll start with a simple L-shape mass model.

  1. Add the following to the myMass_01.cga rule:

    attr height    = rand(30, 60)
    attr wingWidth = rand(10, 20)
    
    Lot --> 
    	LShape
    
    LShape -->
    	shapeL(wingWidth, wingWidth) { shape : LFootprint }
    
    LFootprint --> 
    	extrude(height) Mass
    
    LotInner -->
    	OpenSpace

    The LShape rule creates an L-shape footprint, with dimensions ranging between 10 and 20 meters. The LFootprint rule extrudes the L-shape to its height.

    LotInner applies OpenSpace, leaving the lot shape as is.

    Next, you'll apply the rule to the lots.
  2. Press Ctrl+S to save the rule file.
  3. For the moment, you won't need the streets to create models, so click the Visibility settings Visibility settings and deselect Graph Networks (F10) to hide the streets.
  4. To select all the lots, right-click the Blocks sublayer in the Streetnetwork layer in the Scene Editor window and click Select Objects:

    Select lots in Scene Editor window

  5. To assign the myMass_01.cga rule to the lots, do the following:
    1. Ensure the Lots tab in the Inspector window is selected.
    2. Next to Rule File, click Assign in the Inspector window.

      Assign rule in the Inspector window

    3. Click the myMass_01.cga rule in the Assign Rule File dialog box.
  6. Click the Select tool Select tool (Q) and select some lots in the Viewport window.
  7. Click Generate Generate (Ctrl+G):

    Simple L-shapes on a small city part

Showing just L-shaped mass models does work, but a mix of model shapes may be better. Next, you will add more variation to the mass models.

Add variation to the L-shape

The current L-shape's side wing is always positioned on the left side of the main mass. Using the rotateScope operation, you can change the L-shape to be on the right side as well.

  1. Change the LShape rule by making use of the % probability operator to improve the L-shape so that its side wing is on either the left or right side:

    LShape -->
    	50%  :
    		shapeL(wingWidth, wingWidth) { shape : LFootprint }
    	else :
    		rotateScope(0, 90, 0) 
    		shapeL(wingWidth, wingWidth) { shape : LFootprint }

  2. Using the convexify operation, you can split the L-shape into its wings and change the height of the two wings. Again, you'll use random probability to add variation by editing the LFootprint rule:

    LFootprint -->
    	75%  :
    		extrude(height) Mass
    	else :
    		convexify
    		comp(f) { 0   : extrude(height) Mass
    			| all : extrude(height*0.7) Mass }

  3. Save the rule file and generate:

    Varying L-shapes

Side wings now appear on the left and right sides and vary in height. Now, you'll add additional shape typologies.

Add a U-shape

To add a U-shape, do the following:

  1. Call the UShape rule instead of the LShape rule in the starting Lot rule:

    Lot --> 
    	UShape

  2. Similarly, use the shapeU operation and add the UShape and UFootprint rules below the Lot rule:

    UShape -->
    	shapeU(wingWidth, wingWidth*0.7, wingWidth*0.7) { shape : UFootprint }
    
    UFootprint --> 
    	extrude(height) Mass

  3. Save and generate:
    Models with U-shapes
  4. Next, add some variation by randomly rotating some of the U-shapes 180-degrees:

    UShape -->
    	80%  :
    		rotateScope(0, 180, 0)
    		shapeU(wingWidth, wingWidth*0.7, wingWidth* 0.7) { shape : UFootprint }
    	else :
    		shapeU(wingWidth, wingWidth*0.7, wingWidth*0.7) { shape : UFootprint }

  5. Save and generate:

    Varying U-shapes with rotation

You can see that U-shapes do not work well on the all lots. In the next section, you'll combine the L and U-shapes.

L and U-shapes combined

Combine the L and U-shapes and add some variation in the height distribution.

  1. To have better control over the building heights, you'll add the condition to the height attribute that only large area lots can create tall buildings:

    attr height =
    	case geometry.area < 1000 : rand(20, 50)
    	else : rand(50, 150)

  2. In the Lot rule, call LUShape instead of Ushape and add a new rule, LUShape, to control which shape typoloogy is created on which lots:

    Lot -->
    	LUShape
    
    LUShape -->
    	case scope.sx > scope.sz :
    		60%  : UShape
    		else : LShape
    	else : LShape

    U-shapes work best on lots that are wider than they are deep, or translated in CGA grammar, in which scope.sx is larger than scope.sz. In all other cases, you'll trigger only L-shapes.

  3. Save and this time select more shapes to create more building masses.
  4. Generate the buildings:

    L and U-shapes combined

  5. Neither L nor U-shapes work well on non-rectangular lots. The next case statement ensures the UShape and LShape models are only created on approximately rectangular lots (with a tolerance of 15 degrees). Otherwise, you'll call a new footprint rule:

    LUShape -->
    	case geometry.isRectangular(15) :
    		case scope.sx > scope.sz :
    			60%  : UShape
    			else : LShape
    		else : LShape
    	else : BasicFootprint

  6. Since extruded lot shapes are too large compared to the L and U-shapes, add a BasicFootprint rule with a negative offset. This allows more space between individual buildings. Place it after the LFootprint rule:

    BasicFootprint -->
    	offset(-5, inside)
    	extrude(height) Mass

  7. Save and generate:

    L and U-shapes combined with normal footprint extrusions

To see what the scene looks like at this point, open the 01_MassModeling_.cej scene without saving. Click Yes when prompted to regenerate the models.

In the next section, you'll learn how to use recursion in the shape grammar for mass modeling.

Use recursion for mass modeling

Now, you'll model repetitive building elements using recursive shape grammar calls.

Tower shapes

  1. Create a new rule file called myMass_02.cga.
  2. Add the following to the new rule file:

    height =
    	case geometry.area > 1000 : rand(50, 200)
    	else: rand(20, 50)
    	
    Lot -->
    	Tower
    
    Tower -->
    	extrude(height) Envelope
    	
    Envelope -->
    	RecursiveSetbacks

    The height function returns a random value for the building height. The Lot starting rule calls the Tower rule, which extrudes the footprint to the tower envelope. The Envelope rule calls the recursion rule.

  3. For the following recursive rules, you'll need three additional variables: lowHeight, scale, and floorheight and place them after the height function:

    lowHeight =
    	50%  : 0.4
    	else : 0.6
    
    attr scale       = rand(0.75, 0.9)
    attr floorheight = rand(4, 5)

    Since, scale needs to be constant for a building, you'll define it as an attribute. This is because, unlike functions, attributes are only evaluated once at the beginning of the generation process.

  4. Add the RecursiveSetbacks rule after the Envelope rule:

    RecursiveSetbacks -->
    		case scope.sy > 2*floorheight :
    			split(y) { 'lowHeight : Mass | ~1 : Setback }
    		else :
    			s('1, floorheight, '1) Mass

    The RecursiveSetbacks rule splits the mass, as long as it's higher than two floors, into a lower part mass with the lowHeight relative height . The upper remaining part generates the Setback shape.

    If the RecursiveSetbacks shape is smaller than two stories, the remaining part is set to floorheight as the height, and a Mass shape is generated.

  5. Add the Setback rule which scales and centers the shape and recursively invokes the RecursiveSetbacks rule:

    Setback -->
    	s('scale, '1, 'scale)
    	center(xz)
    	RecursiveSetbacks

  6. Save the rule file.
  7. Select all lots in the scene and assign the myMass_2.cga rule file in the Inspector window.

    The simple building masses from the previous rule are changed to towers with setbacks:

    Generated models using recursive shape grammar calls

Round shape

Using an external cylinder asset, you can create round versions of the recursive towers.

  1. Modify the Envelope rule:

    Envelope -->
    	case geometry.isRectangular(20) :
    		20%  : i("cyl.obj") RecursiveSetbacks
    		else : RecursiveSetbacks
    	else : RecursiveSetbacks

    In 20 percent of all towers, you'll insert a cylinder asset instead of using the implicit cube as the underlying shape:

    Cylinder asset compared to implicit cube

  2. Save and generate:

    Mixed rectangular and round recursion towers

The 02_MassModeling.cej scene shows what the scene should look like now.

In the next section, you'll modify the lot parcels with setbacks.

Adapt the parcel with setbacks

Apply setbacks to the lot shapes.

Street setback

To apply street setbacks, do the following:

  1. Create a new rule called myMass_03.cga.
  2. Add the following to the new rule file:

    attr height =
    	case geometry.area > 1000 : rand(50, 200)
    	else : rand(20, 50)
    
    attr distanceStreet =
    	20%  : 0
    	else : rand(3, 6)
    
    Lot --> 
    	Parcel
    	
    LotInner --> 
    	OpenSpace
    
    Parcel -->
    	setback(distanceStreet) { street.front : OpenSpace
    				| remainder    : Footprint }
    
    Footprint -->
    	extrude(height)
    
    OpenSpace -->
    	color("#77ff77")

    The Parcel rule applies a setback on all the street sides of the lot and forwards this area to the OpenSpace rule. The inner part away from street sides, is forwarded to the Footprint rule which extrudes to a random height.

    The street.front selector evaluates the streetWidth shape object attributes, which is automatically set for dynamic lot shapes created from blocks. These attributes may not be present on manually created shapes.

  3. Save the rule and select all lots.
  4. Click Shapes > Delete Models in the main menu to delete all the generated models.
  5. Assign the myMass_03.cga rule to all lots.
  6. Select just a single lot and generate:

    Parcel with street.front setback

    You can see the setback from the street is applied to the selected lot.
  7. Select some additional lots and generate again:

    Multiple lots with just street setbacks

    Notice how the buildings are setback from the street but there isn't open space between them.

Buildings distance

You’ll now add a similar setback to control the distance between the buildings.

  1. Add the distanceBuildings attribute below the distanceStreet attribute:

    attr distanceBuildings =
    	30%  : 0
    	else : rand(4, 8)

  2. Modify the Parcel rule to call the SubParcel rule in the remainder, and add the new SubParcel rule after it:

    Parcel -->
    	setback(distanceStreet) { street.front  : OpenSpace
    				| remainder     : SubParcel }
    
    SubParcel -->
    	setback(distanceBuildings/2) { !street.front  : OpenSpace
    				     | remainder      : Footprint }

    In the SubParcel rule you again apply a setback, but this time on the non-street facing edges.

  3. Save and generate.
  4. Select a model to explore in the Inspector window:

    distanceBuildings and distanceStreet rule parameters manually set in the Inspector window

    The distanceBuildings and distanceStreet are rule parameters which you can adjust to change the setback distances. You can set the values on a single or multiple models.

    To reset to the default random value from the rule file, click the drop-down menu next to either parameter value and click Rule default.

  5. Select all lots and generate:

    Resulting setback parcels

You can open the 03_MassModeling.cej scene, if you want to see an example of the buildings with the setback parcels.

In the next section, you'll combine the mass models from the previous sections with the setback parcels.

Combine masses and setback parcels

Next, you'll combine mass models and the setback parcels.

Import LU shapes and tower mass rules

  1. Ensure that the myMass_03.cga window is active and save it as myMass_04.cga.
  2. Add the following two imports to the top of the rule file after the version statement:

    import lushapes : "myMass_01.cga"
    import towers   : "myMass_02.cga"

    All rules, attributes, and functions from the imported rule file are now available for use in the current rule file.

  3. Modify the Footprint rule:

    Footprint -->
    	case geometry.isRectangular(15):
    		25%  : towers.Tower
    		else : lushapes.LUShape
    	else:
    		25%  : towers.Tower
    		else : lushapes.BasicFootprint

    Instead of a simple extrusion, the Footprint rule now generates the more advanced mass model you created in the previous steps. The geometry.isRectangular function in the case statement makes sure that the LUShapes are only generated on mostly rectangular shapes.

  4. Save the rule file.
  5. Select all lots and assign the myMass_04.cga rule file:

    Generated buildings

    You can see all the lots with setbacks and a mixture of square and round towers with different heights.

    City with mixed LU shapes, tower shapes, and setback parcels

The 04_MassModeling.cej scene shows the buildings and towers with setbacks.

In the next section, you'll add textured facades to the mass models.

Add textured facades

To add textured facades, do the following:

  1. Save the myMass_04.cga rule file as myMass_05.cga.
  2. Add a const function below the attributes that randomly selects one of your 12 facade texture tiles:

    const randomFacadeTexture = fileRandom("*facade_textures/f*.tif")

  3. Next, add the floorHeight attribute:

    attr floorheight = rand(4,5)

  4. To correctly map the texture tiles to the facade, define two functions that calculate the actual floor height and tile width of the current mass:

    actualFloorHeight = 
    	case scope.sy >= floorheight : scope.sy/rint(scope.sy/floorheight)
    	else : scope.sy
    	
    actualTileWidth = 
    	case scope.sx >= 2 : scope.sx/rint(scope.sx/4)
    	else : scope.sx

    With these functions, you ensure that texture tiles are not cut off at the edge of the facade.

  5. Add a Mass rule at the bottom of the rule file using the component split to get the facade components from the mass models:

    Mass --> 
    	comp(f){ side : Facade | top : Roof. }

  6. Next, instruct the imported rules to use this Mass rule:

    towers.Mass -->
    	Mass
    	
    lushapes.Mass --> 
    	Mass

  7. Finally, in the Facade rule, setup the UV coordinates on the facade based on the actual, define the texture file using the randomFacadeTexture function, and project the UVs:

    Facade -->
    	setupProjection(0, scope.xy, 8*actualTileWidth, 8*actualFloorHeight)
    	texture(randomFacadeTexture)
    	projectUV(0)

  8. Save the rule file.
  9. Select all the lots and assign the myMass_05.cga rule to the lots.
  10. Generate the buildings.

    View of the generated buildings

    The models now have randomized textured facades.

    View of the generated buildings showing textured facades

    Generated buildings

    View of the generated buildings showing textured facades from the side

Open the 05_MassModeling.cej scene to see the final textured buildings.

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

  • Generate mass modeling using L and U-shaped buildings.
  • Apply distance and setback variation to parcels.
  • Create buildings with a diversity of shapes, heights, and textures.

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