Version 13 of Recursive curves

Updated 2004-01-20 02:47:46

if 0 {Richard Suchenwirth 2004-01-18 - The C curve is a quite beautiful image created by a simple recursive function, originally described by William Gosper. I met it in the Winston/Horn book on LISP and of course wanted to have it in Tcl/Tk too, and on my PocketPC. Translation wasn't difficult - somehow Tcl is "the LISP of the 21st century".

http://mini.net/files/ccurve.gif http://mini.net/files/dragon.gif

As the code is a bit slow on PocketPC (171 sec for the C curve, 615 sec for the dragon curve!), I chose to update the canvas after every line segment is drawn, for animation. Also, the related but very different dragon curve was added - I think fractals developed from these studies. So here is yet another weekend fun project, which again demonstrates interesting things in few lines of code:

KPV See also Dragon Curve. AK And IFS.

}

 proc main {} {
    pack [canvas .c -width 240 -height 280]
    set ::x0 150; set ::y0 190
    set ::pi_4 [expr atan(1)]
 # ETFS configurability: uncomment what you want
    set t [time {ccurve .c 120 0 2}]
    #set t [time {dragon .c 160 0 1 2.4}]
    wm title . "[expr {[lindex $t 0]/1000000.}] sec"
    bind . <Return> {exec wish $argv0 &; exit}
 }

# C curve drawer, translated from Lisp

 proc ccurve {w len angle minlen} {
    if {$len<$minlen} {
       plotline $w $len $angle
    } else {
       set len [expr {$len/sqrt(2)}]
       ccurve $w $len [expr {$angle+$::pi_4}] $minlen
       ccurve $w $len [expr {$angle-$::pi_4}] $minlen
    }
 }

# Dragon curve drawer

 proc dragon {w len angle s minlen} {
    global pi_4
    if {$len<$minlen} {
       plotline $w $len $angle
    } else {
       set len [expr {$len/sqrt(2)}]
       dragon $w $len [expr {$angle+$s*$pi_4}] 1 $minlen
       dragon $w $len [expr {$angle-$s*$pi_4}] -1 $minlen
    }
 }

# Plot a line from last end point in specified direction and length

 proc plotline {w len ang} {
    global x0 y0
    update
    set x [expr {$x0-$len*sin($ang)}]
    set y [expr {$y0-$len*cos($ang)}]
    $w create line $x0 $y0 $x $y
    set x0 $x; set y0 $y
 }

# Let's go!

 main

NEM Very nice. Of course, the [update] call in the code slows it down quite a lot. ccurve takes just over 2 seconds with the update on my box (2.4GHz pentium4), while without the update takes just 0.12 seconds. A marginal speed increase can be had by precomputing the sqrt(2). To get a trade-off between speed and updating the GUI, perhaps it would be better to update the screen once a second or so:

 proc plotline {w len ang} {
     global x0 y0 time
     if {[clock seconds] > $time} { update; set time [clock seconds] }
     set x [expr {$x0-$len*sin($ang)}]
     set y [expr {$y0-$len*cos($ang)}]
     $w create line $x0 $y0 $x $y
     set x0 $x; set y0 $y
 }
 set ::time [clock seconds]
 main

I haven't tested this as it takes less than a second to run on my machine at uni.


MS proposes the following: first replace in main

    set ::x0 150; set ::y0 190

by

    set ::points {150 190}

and then redefine plotline to draw only every say 100 points, using a single call to the canvas.

 set count0 100
 set count  100
 proc plotline {w len ang} {
    global points
    set x0 [lindex $points end-1]
    set y0 [lindex $points end]
    set x [expr {$x0-$len*sin($ang)}]
    set y [expr {$y0-$len*cos($ang)}]
    lappend points $x $y
    if {![incr ::count -1]} {
       $w create line $points
       set points [list $x $y]
       update
       set ::count $::count0
    } 
 }

This is noticeably faster already: on my machine the c-curve takes 0.2 sec (down from 4 sec), the dragon curve takes 0.6 sec (down from 16 sec).

Further optimisations are possible in the algorithm itself: inlining the values of 'sqrt(2)' and 'pi_4', replacing the second recursive call by a while loop, maybe specialised trig computations. But the effect will not be as large as this.

RS: Hm... is this worse in performance? At least it's simpler:

 proc plotline {w len ang} {
    global points
    set x [expr {[lindex $points end-1]-$len*sin($ang)}]
    set y [expr {[lindex $points end]-$len*cos($ang)}]
    if {[llength [lappend points $x $y]]>200} {
       $w create line $points
       set points [list $x $y]
       update
    } 
 }

MS notes that it is indeed simpler ... and probably faster too. Even faster is

 proc plotline {w len ang} {
    global points
    lappend points [expr {[lindex $points end-1]-$len*sin($ang)}]
    lappend points [expr {[lindex $points end-1]-$len*cos($ang)}]
    if {[llength points]>200} {
       $w create line $points
       set points [lrange $point end-1 end]
       update
    } 
 }

Just for the sake of it: most floating point operations can be eliminated, as shown in Recursive curves (table based). But it is not that much faster.


Arts and crafts of Tcl-Tk programming