Version 3 of Animated Starfield

Updated 2002-04-13 04:28:05

As a late night project, I converted some VB code I wrote years ago to create an animated starfield. 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, 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
    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