Version 1 of iFly

Updated 2003-07-17 16:28:05

if 0 {Richard Suchenwirth 2003-07-13 - As you may have read, I'm not afraid of anything, in Tcl coding at least. Another extended weekend, and I started implementing a thing I long wanted to do: flight simulation in Tcl, and on the iPaq for that (but it should run on any other box too). I'm fully aware this can never compete with professional products, but it may be educational for understanding how things work.

http://mini.net/files/iFly.jpg

Let's see what properties an airplane might need. First of all, where are we, in 3D space?

  • latitude, longitude
  • altitude (ft over sea level)

Then our plane has an orientation:

  • heading (compass direction where nose points)
  • horizontal angle (0 for level flight, positive for nose up)
  • vertical angle (0 if both wings are at same height, positive if left is lower)

...and it moves in space:

  • horizontal speed (mph, over ground)
  • vertical speed (1000 ft/min, positive for ascending)
  • gas throttle, which influences speed and fuel consumption

So our model has 10 parameters, between which many dependencies exist. For controllers, we have a gas throttle, done as a number 0..9 to be tapped on screen, and the cursor keys for rudders, mimicking the control stick:

  • <Up> lowers nose
  • <Down> raises nose
  • <Left>, <Right> lower the corresponding wing

Finally, views upon our model: besides numeric display of parameters, I added an "artificial horizon" for visual feedback of horizontal and vertical angles. No objects to be seen on the ground, but at least you can pass through a layer of clouds at 500 ft ;-) The blue line at left is an experiment to display three parameters in a single item: its height represents altitude, its length horizontal speed, while the slope indicates vertical angle. If speed is too low or too high, it turns a warning red. The arrow at bottom right indicates compass heading. }

 proc main {} {
    wm geometry . +0+0
    pack [canvas .c -bg lightblue]
    set ::g(canvas) .c
    .c create window 15 10 -window [button .r -text Reset -command reset]
    .c create window 230 10 -window [button .x -text X -command exit]
    .c create poly 0 100 240 100 240 200 0 200 -fill green -tag ground
    .c create line 110 100 130 100
    .c create line 120 95 120 105
    .c create line 0 190 2 190 -fill blue -tags multi -arrow last
    .c create text 225 175 -tag cname
    .c create line 225 185 225 195 -arrow first -arrowshape {3 5 3} -tag compass
    .c create rect 0 200 240 300 -fill grey -tag dashboard
    .c create rect 15 205 25 215 -fill yellow -outline yellow -tag hilite
    foreach i {0 1 2 3 4 5 6 7 8 9} {
       .c create text [expr $i*20+20] 210 -text $i -tag gas$i
       .c bind gas$i <1> "set g(gas) $i"
    }
    trace var ::g(gas) w {.c coords hilite [.c bbox gas$::g(gas)] ;#}
    set x 10; set y 230
    foreach i {lat lon alt head hor ver fuel spd vspd} {
       .c create text $x $y -text $i -anchor w
       incr x 50
       ctextvar .c $x $y g($i)
       if {[incr x 20]>180} {set x 10 ; incr y 13}
    }
    bind . <Up>   {incr g(hor) -1}
    bind . <Down> {incr g(hor) 1}
    bind . <Left>  {incr g(ver) -1}
    bind . <Right> {incr g(ver) 1}
    reset
 }
 #-- canvas text variable, auto-update
 proc ctextvar {w x y var} {
    $w create text $x $y -tag var$var
    trace var ::$var w "$w itemconfig var$var -text \$::$var ;#"
 }
 proc reset {} {
    foreach event [after info] {after cancel $event}
    array set ::g {
       lat 0 lon 0 alt 0 head 0 hor 0 ver 0 gas 0 spd 0 vspd 0 fuel 5000
    }
    every 250 {recompute; redraw $::g(canvas)}
 }

if 0 {The behavior of the plane is modelled by recomputing parameters from other parameters - please improve if you know better! Some important points:

  • If in flight speed drops below 50 mph, the plane "stalls": the nose dips deep down. PULL UP!
  • If speed exceeds 250 mph, the motor turns off
  • If no fuel is left, the motor turns off too (obviously). You may still glide down.

}

 proc recompute {} {
    global g
    if {$g(gas)>4||$g(alt)} {
      set g(spd) [expr {round($g(spd)+$g(gas)-4-$g(hor)*.5 - abs($g(ver))*.1)}]
    }
    if {$g(alt)>0 && $g(spd)<50} {
        tk_messageBox -message STALL
        set g(hor) -30
    }
    set g(fuel) [expr {$g(fuel)-$g(gas)/2}]
    if {$g(fuel)<=0} {set g(fuel) 0; set g(gas) 0}
    if {$g(spd)>250} {set g(gas) 0}
    set g(vspd) $g(hor)
    incr g(alt) $g(vspd)
    if {$g(alt)<1 && $g(vspd)} land
    set g(head) [format %.1f [expr {fmod($g(head)+$g(ver)*.1+360,360)}]]
    set a [expr {($g(head)-90)*acos(-1)/180}]
    set g(lat) [format %.3f [expr {$g(lat)+cos($a)*$g(spd)*.0001}]]
    set g(lon) [format %.3f [expr {$g(lon)+sin($a)*$g(spd)*.00005}]]
 }
 proc land {} {
    global g
    foreach event [after info] {after cancel $event}
    if {$g(vspd)>-2} {
       set t "Smooth landing - congrats!"
    } elseif {$g(vspd)>-8} {
        set t "Rough landing - plane may need repair"
    } else {
       set t "CRASH!!!\nRelatives will be notified"
       crash
    }
    set dist [expr {hypot($g(lat),$g(lon))*10}]
    append t "\n[format %.1f $dist] miles off target"
    tk_messageBox -message $t
    reset
 }
 proc crash {} {
     set c $::g(canvas)
     $c config -bg red
     $c itemconfig ground -fill red
 }
 proc redraw w {
    global g
    set y [expr {100+$g(hor)*3}]
    set y1 [expr {$y+$g(ver)*3}]
    set y2 [expr {$y-$g(ver)*3}]
    $w coords ground 0 $y1 240 $y2 240 200 0 200
    if {$g(alt)<500} {
       $w config -bg lightblue
       $w itemconfig ground -fill green
    } elseif {$g(alt)<550} {
       $w config -bg white
       $w itemconfig ground -fill white
    } else {
       $w config -bg lightblue
       $w itemconfig ground -fill white
    }
    set x1 [expr {5+$g(spd)/2}]
    set y0 [expr {195-$g(alt)/10}]
    set y1 [expr {$y0-$g(vspd)*2}]
    $w coords multi 0 $y0 $x1 $y1
    set a [expr {($g(head)+90)*acos(-1)/180.}]
    set x [expr {cos($a)*6}]
    set y [expr {sin($a)*6}]
    $w coords compass [expr {225-$x}] [expr {190-$y}] [expr {225+$x}] [expr {190+$y}]
    $w itemconfig multi -fill [expr {$g(spd)>60 && $g(spd)<230? "blue": "red"}]
    $w itemconfig cname -text [compass'name $g(head)]
 }
 proc compass'name hdg {
     lindex {N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW N} [expr {round($hdg/360.*16.)}]
 }
 proc every {ms body} {eval $body; after $ms [info level 0]}

#--

 main

if 0 {Finally, some hints for a successful flight, starting from take-off:

  • Full throttle (9)
  • When speed is above 50, you can pull up
  • Adjust ascent so speed remains constant
  • When you reach desired hight, pull down until vspd=0
  • For level flight at constant speed, set throttle to 4
  • For landing, throttle to 0
  • Adjust descent so speed remains constant
  • Close to ground reduce descent so you finally land with vspd=-1
  • If speed falls close to 50 mph, increase throttle to avoid stall (which is fatal if close to ground)

Have a good flight! }

 bind . <Return> {exec wish $argv0 &; exit}

Arts and crafts of Tcl-Tk programming