Animated Starfield

As a late night project, I converted some VB code I wrote years ago to create an animated starfield. My original VB code can be found here [1 ]. I must say that the job was much easier, and required less code in TK than my original VB version. Anyway, it's not perfect, and not *quite* as complete as the original, but it's kind of interesting... Try clicking and dragging the mouse in canvas area. - Jeff Godfrey


 proc main {} {
    initVars
    buildUI
    getNewVortex [expr {$::global(sfWidth) / 2}] [expr {$::global(sfHeight) / 2}]
    initStars
    # --- give us a way out...
    bind . <Control-c> {destroy .}
    animLoop
 }

 proc initVars {} {
    set ::global(sfWidth)  800
    set ::global(sfHeight) 600
    set ::global(maxStars) 100
 }

 proc initStars {} {
    set xl $::global(xlVortex)
    set xh $::global(xhVortex)
    set yl $::global(ylVortex)
    set yh $::global(yhVortex)
    for {set i 1} {$i <= $::global(maxStars)} {incr i} {
       set xs [expr {($xh - $xl - 1) * rand() + $xl}]
       set ys [expr {($yh - $yl - 1) * rand() + $yl}]
       set me [.c1 create line $xs $ys $xs $ys -width 2 -fill black -tag "star"]

       # --- calculate a random star speed from .1 to 1.1
       set ::global(speed,$me) [expr {rand() + 0.1}]
    }
 }

 proc animLoop {} {
    set xCen     $::global(xCenVortex)
    set yCen     $::global(yCenVortex)
    set sfWidth  $::global(sfWidth)
    set sfHeight $::global(sfHeight)
    set xlVortex $::global(xlVortex)
    set xhVortex $::global(xhVortex)
    set ylVortex $::global(ylVortex)
    set yhVortex $::global(yhVortex)

    foreach star [.c1 find withtag "star"] {
       set starCoords [.c1 coords $star]
       set xs [lindex $starCoords 0]
       set ys [lindex $starCoords 1]
       set xe [lindex $starCoords 2]
       set ye [lindex $starCoords 3]

       set speed $::global(speed,$star)

       # --- calculate the X and Y distances from the current vortex center
       set xVector [expr {abs($xCen - $xe)}]
       set yVector [expr {abs($yCen - $ye)}]

       # --- calculate the star's X direction and length based on
       #     the current vortex X center
       if {$xe > $xCen} {
          set newXe [expr {$xe + int($xVector * 0.2) * $speed}]
       } else {
          set newXe [expr {$xe - int($xVector * 0.2) * $speed}]
       }

       # --- calculate the star's Y direction and length based on
       #     the current vortex Y center
       if {$ye > $yCen} {
          set newYe [expr {$ye + int($yVector * 0.2) * $speed}]
       } else {
          set newYe [expr {$ye - int($yVector * 0.2) * $speed}]
       }

       # --- if new start coord is off the screen, reset it "near" the
       #     vortex center
       if {$xe < 0 || $xe > $sfWidth || $ye < 0 || $ye > $sfHeight} {
          set xs [expr {($xhVortex - $xlVortex - 1) * rand() + $xlVortex}]
          set ys [expr {($yhVortex - $ylVortex - 1) * rand() + $ylVortex}]
          .c1 coords $star $xs $ys $xs $ys
          .c1 itemconfigure $star -fill black
       } else {
          set range [getStarRange $xe $ye]
          set colorVector [expr {$range * 25}]
          set color [format "#%x%x00" $colorVector $colorVector]
          .c1 coords $star $xe $ye $newXe $newYe
          .c1 itemconfigure $star -fill $color
       }
    }
    update
    after 1 animLoop
 }

 # --- Calculate a star's brightness based on its distance from the vortex.
 #     Since this is called within the animation loop for *every* star, it is
 #     computationally expensive.  This should be improved, but it'll do for
 #     now...
 proc getStarRange {x y} {
    set xVector [expr {abs($::global(xCenVortex) - $x)}]
    set yVector [expr {abs($::global(yCenVortex) - $y)}]

    # --- Calculate the distance from vortex center
    set dist [expr {sqrt($xVector * $xVector + $yVector * $yVector)}]

    # --- return a value in the range of 1-10
    set range [expr {int(($dist / $::global(maxRad)) * 10)}]
    if {$range < 1} {
       set range 1
    } elseif {$range > 10} {
       set range 10
    }

    return $range
 }

 proc getNewVortex {xVortexCen yVortexCen} {
    set xCen $xVortexCen
    set yCen $yVortexCen

    # --- calculate a range distance from teh vortex center
    set xOffset [expr {int($::global(sfWidth) * 0.1)}]
    set yOffset [expr {int($::global(sfHeight) * 0.1)}]

    # --- calculate the GLOBAL actual range for both axis'
    #     a new star will always be "born" within this area...
    set xlVortex [expr {$xCen - $xOffset}]
    set xhVortex [expr {$xCen + $xOffset}]
    set ylVortex [expr {$yCen - $yOffset}]
    set yhVortex [expr {$yCen + $yOffset}]

    # --- Calculate a "maximum screen radius".  This is used in the
    #     star's brightness calculation
    if {$::global(sfWidth) < $::global(sfHeight)} {
       set maxRad [expr {$::global(sfWidth) / 2}]
    } else {
       set maxRad [expr {$::global(sfHeight) / 2}]
    }

    set ::global(xCenVortex) $xCen
    set ::global(yCenVortex) $yCen
    set ::global(xlVortex)   $xlVortex
    set ::global(xhVortex)   $xhVortex
    set ::global(ylVortex)   $ylVortex
    set ::global(yhVortex)   $yhVortex
    set ::global(maxRad)     $maxRad
 }

 proc buildUI {} {
    canvas .c1 \
        -width  $::global(sfWidth) \
        -height $::global(sfHeight) \
        -background black \
        -highlightthickness 0 \
        -borderwidth 0
    pack .c1 -fill both -expand 1
    bind .c1 <B1-Motion>     {getNewVortex %x %y}
    bind .c1 <ButtonPress-1> {getNewVortex %x %y}
    wm title . "StarField Demo"
 }

 main