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".
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:
Question: does the PocketPc have a float coprocessor? If not, the difference with Recursive curves (table based) could be huge.
KPV See also Dragon Curve.
KBK The dragon curve is closely related to the Penney numerals, an interesting binary system for encoding complex numbers.
}
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]
mainI 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 (and brings runtime on the iPaq down to 12 resp. 20 sec):
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.