[099] How to Use Recursive Calls for Object Placement in CGA

Master object duplication and spacing logic by implementing recursive CGA rules in CityEngine.

In this tutorial, we will learn how to duplicate objects and arrange them at regular intervals using recursive calls. We will explore the logic of calling a rule within itself, managing variable changes to prevent infinite loops, and using conditional statements to control procedural placement.

In this lesson, we will build a simple recursive placement system that duplicates objects at fixed intervals. This technique is commonly used for procedural repetition, geometric patterns, and rule-based layouts in CityEngine.

CityEngine 3D View showing multiple cubes placed at intervals
img 1 : Example of object spacing using recursive calls

Before diving into the logic, watch how the recursive duplication behaves in practice.

Video: Demonstration of recursive object placement

Understanding the Logic

The example above duplicates an object repeatedly based on the value entered into the count attribute.

Before reviewing the code, take a moment to think about how this repetition might be created procedurally.

The key challenge is not writing the syntax immediately, but understanding how the rule can continue repeating while still knowing when to stop.

What Is a Recursive Call?

A recursive call occurs when a rule calls itself.

In CGA, recursion happens when a rule such as A() invokes itself within its own definition.

Important Condition for Recursion

There must be at least one specific variable that changes every time it is called. This also means you must ensure that a specific variable changes with each call. Only then can you check the change in that variable to decide whether to continue the recursive call or stop. Otherwise, it will repeat infinitely.

Using Parameters to Control Recursion

• Make the rule accept a parameter (how many copies to make), and use a case...else statement to set a condition on the parameter (using the #case_else statement to match the number of copies).

The logic is to move the object by a specific distance until the condition is met (moving and then copying while the condition is satisfied). I explained the concept of copying previously.

Code Breakdown

The following CGA rule demonstrates how recursive placement works.

As I always say, coding styles vary greatly depending on personal preference, so what I'm showing is not the only "correct" answer.


@Range(min = 0, max = 30, stepsize = 1)
attr count = 5
# Enter the number of repetitions.
# Initial value is 5.

@Hidden
attr repeat = count
# Prepare a hidden variable to store the value entered in count.
# It acts like a global variable and will be the variable used for conditions in recursion.

Lot --> # Initial shape
    extrude(10)
    # After extruding by 10
    X.
    # Create the model and finish without any further sub-rules.
    A(count)
    # 01. Copy the extruded model to A and pass 'count' (5) as a parameter.

A(cnt) -->
    # When calling rule A for the first time from Lot
    # It receives the value 5 set in 'count' as a parameter.
    case cnt > 0 :
        # If the number of copies to make is greater than 0
        t(scope.sx*2, 0, 0)
        # Move it by 2 times the width of the model along the X axis.
        X.
        # Create the model and finish without further sub-rules.
        set(repeat, repeat-1)
        # The initial 'repeat' was 5 (same as count), but we subtract 1 from 'repeat'.
        # Now the 'repeat' value becomes 4.
        A(repeat)
        # This time, pass the value 4 to rule A as a parameter.
    else : NIL
    # If the 'cnt' value reaches 0, the rule terminates.

Recursive execution begins when the rule is first called externally. From that point onward, the rule continues invoking itself until the termination condition is met. You must first wake up (call) the rule from the outside.

The `A(count)` in line 18 serves to start the `A(cnt)` rule in line 21. Once awakened, `A(cnt)` performs the conditional statement and starts calling itself from the moment it encounters `A(repeat)` in line 34.

Initially, the `count` value was passed to `A(cnt)`, but from the second call onwards, the `repeat` value is passed. The initial `repeat` value was the same as `count`, but it is reduced by 1 in line 30 before being passed as a parameter.

Simultaneously, the position of the box moves along the X-axis by 2 times the box size, and when the `repeat` value becomes 0, it exits the `A(cnt)` rule.

There is a difference from general programming, isn't there? In general programming, you can freely create global or local variables anywhere, but in CGA, you must create them as `attr` and register them.

Many of you probably encountered errors because of this. Keep this in mind as a reference.

Why attr Matters in CGA

`attr` only shows real-time changes within the Inspector panel when you adjust them there; changes made within a rule are not reflected in the Inspector panel in real-time. Therefore, when used like a global variable as we did here, we use `@Hidden` to hide it from the Inspector panel. (However, if the value of an `attr` is changed using a Python script, it will be reflected in the Inspector panel.)

Practice Challenge

Try extending this example so the object moves along both the X-axis and Y-axis during recursion.

Experimenting with variations like this is one of the best ways to understand procedural logic.

This recursive structure becomes a foundation for many advanced procedural modeling workflows in CityEngine.

Previous Post
  • Learn previous workflow in [098] Transitioning from Basic Geometry to Advanced Urban Districts
Next Post
  • Continue learning with [100] How to Use Sine and Cosine Functions in CityEngine CGA
"Recursion is the root of computation since it allows us to specify an infinite set of objects by a finite set of words."
— John McCarthy
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