Version 3 of Recursive curves (table based)

Updated 2004-01-19 23:41:59

MS: this is a version of Recursive curves that avoids floating point operations:

  • as all segments drawn have one of two lengths, thess lengths are precomputed, as well as the depth at which a segment is drawn. The depth is hence kept in an integer.
  • as all angles are multiples of $pi/4, the angle is kept by the integer number of $pi/4
  • the angle-dependent values are precomputed in a table, for the 8 possible values of ($angle%8)

An additional optimisation is that the second recursion is tail-optimised using while.

Note that this is pure sport - the extra optimisation is not really relevant here, there is not much further improvement from the original version as the app is I/O bound (drawing on the canvas).

Also note that this implementation makes additional assumptions:

  • the intial angle is 0
  • (dragon only) the intial value of s is 1

 proc main {} {
    pack [canvas .c -width 240 -height 280]
    set ::points {150 190}
 # 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}
 }

# This just sets up the parameters for # table-based ops; we assume here that # the initial angle is 0.

 proc setParams {len angle minlen} {
     global parList
     set maxDepth [expr {(1+floor(2*log($len/$minlen)/log(2)))}]
     set minlen [expr {$len/pow(sqrt(2),$maxDepth)}]
     set minlen0 [expr {$minlen/sqrt(2)}]
     set parList {}
     lappend parList [list  0.0        $minlen ];# 0
     lappend parList [list  $minlen0   $minlen0];# 1
     lappend parList [list  $minlen    0.0     ];# 2
     lappend parList [list  $minlen0  -$minlen0];# 3
     lappend parList [list  0.0       -$minlen ];# 4
     lappend parList [list -$minlen0  -$minlen0];# 5
     lappend parList [list -$minlen    0.0     ];# 6
     lappend parList [list -$minlen0   $minlen0];# 7

     return [expr {int($maxDepth)}]
 }     

# C curve drawer: assumes initial angle 0

 proc ccurve {w len angle minlen} {
     set maxDepth [setParams $len $angle $minlen]
     ccurveInt $w $maxDepth $angle
 }

 proc ccurveInt {w depth angle} {
    while {$depth} {
        incr depth -1
        ccurveInt $w $depth [expr {$angle + 1}] 
        incr angle -1
    }
    plotline $w $angle
 }

# Dragon curve drawer: assumes initial angle 0, initial s 1

 proc dragon {w len angle s minlen} {
     set maxDepth [setParams $len $angle $minlen]
     dragonInt $w $maxDepth $angle $s
 }

 proc dragonInt {w depth angle s} {
    while {$depth} {
        incr depth -1
        dragonInt $w $depth [expr {$angle + $s}] 1
        if {$s == 1} {
            incr angle -1
            set s -1
        } else {
            incr angle 1
        }
    }
    plotline $w $angle
 }

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

 proc plotline {w ang} {
     global points
     set idx [expr {$ang%8}]
     if {$idx < 0} {
         incr $idx 8
     }
     foreach {xadd yadd} [lindex $::parList $idx] {
         set x [expr {[lindex $points end-1]-$xadd}]
         set y [expr {[lindex $points end  ]-$yadd}]
     }
     if {[llength [lappend points $x $y]]>200} {
         $w create line $points
         set points [list $x $y]
         update
     }
 }

# Let's go!

 main