Turtle Graphics

January 3, 2012

We choose PostScript as our output device, but our use of PostScript will be primitive: a very brief initialization, an even briefer termination, and only moveto lineto commands in between. We will keep track ourselves of the turtle state — the current x-position and y-position, the current heading, and whether the pen is up or down — because it is hard to get such information back from PostScript to the controlling Scheme script. Here’s the initialization:

(define (clearscreen)
  (set! xpos 0)
  (set! ypos 0)
  (set! pen? #t)
  (set! head 0)
  (send "%!")
  (send "initgraphics")
  (send "newpath 306 396 moveto")
  (send "currentpoint translate")
  (send "0 setgray 2 setlinewidth"))

We reset Scheme’s turtle coordinates then send five initialization commands: %! identifies the output as PostScript, initgraphics initializes PostScript to accept graphics commands, newpath 306 396 moveto starts a new drawing and moves the PostScript turtle to the center of an 8%half;-by-11 page, currentpoint translate resets the current PostScript turtle to the point 0,0 so it is the same as the Scheme turtle, and 0 setgray 2 setlinewidth sets a black pen with width 2. There is no scale command, so a turtle step will be PostScript’s default measure of 1/72 of an inch.

The penup and pendown commands are trivial; the global variable pen? is #t when the pen is down:

(define (penup) (set! pen? #f))
(define (pendown) (set! pen? #t))

The most complicated commands are forward and back. We compute new coordinates using trigonometry (one degree, which is the measure used by the turtle, is equal to 0.017453292519943295 radians, which is the measure used by the Scheme sin and cos functions), write a PostScript command using either lineto or moveto depending on whether the pen is down or up, and reset the global turtle position:

(define (forward n)
  (let ((newx (inexact->exact (round
          (+ xpos (* n (sin (* head 0.017453292519943295)))))))
        (newy (inexact->exact (round
          (+ ypos (* n (cos (* head 0.017453292519943295))))))))
    (send newx newy (if pen? "lineto" "moveto"))
    (set! xpos newx) (set! ypos newy)))

(define (back n)
  (let ((newx (inexact->exact (round
          (- xpos (* n (sin (* head 0.017453292519943295)))))))
        (newy (inexact->exact (round
          (- ypos (* n (cos (* head 0.017453292519943295))))))))
    (send newx newy (if pen? "lineto" "moveto"))
    (set! xpos newx) (set! ypos newy)))

Left and right are simpler; they merely reset the global turtle heading, wrapping around at 360 degrees:

(define (left n) (set! head (modulo (- head n) 360)))
(define (right n) (set! head (modulo (+ head n) 360)))

Setpos and setheading reset the global turtle state; setpos also moves the PostScript turtle, drawing a line if the pen is down.

(define (setpos x y)
  (send x y (if pen? "lineto" "moveto"))
  (set! xpos x) (set! ypos y))
(define (setheading n) (set! head n))

Pos and heading return the appropriate values from the global turtle state; done performs the very brief termination:

(define (pos) (values xpos ypos))
(define (heading) head)

(define (done) (send "stroke showpage"))

Finally, we use the send command to write output:

(define (send x . xs)
  (cond ((null? xs) (display x) (newline))
  (else (display x) (display " ") (apply send xs))))

And that’s it. As a demonstration of the library, here is the tree function from Harvey’s book, and the commands to draw the tree shown on the previous page:

(define (tree size)
  (cond ((< size 5) (forward size) (back size))
  (else (forward (/ size 3))
        (left 30) (tree (* size 2/3)) (right 30)
        (back (/ size 3))
        (forward (/ size 2))
        (right 25) (tree (/ size 2)) (left 25)
        (back (/ size 2))
        (forward (* size 5/6))
        (right 25) (tree (/ size 2)) (left 25)
        (back (* size 5/6)))))

(with-output-to-file "tree.ps"
  (lambda ()
    (clearscreen)
    (penup)
    (back 300)
    (pendown)
    (tree 400)
    (done)))

You can run the program at http://programmingpraxis.codepad.org/cX7wPlQa.

About these ads

Pages: 1 2

2 Responses to “Turtle Graphics”

  1. A module in python 3 and an example tree rewritten. Uses pygame.

    
    # myturtle.py
    
    import pygame
    import math
    
    _center = (400, 300)
    _pos = (0, 0)
    _ang = 0
    _pen = False
    _surf = None
    _changed = True
    _color = (0, 0, 0)
    
    def init_turtle(surf):
        global _surf, _center
        _surf = surf
        _center = (_surf.get_width() // 2, _surf.get_height() // 2)
    
    def pendown():
        global _pen    
        _pen = True
    
    def penup():
        global _pen
        _pen = False
    
    def clearscreen():
        global _surf, _changed, _pos
        _surf.fill((255, 255, 255))
        _pos = (0, 0)
        _changed = True
    
    def _fwdbackutil(v):
        global _surf, _pos, _ang, _changed
        vr = _rot(v, _convert_angle(_ang))
        new_pos = _vsum(_pos, vr)
        if _pen:
            pygame.draw.aaline(_surf, _color, _convert_pos(_pos), _convert_pos(new_pos))
            _changed = True
        _pos = new_pos
    
    def forward(n):
        _fwdbackutil((n, 0))
    
    def back(n):
        _fwdbackutil((-n, 0))
        
    def right(a):
        global _ang
        _ang += a
        while _ang > 360.0:
            _ang -= 360.0
    
    def left(a):
        global _ang
        _ang -= a
        while _ang < 0.0:
            _ang += 360.0
    
    def setpos(new_pos):
        global _surf, _pos, _changed    
        if _pen:
            pygame.draw.aaline(_surf, _color, _convert_pos(_pos), _convert_pos(new_pos))
            _changed = True
        _pos = new_pos
    
    def setheading(a):
        global _ang
        _ang = a
    
    def pos():
        return _pos
    
    def heading():
        return _ang
    
    def changed():
        return _changed
    
    def _convert_angle(deg):
        return (90 - deg) / 360.0 * 2 * math.pi
    
    def _convert_pos(pos):
        return (pos[0] + _center[0], _center[1] - pos[1])
    
    def _rot(v, a):
        sa = math.sin(a)
        ca = math.cos(a)
        return (v[0] * ca - v[1] * sa, v[0] * sa + v[1] * ca)
    
    def _vsum(x, y):
        return (x[0] + y[0], x[1] + y[1])
    
    if __name__ == "__main__":
    	print("usage: import myturtle")
    
    
    # turtledemo.py
    
    import pygame
    import os
    import time
    import math
    from  myturtle import *
    
    def tree(r):
        if r < 5:
            forward(r)
            back(r)
        else:
            forward(r / 3)
            left(30)
            tree(r * 2 / 3)
            right(30)
            back(r / 3)
    
            forward(r / 2)
            right(25)
            tree(r / 2)
            left(25)
            back(r / 2)
            
            forward(r * 5 / 6)
            right(25)
            tree(r / 2)
            left(25)
            back(r * 5 / 6)
    
    os.environ['SDL_VIDEO_CENTERED'] = '1'
    
    pygame.init()
    
    mysurf = pygame.Surface((800, 600), depth=32)
    
    init_turtle(mysurf)
    
    clock = pygame.time.Clock()
    running = True
    
    try:
        screen = pygame.display.set_mode((800, 600))
        pygame.display.flip()
        mysurf.fill((255, 255, 255))
    
        g_surf = mysurf
    
        setpos((0, -300))
        pendown()
        tree(400)
        
        while running:
    
            clock.tick(100)
    
            for evt in pygame.event.get():
                if evt.type == pygame.KEYDOWN and evt.key == pygame.K_ESCAPE or \
                   evt.type == pygame.QUIT:
                    running = False
                    break
                
            screen.blit(mysurf, (0, 0))
            pygame.display.flip()
    finally:
        pygame.quit()  
    
    
    
  2. [...] you read books about Logo while you were a kid you remember all that fun with turtle graphics, using loops to draw circles and recursion to generate really complicated [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 574 other followers

%d bloggers like this: