Random City Skyline

Another presentation to talk about what computer programs are and how they work and I needed a short demo on how to iteratively build a program in python which had some kind of obvious visual component so that changes to the program would be reflected in the remote session.

This turned into how to build a random cityscape and a city skyline at sunset.

Straight Lines

Since this was going to be done in processing I started off with a sketch that had an orange-y background and added a single line crossing the screen.


roofline = [] 

def setup():
    fullScreen()
    stroke(255)
    frameRate(10)
    
    global roofline
    roofline.append(int(height / 2))
    
def draw():
    orange = color(192, 64, 0)
    background(orange)
    
    global roofline
    x = 0  
    x_step = int(width / 50)
    last_y = 0
      
    for roof in roofline:
        stroke(255)
        if last_y > 0:
            line(x, last_y, x, roof)
        line(x, roof, x + x_step, roof)
        x += x_step
        last_y = roof
        
    # each step, add a new section to the line    
    y = last_y  
    roofline.append(y)  

This line will carry on forever so it would be nice to reset it when we reach the far edge of the screen.


roofline = [] 

def reset():
    global roofline
    roofline = []    
    
    saveFrame("output-####.png")
    
def setup():
    fullScreen()
    stroke(255)
    frameRate(10)
    
    global roofline
    roofline.append(int(height / 2))
    
def draw():
    orange = color(192, 64, 0)
    background(orange)
    
    global roofline
    x = 0  
    x_step = int(width / 50)
    last_y = 0
      
    for roof in roofline:
        stroke(255)
        if last_y > 0:
            line(x, last_y, x, roof)
        line(x, roof, x + x_step, roof)
        x += x_step
        last_y = roof
        
    # each step, add a new section to the line    
    y = last_y  
    roofline.append(y)  
    
    if x > width:
        reset()

This is all wonderful to get started but there's not a lot of variety yet and it doesn't look much like a city yet.

Wiggly Lines

Introducing some randomness into the height of the line as it crosses the screen makes things a bit more interesting.


roofline = [] 

def reset():
    global roofline
    roofline = []    
    
    saveFrame("output-####.png")
    
def setup():
    fullScreen()
    stroke(255)
    frameRate(10)
    
    global roofline
    roofline.append(int(height / 2))
    
def draw():
    orange = color(192, 64, 0)
    background(orange)
    
    global roofline
    x = 0  
    x_step = int(width / 50)
    last_y = 0
      
    for roof in roofline:
        stroke(255)
        if last_y > 0:
            line(x, last_y, x, roof)
        line(x, roof, x + x_step, roof)
        x += x_step
        last_y = roof
        
    # each step, add a new section to the line    
    y = last_y  
    
    if random(100) > 60:
        border = int(height / 4)
        y = random(border, height - border)
        
    roofline.append(y)  
    
    if x > width:
        reset()

That is starting to look like a city skyline. We can do more by filling in the bottom half of the rectangles to make them black as if the sunset is casting a shadow.

Crooked Teeth

In homeage to the Ben Gibbard song "Crooked Teeth" about a city skyline, if we fill in the buildings in black we get this.


roofline = [] 

def reset():
    global roofline
    roofline = []    
    
    saveFrame("output-####.png")
    
def setup():
    fullScreen()
    frameRate(10)
    
    global roofline
    roofline.append(int(height / 2))
    
def draw():
    orange = color(192, 64, 0)
    dark_gray = color(45, 45, 45)
    background(orange)
    
    global roofline
    x = 0  
    x_step = int(width / 50)
    last_y = 0
      
    for roof in roofline:
        stroke(255)
        if last_y > 0:
            line(x, last_y, x, roof)
        line(x, roof, x + x_step, roof)

        fill(dark_gray)
        stroke(dark_gray)
        rect(x, roof + 1, x_step, height - roof)
       
        x += x_step
        last_y = roof
        
    # each step, add a new section to the line    
    y = last_y  
    
    if random(100) > 60:
        border = int(height / 4)
        y = random(border, height - border)
        
    roofline.append(y)  
    
    if x > width:
        reset()

