2015-09-18

Basics - Part 1: Hacking the standard Saitek Attitude Indicator to use the full screen

I'm conscious that I've promised you all a how to this week, but having finished my HSI at the weekend I've enjoyed flying with it, and in the process have found a couple of quirks that I want to fix in it... After all, the hobby is supposed to be flying, not designing gauges!

I'm also aware that if I launch into "how to make my HSI" then the post is going to be unwieldy and long, even more so than this post is, and a quick scroll down will reveal that I'm going pretty in-depth and as much for a beginner as possible.

So that we can ease into the process without me being able to sell a hardcover edition of the post, I've decided that a better beginning example would be to modify the existing Saitek Attitude Indicator so that it gives me speed and altitude readouts - something I'm sorely lacking on my current 2-FIP setup.

The end result is by no means going to be spectacular (I'll be making my own "FPD" to compliment the HSI soon anyway), but it will give me the missing information to allow me to fly without such reliance on the sim's view being that of the cockpit instruments, and hopefully it should be good for you as the reader too because most of the hard work is done and we can take our time picking through what's already there in order to get a real grasp of how these gauges are constructed.



So, let's take a look at just what's already in the Attitude Indicator, then we can move on to what we need to add. You should (and again, modify the "C:\X-Plane 10" bit if you've installed X-Plane somewhere elese) head to your C:\X-Plane 10\Resources\plugins\XSaitekProFlight\Data\Gauges directory. The file we'll be mostly concerned with is called Attitude.xml, but before we delve into the XML "code", we should first take a look in the 1024 subdirectory.

Images are the only method we have of drawing to the FIP's screen. We cannot write text to the screen and we cannot draw lines and circles; all we can do is rotate, scale or shift (translate) images.

As you'll find by scanning over the (currently a bit sketchy) info pages, all of these images have to conform to the following format:

  • Windows Bitmap File Format (.bmp)
  • 24-bit colour (lower colour depth may work, but higher certainly does not)
  • Background Colour set to #010101 or rgb(1,1,1) if that's a better notation. Note that I simply mean set the background colour to this value in your image editor before you save the image rather than that you need to use this colour instead of black.
This is worth remembering, as a single file that diverges from this format will stop your entire gauge from being displayed!

It's also worth noting that black (#000000 or rgb(0,0,0)) is transparent when rendered on the FIP's screen, but no other colours are. This is very important when creating these images, as it's important to understand that where a colour may "blend" to black, only the truly black pixels will be transparent, and a very dark gray would block out anything behind it - this makes it difficult as the human eye can't distinguish colours that well, and you may often find that you've set a pixel to a really dark colour where you had really intended it to be black, causing your display to have artifacts.
Any gauge folder (this, the most simple of examples, aside, I strongly recommend you put each gauge or set of gauges in its own subdirectory of the Gauges directory - more on this in a later post or check the Info pages) must have a subdirectory called "1024" in which all of its image resources lie. Scanning through this directory, you will see every element from every gauge that came with the plugin.

Had a good look at all those images? You'll notice that the images we're actually going to be dealing with have filenames that begin with "Attitude_" - this is a wise convention when you have 10 or more gauges in a single folder, and I suggest that, even if you have a dedicated directory for your gauge, you decide on a naming strategy and stick with it - putting which gauge the file is needed for at the beginning allows you to quickly glance at all of a gauge's components together in an alphabetical file listing even if they are lumped in with hundreds of other gauges.

So now we've had a look, we discover that there are five images with which we are concerned; the Background, the Card, the Inner, the Outer and the Overlay. Go ahead and open Attitude.xml in your favoured text editor - if you have syntax highlighting, you will easily be able to pick out each filename within the XML and confirm that these are the only .bmp files it refers to.

Dissecting the Gauge

I'm assuming you have the xml file to refer to and a way to reference line numbers easily - click here to open another browser window for it if not. As we read from top to bottom, we'll notice a couple of things we'd expect to see in any XML document; the XML Declaration which for our purposes you can always assume will be "<?xml version="1.0" encoding="utf-8"?>" - this directive tells the XML parser what kind of document it's dealing with, and as were always going to be dealing with the same kind of document, it'll always be the same.

The second thing you'll notice is the root element of the file, which will always be a <Gauge> element, obviously with a closing element at the end of the file. The name and version you specify within the opening Gauge tag doesn't seem to have any actual effect either, but for informational purposes it's always a good idea to keep these updated, that way if you end up with several copies of the same gauge you can compare versions, or if for some reason you give your gauge an abstract name and later wonder "what is this again?"

Inside the Gauge element (between lines 2 and 38 in this example) is contained each <Element> tag in turn. Bear in mind that everything we display on the gauge is ultimately going to be all or part of a .bmp file in the 1024 directory, but before these <Element> defintions there is, on line 3, an <Image> element which is not inside an <Element> at all. This is your all-important background image.

Attitude_Background.bmp
There must be a background image, and the <Image> element must have its ImageSizes property set. It's important to realise that all the elements we draw on top of our background image will be restricted to the confines of this image.

This means that if this image is absent or sized too small, only parts of your gauge that are over this background image will be displayed, so even if you just want a plain black background you will need a background image filled with black, and it will need to be large enough to cover the area you want to display.

In our case, because we want to add things in the extra space, we will want to set this to "320,240,320,240" rather than "240,240,240,240" which will only allow me to use the right-hand two-thirds of the actual screen.

It's also very important to realise that this means I have to have the appropriate amount of pixels to this background image (320 wide rather than 240), or again the gauge will fail to display properly. This is not so for the other elements, which I can just reposition through changing the XML. For our example we will centre the gauge in the screen, so we want to add a 40 pixel strip to both sides of this image, and we'll save it as "Attitude2_Background.bmp", and then adjust line 3 to read:

<Image  Name="Attitude2_Background.bmp"  ImageSizes="320,240,320,240"/>

This allows us to use the whole screen, but also requires us to reposition the other elements over the now-centred background.

Attitude_Card.bmp
Lines 4 through 12 define the "attitude card" - this is the bit that rotates in the background of the gauge to show the roll, and it basically exists as a background to the elements on top of it.

You'll have noticed that the elements are listed in order of appearance, with the image "at the bottom" first in the file and the overlay image (which as its name suggests is overlaid over the gauge just as the background is "underlaid". beneath it - static and unmoving) is last. The order is very important, as we need one element to always be above other elements and yet beneath others!

You can see on line 5 that the "position" is set first - this is the x,y (where x is pixels from the left and y is pixels from the top) co-ordinates of where the gauge should initially appear. I say initially because some animation effects on elements can cause it to move, often before you've even had a chance to see it in this position. This, however, is what we need to modify in order to centre this element. Notice that these co-ordinates are actually set for the centre of the image rather than it's upper-left corner, and this is related to the Axis which we'll come to in a minute. For now, though, just take it as read that this works and apply the same maths we did for adjusting the background - we need to add 40 pixels to the left hand side. Doing this should give us the following replacement for line 5:

<Position  X="160"  Y="120"/>

(Please note that in a moment of stupidity I'd previously typed 80 here and subtracted 40 pixels instead of added - I do apologise to readers from before this edit)

Because all of the elements in the default gauge are centred around a pivotal spot in the middle, all of their positions should be set to the same, so go ahead and replace lines 14 and 27 with the above line too, and we'll say no more about positioning until later when we actually come to add stuff.

On line 6 we see the actual <Image> element that calls for the attitude card and sets its size. In practice I've found that the ImageSizes attribute for images that are inside elements need not be set at all unless you need to force the size to something other than the natural resolution of the file, so we could omit this here all together, but as it's best to be as "formal" with these gauges as possible (lest they crash or otherwise fail) it's probably best to leave them as they are. Because this is an image inside an element, we can move it around to where we want, so we don't need to resize the image or anything of the sort as we did for the background. Just this once we are going to add a couple of the files to the default Gauge directory rather than encapsulating the gauge in its own subdirectory, so we can just refer directly to the original Attitude_Card.bmp instead of our own copy - so this line stays the same.

Line 7 also stays the same, but deserves an explanation (even though it's fairly obvious) - the <Axis> element defines the point of reference for any animation we do with it. In the case of rotation, which is mostly what this gauge is going to do, it defines the centre around which the image will be rotated. This is in reference to the image it is within, so when we say "120,120" we mean 120 pixels from the left hand edge of the image and not the screen, and as we haven't changed this image at all the centre of the image is still located at 120,120. The reason I've used the words "point of reference" rather than "axis of rotation" is because this element is also used when we shift (translate) an image.

Now we come on to line 9, the <Rotate> element - note this is a child of the <Element> and not the <Image>, and you've probably already guessed that this is the bit that tells the gauge what to do with this image; Rotate it. Within this rotate element on line 10, we can see the <Value> element, which tells it how much to do to that image

In actual fact what is inside the <Value> element is what's known as an expression - in this case it is an expression that simply evaluates to a given DataRef in X-Plane that we want to link to.

Those of you who aren't familiar with X-Plane's DataRefs should take a look in their C:\X-Plane 10\Resources\DataRefs.txt where you will find all of the hundreds of bits of data we can reference (it's worth noting that this file is specific to your version of X-Plane - people with older versions may not have all of those datarefs).

Expressions are, by the by, where we do all of our maths and all of our logic. If we need to add different things together, convert data, check if data matches given parameters, and so on... We do it in an expression. The dataref you see in (parentheses) will actually become a number, and as there are no further instructions within this expression, that is the number which will be passed on to the "rotate this element command" if we can put it this way. The more astute of you will also have noticed that the dataref is actually preceeded by "A:" and is succeeded by ", ndegree.

The "A:" is, as far as I can tell, a hangover from the API that Saitek based their plugin on where various letters were available to obtain various other information, such as V: which allowed you to save and retrieve values within your gauge (which would be very useful if it weren't for...) - as far as I can tell none of the other prefixes work, however, so just take it as part of the "language" that datarefs begin with "(A:".

The latter part, ndegree, is responsible for turning the value of the dataref from degrees into radians. There are various different suffixes like these that come into play, and I have to admit I have yet to figure out which one to use when other than experimentation, so in this instance we'll just trust the fact that it already works and leave it be.
In this case we are rotating the image, so whatever is contained within the <Value> element will be used as an angle in radians. If you are not familiar with measuring angles in radians, there are pi radians in 180 degrees - this may seem like an odd way to measure things, but without going into the mathematics and theory, suffice it so say that it does make perfect sense if you're a programmer. It's also very important to note - if you expect something to spin steadily throughout 360 degrees and instead it jumps, seemingly erratically, from one disparate angle to another, you've probably forgotten to convert to radians!

Attitude_Inner.bmp
The next element we come across, line 13, is the "Inner". This is the "main bit" the pilot will see when looking at the indicator, and is the bit that portrays not only roll but also the pitch of the aircraft.

Notice that, aside from the image that it's using, the only other difference between this and the previous element is that it has an extra <Shift> element. This is what's responsible for "shifting" this image up and down, which will then indicate the pitch of the aircraft to the pilot. Because this is an Attitude Indicator, we also need to <Rotate> it as we did the previous element. Note that we want to shift it before we rotate it, not the other way around, so again the order of these elements is important.

We can also see from line 19 that <Value> elements can also have maximum and minimum values; these are what makes this "inner" card stay within view if you climb dive beyond its' limits, rather than it shooting yet higher or lower until it is off the screen entirely. Note the values in this instance are in degrees, and we can surmise that the ", negative" at the end of the dataref means that we want this in the form of a signed number, a number that can go below zero.

Because we're shifting the image up and down we aren't actually using angles, so unlike when we rotated it and we needed to work in radians (albeit invisibly thanks to the ", ndegree") we can work with degrees even more easily, as it is degrees we want to express to the pilot anyway.

The actual units we'll be working with is pixels when we're using <Shift>, so the <Value> here will be shifted up 25 pixels or shifted down 25 pixels, or any point in between... Not quite. If you look very carefully at the image (or just take my word for it) shifting the image up by 25 pixels will not move so that the centre of the screen shows 25 degrees - the scale is out. In order to rectify this, you can see that a <Scale> element has been used here to scale this movement down to 85%, thus matching the actual number of pixels the image needs to move in order to display the angle correctly.

This is also used, however, to specify that we want the shift to happen vertically - notice that there is no "X=" in this Scale - there could be, but including a scale in the X axis would cause it to also be deflected on that axis, and thus it'd no longer be an attitude indicator, it would just be annoying.


Attitude_Outer.bmp
The final "moving layer" is the "Outer" image. This is simply a ring which rotates around its centre, much the same as the previous two images have. The reason this is here is so that the white marks in the "sky region" line up with the orange arrow in the overlay and give a more precise indication to the pilot of what their angle of bank is.

This is functionally identical to the "Card", so I won't break it down for you.

Next, and finally for our dissection, we come to the final element on line 35. This is an unbelievably simple element; it just contains an image which will remain static, ie. not move at all.

Attitude_Overlay.bmp

We do, however, face the problem that this element has no <Position> element, and as such it will just appear in the top left of the screen; we want it centred.

We could do what we did for the background; add 40 pixels of black to the left hand side of the image, save it as a new file and then change the ImageSizes attribute on line 36 accordingly, and this is a perfectly valid way of dealing with it.

In this case, however,  all we will do is insert a <Position> to the element and an <Axis> to the image, just as seen for the card, or the inner, or the outer, right? Doing this will also centre the image, and you can do so without all the resizing malarkey. There is one important piece missing here though: we are not doing anything with this element, it is just going to stay on the screen, overlaid statically over all the other elements. Because we have no <Rotate> or <Shift> action going on, the <Axis> is going to be meaningless.

In this case, we aren't going to add an <Axis> at all, we are only going to add a <Position>, and because we are not using the Axis as a point of reference, the reference will be the top-left of the image rather than its centre as before!

What we need to do is add the following in between lines 35 and 36:

<Position  X="160"  Y="120"/>

And lo and behold, we've reached the end! Hopefully now you can fully understand the file as it stands. As it's kind of late now, I shall continue this lecture/tutorial/post tomorrow; as we've gotten all the boring stuff out of the way here, we can move on to actually adding something.

So far you should now have a different gauge.xml file (which you should, of course, save under a new filename) - while you wouldn't gain much from doing so, you should find that including this gauge in your XSaitekProFlight.xml now centres the gauge. Stay tuned for tomorrow, when as I said I will complete the story and add the vital new elements.


No comments:

Post a Comment

Please feel free to leave comments, add questions, correct my errors, leave handy hints, suggest additions, request new gauges and so forth - No abuse please, and no flaming each other!