4

I created an a program using python's turtle graphics that simulates tree growth in a forest. There's 3 tree patterns that are randomly chosen, and their starting coordinates and angles are randomly chosen as well. I chose some cool looking tree patterns, but the problem I'm having is that many of the trees are overlapping, so instead of looking like a forest of trees, it looks like a bad 5 year old's painting.

Is there a way to make this overlapping less common? When you look at a forest, some trees and their leaves do overlap, but it definitely doesn't look like this:

enter image description here

Since there's a lot of randomization involved, I wasn't sure how to deal with this.

Here's my code:

import turtle
import random

stack = []

#max_it = maximum iterations, word = starting axiom such as 'F', proc_rules are the rules that 
#change the elements of word if it's key is found in dictionary notation, x and y are the 
#coordinates, and turn is the starting angle 

def createWord(max_it, word, proc_rules, x, y, turn):

    turtle.up()
    turtle.home()
    turtle.goto(x, y)
    turtle.right(turn)
    turtle.down()

    t = 0
    while t < max_it:
        word = rewrite(word, proc_rules)
        drawit(word, 5, 20)
        t = t+1


def rewrite(word, proc_rules):

   #rewrite changes the word at each iteration depending on proc_rules

    wordList = list(word)

    for i in range(len(wordList)):
        curChar = wordList[i]
        if curChar in proc_rules:
            wordList[i] = proc_rules[curChar]

    return "".join(wordList)


def drawit(newWord, d, angle):

    #drawit 'draws' the words

    newWordLs = list(newWord)
    for i in range(len(newWordLs)):
        cur_Char = newWordLs[i]
        if cur_Char == 'F':
            turtle.forward(d)
        elif cur_Char == '+':
            turtle.right(angle)
        elif cur_Char == '-':
            turtle.left(angle)
        elif cur_Char == '[':
            state_push()
        elif cur_Char == ']':
            state_pop()


def state_push():

    global stack

    stack.append((turtle.position(), turtle.heading()))


def state_pop():

    global stack

    position, heading = stack.pop()

    turtle.up()
    turtle.goto(position)
    turtle.setheading(heading)
    turtle.down()


def randomStart():

    #x can be anywhere from -300 to 300, all across the canvas
    x = random.randint(-300, 300)

    #these are trees, so we need to constrain the 'root' of each
    # to a fairly narrow range from -320 to -280
    y = random.randint(-320, -280)

    #heading (the angle of the 'stalk') will be constrained 
    #from -80 to -100 (10 degrees either side of straight up)
    heading = random.randint(-100, -80)

    return ((x, y), heading)


def main():

    #define the list for rule sets.
    #each set is iteration range [i_range], the axiom and the rule for making a tree.  
    #the randomizer will select one of these for building.

    rule_sets = []
    rule_sets.append(((3, 5), 'F', {'F':'F[+F][-F]F'}))
    rule_sets.append(((4, 6), 'B', {'B':'F[-B][+ B]', 'F':'FF'}))
    rule_sets.append(((2, 4), 'F', {'F':'FF+[+F-F-F]-[-F+F+F]'}))

    #define the number of trees to build
    tree_count = 50

    #speed up the turtle
    turtle.tracer(10, 0)

    #for each tree...
    for x in range(tree_count):

        #pick a random number between 0 and the length
        #of the rule set -1 - this results in selecting
        #a result randomly from the list of possible rules.

        rand_i = random.randint(0, len(rule_sets) - 1)
        selected_ruleset = rule_sets[rand_i]

        #unpack the tuple stored for this ruleset
        i_range, word, rule = selected_ruleset

        #pick a random number inside the given iteration_range to be the 
        #iteration length for this command list.
        low, high = i_range
        i = random.randint(low, high)

        #get a random starting location and heading for the tree
        start_position, start_heading = randomStart()

        #unpack the x & y coordinates from the position
        start_x, start_y = start_position

        #build the current tree
        createWord(i, word, rule, start_x, start_y, start_heading)

if __name__ == '__main__': main()
mdegges
  • 963
  • 3
  • 18
  • 39

2 Answers2

2

I think the problem lies more in the regularity of features among the trees themselves, rather than their placement per se.

A possible solution would be to add mutations. For a global control of "stunted growth", you could suppress say 5% of the production applications. This should give sparser trees that follow the model more loosely.

For finer control, you can suppress each production with a different weight.

Check out The Algorithmic Beauty of Plants section 1.7 Stochastic L-systems for more. They use probability to select among several variants of single rule.

luser droog
  • 18,988
  • 3
  • 53
  • 105
2

From my understanding of an L-system there's a whole grammar that isn't randomly chosen. Could you provide details as to how your grammars work? I would imagine that you can somewhat restrict which directions your trees grow into by making a limited, closed-ended set of productions that go anywhere more than 90 degrees from the starting angle.

But then you can't totally randomize the starting angle... You probably need to just randomize it within a certain range? Of course if you have an L-system that just generates everything randomly, it's just gonna look like a bunch of noise. There's a purpose to putting a restriction on your initial conditions; every grammar has a start symbol, and you need to take advantage of your start symbol to generate stuff that makes sense. I'd imagine that you want your start symbol to always point up.

But I haven't studied L-systems in a long time, so take my answer with a grain of salt.

Edit:

That's an interesting restriction to impose, in that it sound like something that trees do naturally. In nature I think it happens because only a certain amount of sunlight can get through to a given patch of ground, so if a tree is already growing somewhere, nothing else can do so.

Being an AI guy, I love turning real-world solutions into interesting heuristics. What kind of a heuristic are we looking for here? Well, a "patch of ground" in a 2-d coordinate system is just a range of x-coordinates. Maybe have something that makes growth lose impetus if there's too much stuff within some arbitrary x range of a growing leaf? (not a python xrange, but a range of x-coordinates, some "delta" value if you will.)

machine yearning
  • 9,889
  • 5
  • 38
  • 51
  • I definitely have ranges for all the randomization in my code, or none of the trees would actually look like trees. They are all restrained to make the trees stand somewhat upright and start at the bottom of the canvas. But I was wondering if there's a way to somehow limit the number of trees that can start at the same coordinates.. Kind of like having a check that says, 'if a tree is already built here, choose random coordinates again' so the trees don't overlap each other so much. – mdegges Sep 30 '11 at 22:53
  • You got it! It's also unrealistic to have trees springing up in the middle of another tree's stem -- that definitely doesn't happen in nature. That's the solution I was looking for- not quite sure how to implement it, but I like the idea. I want to 'cross out' the coordinates of a tree once it's created, so that when another tries to spring up within it's location, it can't. It'll have to be re-randomized again and be given new coordinates. – mdegges Sep 30 '11 at 23:33
  • I think you could implement a simplified version by keeping a Counter object (see collections module) that represented your x ranges. That way you could increment the Counter object for each x "bucket" that your branches enter/cross. Then you can have your L-system default to a "dead" state if some arbitrary critical x-density is reached (if any counter gets over a certain value). – machine yearning Sep 30 '11 at 23:55
  • Thanks for the suggestion. I'm looking into the collections module and will keep you updated. – mdegges Oct 01 '11 at 00:17
  • I ended up just randomly changing the color of a tree and changing the max trees to like 30. To solve this problem, though, I was thinking of arranging the canvas into a grid and placing a random tree in each section. But since the iterations are random (I changed the boundaries from 2, 6), one tree might be super big and grow very tall (into other grid patches) and some might be really small. So in the end I will still have a lot of overlap.. or maybe even not enough, if the trees are too small in some areas. – mdegges Oct 05 '11 at 19:31