Starfield

Description

Keith Vetter 2003-10-06 -- I bet a lot of people have written this visually fun little program. Here's my version that I wrote several years ago based on some Java applet. I reorganized the code a bit based on DKF's observation from Rain Storm that deleting a canvas item is leaky (the tags are the problem).

Double clicking in the window will toggle the display of a control panel.


Jeff Smith 2019-05-10 : Below is an online demo using CloudTk


Code

##+##########################################################################
#
# stars.tcl -- Does a starfield animation
# by Keith Vetter  Oct 5, 2003
#
package require Tk
 
array set S {w 500 h 500 afterID "" delay 20 lbl,0 "Go" lbl,1 "Stop"}
array set G {go 1 rot 0 drot 1}
 
##+##########################################################################
# 
# NewStar -- Creates new stars of a given type
# 
proc NewStar {cnt type} {
    global S STARS
 
    if {! [info exists STARS(cnt)]} {set STARS(cnt) -1}
    while {[incr cnt -1] >= 0} {
        set x [expr {rand() * $S(w) - $S(w2)}]  ;# Select x,y,z for new star
        set y [expr {rand() * $S(h) - $S(h2)}]
        if {$x == 0 && $y == 0} {set x 10}
        set z [expr {int(rand() * 100)}]
 
        set idx [incr STARS(cnt)]               ;# Save into our global array
        set STARS($idx) [list $x $y $z $type]
        set n [.c create rect -999 -999 -999 -999]
        set STARS(tag,$idx) $n
    }
}
##+##########################################################################
# 
# StarDraw -- draws one star
# 
proc StarDraw {idx} {
    global STARS S G
 
    foreach {x y z type} $STARS($idx) break
    incr z -2                                   ;# It's getting closer
    if {$z < -63} {set z 100}
 
    set xx [expr {($x*64) / (64+$z)}]           ;# Divide by z to get location
    set yy [expr {($y*64) / (64+$z)}]
    set X [expr {$xx * $G(rot,cos) - $yy*$G(rot,sin)}] ;# Rotate if needed
    set Y [expr {$xx * $G(rot,sin) + $yy*$G(rot,cos)}]
    if {abs($X) > $S(w2) || abs($Y) > $S(h2)} {set z 100} ;# Offscreen?
    lset STARS($idx) 2 $z
    
    set color [expr {$z > 50 ? "gray" : $z > 35 ? "lightgray" : "white"}]
    if {$type == 0} {
        set d [expr {(100 - $z) / 50}]
        if {$d == 0} {set d 1}
        .c coords $STARS(tag,$idx) [MakeBox $X $Y $d]
        .c itemconfig $STARS(tag,$idx) -fill $color
    } else {                                    ;# Cross hair type star
        .c delete star_$idx
 
        set d [expr {(100 - $z) / 20}]
        foreach {x0 y0 x1 y1} [MakeBox $X $Y $d] break
        .c create line $x0 $Y $x1 $Y -tag star_$idx -fill $color
        .c create line $X $y0 $X $y1 -tag star_$idx -fill $color
        if {$z < 50} {
            set d [expr {$d / 2}]
            foreach {x0 y0 x1 y1} [MakeBox $X $Y $d] break
            .c create line $x0 $y0 $x1 $y1 -tag star_$idx -fill $color
            .c create line $x1 $y0 $x0 $y1 -tag star_$idx -fill $color
        }
    }
}
##+##########################################################################
# 
# StarAnimate -- does one round of updating all the stars then schedules
# itself to be called again after a short delay.
# 
proc StarAnimate {} {
    global G S STARS
 
    set now [clock click -milliseconds]
    after cancel $S(afterID)
    if {! $G(go)} return
 
    set G(rot) [expr {$G(rot) + $G(drot)*3.14159/180}]
    set G(rot,cos) [expr {cos($G(rot))}]
    set G(rot,sin) [expr {sin($G(rot))}]
 
    for {set j 0} {$j <= $STARS(cnt)} {incr j} {
        StarDraw $j
    }
    if {! $G(go)} return
 
    # Keep delay between rounds constant if possible
    set t [expr {[clock click -milliseconds] - $now}]
    set delay [expr {$S(delay) - $t}]
    if {$delay <= 0} {set delay 1}
    set S(afterID) [after $delay StarAnimate]
}
proc Stop {} {
    global G S
 
    set G(go) [expr {! $G(go)}]
    .stop config -text $S(lbl,$G(go))
    if {$G(go)} StarAnimate
}
proc MakeBox {x y d} {
    return [list [expr {$x-$d}] [expr {$y-$d}] [expr {$x+$d}] [expr {$y+$d}]]
}
proc Scaler {n} {.rot config -label "Rotation: $n"} ;# Relabels scale widget
proc ToggleCtrl {} {                            ;# Toggles control panel
    if {[winfo ismapped .bottom]} {
        pack forget .bottom
    } else {
        pack .bottom -side bottom -fill x -before .c
    }
}
proc ReCenter {W h w} {                   ;# Called by configure event
    global S
    foreach S(w) $w S(h) $h break
    foreach S(w2) [expr {$w /2}] S(h2) [expr {$h /2}] break
    $W config -scrollregion [list -$S(w2) -$S(h2) $S(w2) $S(h2)]
}
proc DoDisplay {} {
    canvas .c -width $::S(w) -height $::S(h) -bg black -highlightthickness 0
    frame .bottom
    button .stop -text "Stop" -command Stop -width 5
    .stop configure  -font "[font actual [.stop cget -font]] -weight bold"
    scale .rot -from -5 -to 5 -orient h -variable G(drot) \
        -showvalue 0 -command Scaler -relief ridge
    pack .c -side top -expand 1 -fill both
    pack .stop .rot -in .bottom -side right -expand 1
    image create photo ::img::blank -width 1 -height 1
    button .about -image ::img::blank -highlightthickness 0 \
        -command {tk_messageBox -message "Stars\nby Keith Vetter, October 2003"}
    place .about -in .bottom -relx 1 -rely 0 -anchor ne
    bind .c <Configure> {
        ReCenter %W %h %w
        NewStar 10 1
        NewStar 90 0
        StarAnimate
        bind .c <Configure> {
            ReCenter %W %h %w
        }
    }
    bind all <Double-Button-1> ToggleCtrl
}
################################################################
 
DoDisplay

uniquename 2013aug18

For those readers who do not have the time/facilities/whatever to setup the code above and execute it, here is an image that shows what the starfield looks like --- but this static image does not do justice to the fact that the viewer seems to be flying toward/among the stars and the stars are rotating (or the viewer is doing slow barrel rolls).

vetter_Starfield_wiki10100_screenshot_503x371.jpg

If the user double-clicks on the starfield (canvas), a 'Rotation' Tk 'scale' widget appears at the bottom of the GUI, by which the user can change the direction and speed of the rotation.

Richard Suchenwirth also provided a similar code at A minimal starfield, apparently in 2005 --- a couple of years after Vetter seems to have posted this code.