Note I have left the white line around the buildings as a highlight from the setting sun.

Anybody Home?

The last detail to add in this scenario as the city is getting ready for night, is the appearance of lights in a few windows in the buildings we can see.


roofline = [] 
windows = []

def reset():
    global roofline, windows
    roofline = []    
    windows = []

    saveFrame("output-####.png")
    
def setup():
    fullScreen()
    frameRate(8)
    
    global roofline
    roofline.append(int(height / 2))
    
def draw():
    orange = color(192, 64, 0)
    dark_gray = color(45, 45, 45)
    background(orange)
    
    global roofline, windows
    x = 0  
    x_step = int(width / 50)
    last_y = 0
    roof = 0

    for roof in roofline:
        stroke(255)
        if last_y > 0:
            line(x, last_y, x, roof)
        line(x, roof, x + x_step, roof)

        fill(dark_gray)
        stroke(dark_gray)
        rect(x, roof + 1, x_step, height - roof)
       
        x += x_step
        last_y = roof
        
    yellow = color(255, 255, 0) 
    for window in windows:    
        window_height = int(height / 100)
        window_width = int(window_height /2 ) 
        fill(yellow)
        rect(window[0], window[1], window_width, window_height)
        
    # each step, add a new section to the line    
    y = last_y  
    
    if random(100) > 60:
        border = int(height / 4)
        y = random(border, height - border)
        
    roofline.append(y)  
    
    if random(100) > 60:
        window_x = int(random(x - x_step, x))
        window_y = int(random(roof + 10, height - 50))
        windows.append([window_x, window_y])

    if x > width:
        reset()

In the same way that each call to draw adds a new section of building, I add a random window to the scene as well now.

Depending on how fast this runs, you may want to change the frame rate so that it's not going by too quickly.

City Depth

As a final, final experiment I added another layer of buildings behind the main layer. Not sure how well this works.


roofline = [] 
distance = [] 
windows = []

def reset():
    global roofline, distance, windows
    roofline = []    
    distance = []
    windows = []

    saveFrame("output-####.png")
    
def setup():
    fullScreen()
    frameRate(10)
    
    global roofline, distance
    roofline.append(int(height / 2))
    distance.append(int(height / 4))
    
def draw():
    orange = color(192, 64, 0)
    light_gray = color(150, 150, 150)
    dark_gray = color(45, 45, 45)
    background(orange)
    
    global roofline, distance, windows
    x = 0  
    x_step = int(width / 50)
    last_y = 0
    roof = 0

    for roof in distance:
        fill(light_gray)
        stroke(light_gray)
        rect(x, roof + 1, x_step, height - roof)
       
        x += x_step
        last_y = roof

    x = 0  
    for roof in roofline:
        stroke(255)
        if last_y > 0:
            line(x, last_y, x, roof)
        line(x, roof, x + x_step, roof)

        fill(dark_gray)
        stroke(dark_gray)
        rect(x, roof + 1, x_step, height - roof)
       
        x += x_step
        last_y = roof
        
    yellow = color(255, 255, 0) 
    for window in windows:    
        window_height = int(height / 100)
        window_width = int(window_height /2 ) 
        fill(yellow)
        rect(window[0], window[1], window_width, window_height)
        
    # each step, add a new section to the line    
    y = last_y  

    if random(100) > 75:
        border = int(height / 3)
        y = random(border, height - border)
    
    distance.append(y)  
    
    y = last_y
      
    if random(100) > 60:
        border = int(height / 4)
        y = random(border, height - border)
        
    roofline.append(y)  
    
    if random(100) > 60:
        window_x = int(random(x - x_step, x))
        window_y = int(random(roof + 10, height - 50))
        windows.append([window_x, window_y])

    if x > width:
        reset()