I've been a pilot for a short while now, and I felt like applying that to programming somehow (or applying programming to flying). Anyway, one of the things I've been working on lately is VOR intercepts. A VOR is a tower that sends out a signal in 360 degrees that corresponds to a radial. A radial is a straight line from the tower in a given direction. The 360 radial, for example, points north. The 270 radial points west. The VOR instrument in an airplane determines which radial the aircraft is on so you can figure out where you are. This program simulates that instrument.
I've provided a heading indicator and a VOR indicator. The heading indicator is on the left and tells you which direction the airplane is pointing. The VOR indicator shows which radial you are on when the needle is centred. You can turn the aircraft with the left and right arrows (hold shift to turn faster). You can rotate the VOR face with the a/d keys (again, hold shift to rotate it faster).
To figure out what radial you are on, rotate the VOR indicator until the needle is centred and the word 'from' shows up on it. I'm not really going to explain more than that because using a VOR takes a while to learn, but if anyone here is a pilot, they can certainly use it (and I recommend you intercept a couple of outbounds first, since the plane spawns on top of the tower).
If you don't know how to use it, that's fine. There's some pretty neat code in here for you to look at. I've managed to draw numbers evenly around a circle, which is a bit more challenging than it first appears.
Turing: | %VOR instrument simulator
%Draws a VOR indicator and a heading indicator on the screen that correspond to a virtual aircraft and VOR station to allow pilots to practice VOR intercepts.
%Controls: a/d: Rotate VOR face. Holding Shift + a/d rotates the VOR faster. Left/right turns the aircraft by 1 degree per second. Shift+left/right turns the aircraft by 3 degrees per second, a rate 1 turn.
View.Set("graphics:400,400, offscreenonly")%The compass and VOR scales are slaved to the length of the screen, so the UI is scaled automatically
/*Type definitions*/
%Holds values required for drawing a compass face
type Compass : record
x : int %(x,y) is the bottom left corner of the compass
y : int
scale : int %the square dimensions of the compass are (scale, scale)
centreX : int%The centre of the compass face
centreY : int
radius : int %the radius of the compass face
heading : real %the heading to display at the top of the compass
end record
%Holds values for drawing the VOR face, as well as coordinates for the VOR tower
type VOR : record
compass:Compass %A VOR is nearly identical to a compass and uses all the same values.
x : real%(x,y) is the location of the vor tower
y : real
end record
%Represents an aircraft.
type Plane : record
x, y : real%(x,y) is the location of the plane
heading : real%the heading of the aircraft
velocity : real%the speed of the aircraft.
end record
%Fills up a plane record with values
fcn newPlane (x: real, y: real, heading: real, velocity: real):Plane
var out : Plane
out.x := x
out.y := y
out.heading := heading
out.velocity := velocity
result out
end newPlane
%fills up a compass record. Size
fcn newCompass (x: int, y: int, scale: int): Compass %generates a Compass. scale = width = height, (x,y) = lower left corner
var out : Compass
out.x := x
out.y := y
out.scale := scale
out.radius := round(scale/ 2)
out.centreX := x+out.radius
out.centreY := y+out.radius
out.heading := 0
result out
end newCompass
%fills up a VOR record
fcn newVOR (display_x: int, display_y: int, scale: int, tower_x: real, tower_y: real):VOR
var out : VOR
out.compass := newCompass (display_x, display_y, scale )
out.x := tower_x
out.y := tower_y
result out
end newVOR
%This function returns the angle between the positive horizontal axis and ((0,0)(x,y)).
fcn atan2 (x: real, y: real) : real
if x > 0 then result 90- arctand(y/x )
elsif x < 0 then result 270- arctand(y/x )
elsif x = 0 then
if y > 0 then result 0
elsif y < 0 then result 180
elsif y = 0 then result - 1
end if
end if
result - 1
end atan2
%draws the face of the compass with the current heading at the top
proc drawCompass (compass : Compass )
var text_font := Font.New ("Times New Roman:" + intstr (round (compass.scale/ 30)))%the font for drawing headings is scaled to fit the compass face
%draw the outline of the compass
%Just drawing the outline of the compass
Draw.FillBox (compass.x, compass.y, compass.x+compass.scale, compass.y + compass.scale, black)
Draw.FillOval (compass.centreX, compass.centreY, compass.radius, compass.radius, white)
Draw.Oval (compass.centreX, compass.centreY, compass.radius, compass.radius, grey)
%draw the 5 degree ticks, scaled to fit the compass and offset by the current heading of the compass.
%First I draw lines radiating from the centre of the compass, then draw a white circle over part to create ticks.
var tick_length : int := round (compass.radius / 20)
for x: 5.. 355 by 10
Draw.Line (compass.centreX, compass.centreY, compass.centreX + round (compass.radius * cosd(x+compass.heading )), compass.centreY + round (compass.centreY * sind(x+compass.heading )), black)
end for
Draw.FillOval (compass.centreX, compass.centreY, compass.radius - tick_length, compass.radius - tick_length, white)
%draw the 10 degree ticks. Works the same as the 5 degree ticks, but these are a bit bigger.
tick_length := round (compass.radius / 10)
for x: 0.. 350 by 10
Draw.Line (compass.centreX, compass.centreY, compass.centreX + round (compass.radius * cosd(x+compass.heading )), compass.centreY + round (compass.centreY * sind(x+compass.heading )), black)
end for
Draw.FillOval (compass.centreX, compass.centreY, compass.radius - tick_length, compass.radius - tick_length, white)
%draw the 30-degree headings on the compass face.
/*This is actually a little complicated. The function calculates where to draw the headings using the
*formula (x+x_radius * cosd(degree), y + y_radius * sind(degree)), however the radii are adjusted depending
*on the scale of the compass face and an offset is applied to the X and Y coordinates to centre the text. The size
*of the text font is also dependant on the compass scale.
*/
var font_height : int
var font_ascent, font_descent, font_internal_leading : int %see Font.Sizes docs for information on this
var dummy : int %I don't need this value, but I need it.
Font.Sizes (text_font, dummy, font_ascent, font_descent, font_internal_leading )%I use this to find the centre of the font, for centering the text.
font_height := font_ascent - font_descent
var text_y_radius := compass.radius - tick_length - font_height
var text_x_radius : real
var heading_text : string
var text_x_offset : real
var text_y_offset : real := font_height/ 2
for x: 0.. 330 by 30
heading_text := intstr ((- 1*x+ 90) mod 360)
text_x_offset := Font.Width (heading_text, text_font )/ 2
text_x_radius := compass.radius - tick_length - Font.Width (heading_text, text_font )
Font.Draw (heading_text, compass.centreX + round (text_x_radius * cosd(x+compass.heading ) - text_x_offset ), compass.centreY + round (text_y_radius * sind(x+compass.heading ) - text_y_offset ), text_font, black)
end for
if round (compass.heading )= 0 then
heading_text := "360"
else
heading_text := intstr (round (compass.heading ), 3)
end if
Font.Draw (heading_text, round (compass.centreX- (Font.Width(heading_text, text_font )/ 2)), round (compass.centreY + compass.radius* 0. 7), text_font, black)
end drawCompass
%Draws the VOR CDI, what else?
proc drawCDI (vor:VOR, plane:Plane )
var bearing := atan2 (vor.x - plane.x, vor.y - plane.y )
var deviation := 60* sind(bearing - vor.compass.heading + 360)
if deviation > 10 then
Draw.Line (round (vor.compass.centreX + 10 * vor.compass.radius/ 20), round (vor.compass.centreY + vor.compass.radius/ 5), round(vor.compass.centreX + 10 * vor.compass.radius/ 20), round (vor.compass.centreY - vor.compass.radius/ 5), red)
elsif deviation < - 10 then
Draw.Line (round (vor.compass.centreX - 10 * vor.compass.radius/ 20), round (vor.compass.centreY + vor.compass.radius/ 5), round(vor.compass.centreX - 10 * vor.compass.radius/ 20), round (vor.compass.centreY - vor.compass.radius/ 5), red)
else
Draw.Line (round (vor.compass.centreX + (deviation/ 10) * vor.compass.radius/ 2), round (vor.compass.centreY + vor.compass.radius/ 5), round(vor.compass.centreX + deviation * vor.compass.radius/ 20), round (vor.compass.centreY - vor.compass.radius/ 5), red)
end if
end drawCDI
%Draws the TO/FROM flag, of course!
%Basically takes the cosine of the angle between the vor and then plane
%if the angle is positive, it draws TO, negative FROM, and 0 (or near 0) OFF.
proc drawToFromFlag (vor:VOR, plane:Plane )
var text_font := Font.New ("Times New Roman:" + intstr (round (vor.compass.scale/ 30)))
var bearing := atan2 (vor.x - plane.x, vor.y - plane.y )
var deviation := cosd (bearing - vor.compass.heading + 360)
if (deviation > 0. 1) then
Font.Draw ("TO", round (vor.compass.centreX + vor.compass.radius * 0. 2), round (vor.compass.centreY - vor.compass.radius / 2), text_font, black)
elsif (deviation < - 0. 1) then
Font.Draw ("FROM", round (vor.compass.centreX+vor.compass.radius * 0. 2), round (vor.compass.centreY - vor.compass.radius / 2), text_font, black)
else
Font.Draw ("OFF", round (vor.compass.centreX+vor.compass.radius * 0. 2), round (vor.compass.centreY - vor.compass.radius / 2), text_font, red)
end if
end drawToFromFlag
%This just draws a compass, then overlays the VOR specific stuff onto it.
proc drawVOR (vor:VOR, plane:Plane )
drawCompass (vor.compass )
for x: - 5 .. 5
%These ovals should technically correspond to 2-degree deviations, but since sine isn't linear, it doesn't actually do that.
Draw.Oval (round (vor.compass.centreX + 2*x * vor.compass.radius/ 20), round (vor.compass.centreY ), round (vor.compass.scale/ 50), round (vor.compass.scale/ 50), black)
end for
drawToFromFlag (vor, plane )
drawCDI (vor, plane )
end drawVOR
%Draws a compass with heading indicator stuff overlayed
proc drawHeadingIndicator (compass:Compass )
drawCompass (compass )
Draw.ThickLine (compass.centreX, round (compass.centreY + compass.radius* 0. 6), compass.centreX, round (compass.centreY - compass.radius * 0. 6), 3, black)
Draw.ThickLine (round (compass.centreX + compass.radius * 0. 6), compass.centreY, round (compass.centreX - compass.radius * 0. 6), compass.centreY, 3, black)
end drawHeadingIndicator
var compass : Compass := newCompass (0, 0, round (maxx / 2))
var vor : VOR := newVOR (compass.scale, compass.y, compass.scale, 300, 300)
var plane : Plane := newPlane (300, 300, 90, 0. 1)
compass.heading := (90-plane.heading ) mod 360
vor.compass.heading := compass.heading
%this just applies velocity to the aircraft to move it around.
proc movePlane
plane.x := plane.x+plane.velocity * cosd (plane.heading )
plane.y := plane.y+plane.velocity * sind (plane.heading )
end movePlane
var keys : array char of boolean
loop
Input.KeyDown (keys )
movePlane
compass.heading := (90-plane.heading ) mod 360
%a/d rotate the vor by 0.01 degrees. Shift+a/d rotate the vor by 0.3 degrees.
if keys ('a') and keys (KEY_SHIFT ) then
vor.compass.heading := (vor.compass.heading + 0. 3)
elsif keys ('d') and keys (KEY_SHIFT ) then
vor.compass.heading := (vor.compass.heading - 0. 3)
elsif keys ('a') then
vor.compass.heading := (vor.compass.heading + 0. 01)
elsif keys ('d') then
vor.compass.heading := (vor.compass.heading - 0. 01)
end if
if vor.compass.heading <= 0 then vor.compass.heading + = 360
elsif vor.compass.heading > 360 then vor.compass.heading - = 360
end if
%left/right arrows rotate the aircraft by 0.01 degrees. Shift+left/right rotate the aircraft by 0.03 degrees (rate 1)
if keys (KEY_LEFT_ARROW) and keys (KEY_SHIFT ) then
plane.heading := (plane.heading + 0. 03)
elsif keys (KEY_RIGHT_ARROW) and keys (KEY_SHIFT ) then
plane.heading := (plane.heading - 0. 03)
elsif keys (KEY_LEFT_ARROW) then
plane.heading := (plane.heading + 0. 01)
elsif keys (KEY_RIGHT_ARROW) then
plane.heading := (plane.heading - 0. 01)
end if
if plane.heading <= 0 then plane.heading + = 360
elsif plane.heading > 360 then plane.heading - = 360
end if
%draw stuff
drawVOR (vor, plane )
drawHeadingIndicator (compass )
Draw.FillArc (maxx div 2, round (maxy * 0. 75), 5, 10, round (plane.heading - 5 mod 360), round (plane.heading + 5 mod 360), blue)
Draw.FillOval (round (vor.x - plane.x + maxx div 2), round (vor.y - plane.y + maxy * 0. 75), 5, 5, red)
View.Update
Time.DelaySinceLast (10)
cls
end loop |
|