2015-09-20

Basics Part 2: Portraying Numeric Output

Back again, as promised, and we're going to be following on from Yesterday's in-depth dissection of one of the simpler default gauges. You should, if you'd followed this, now have the following XML file called "Attitude2.xml":



1<?xml  version="1.0"  encoding="utf-8"?>
2<Gauge  Name="Attitude  Indicator"  Version="1.0">
3    <Image  Name="Attitude2_Background.bmp"  ImageSizes="320,240,320,240"/>
4    <Element>
5        <Position  X="160"  Y="120"/>
6        <Image  Name="Attitude_Card.bmp"  ImageSizes="240,240,240,240">
7            <Axis  X="120"  Y="120"/>
8        </Image>
9        <Rotate>
10            <Value>(A:sim/cockpit2/gauges/indicators/roll_AHARS_deg_pilot,  ndegree)</Value>
11        </Rotate>
12    </Element>
13    <Element>
14        <Position  X="160"  Y="120"/>
15        <Image  Name="Attitude_Inner.bmp"  ImageSizes="240,240,240,240">
16            <Axis  X="120"  Y="120"/>
17        </Image>
18        <Shift>
19            <Value  Minimum="-25"  Maximum="25">(A:sim/cockpit2/gauges/indicators/pitch_AHARS_deg_pilot,  negative)</Value>
20            <Scale  Y="0.85"/>
21        </Shift>
22        <Rotate>
23            <Value>(A:sim/cockpit2/gauges/indicators/roll_AHARS_deg_pilot,  ndegree)</Value>
24        </Rotate>
25    </Element>
26    <Element>
27        <Position  X="160"  Y="120"/>
28        <Image  Name="Attitude_Outer.bmp"  ImageSizes="240,240,240,240">
29            <Axis  X="120"  Y="120"/>
30        </Image>
31        <Rotate>
32            <Value>(A:sim/cockpit2/gauges/indicators/roll_AHARS_deg_pilot,  ndegree)</Value>
33        </Rotate>
34    </Element>
35    <Element>
26        <Position  X="160"  Y="120"/>
37        <Image  Name="Attitude_Overlay.bmp"  ImageSizes="240,240,240,240"/>
38    </Element>
39</Gauge>


You should also have a modified copy of the background image file, to match the 320x240 dimensions of the screen we're using, like the one to the right here.

The next thing we want to do is add one of the two promised readouts - speed or altitude. You may, like I did when I started out, think "easy, there must be a way of printing to the screen". Nope, there isn't - you may remember from yesterday that the only tool we have to display things on the FIP screen is .bmp images.

So how, then, do we simulate a text display? This involves an image which is essentially a strip of numeric digits in order, and another image called a MaskImage. The mask image is used to ensure that only one character's space will be shown of the main image at any one time, we then use the <Shift> element that we learned about yesterday to shift each digit up and down according to the value of the speed or altitude that we want to show.

This is probably not as simple as it sounds - we have to use expressions to extract each digit of the value we want to display individually, so we are going to need to delve into expressions today just as we delved into general modification of the xml yesterday.


We'll start with the speed readout, and then we'll do the altitude - they are both exactly the same method anyway. You'll see a simple number strip to the left; these are numbers I drew by hand in order to replace the default HSI's, but they didn't end up getting used. There are some important things to recognise about this image:
  • There is an empty space at the top and bottom. These are in case we want to have blank rather than zero values, something we'll forgo in this tutorial as it makes the expressions a bit more complicated
  • The image is of a size that is exactly divisible by 12; 10 digits plus two blanks. Each digit occupies 12x14 pixels, including spaces at the top and bottom.
  • Each character has the same amount of space above and below it. I say it this way because you could have an odd number of pixels between characters, in which case you would need to decide whether the "middle" pixel was added to the top or the bottom.
  • As with all images, this is set to the 24-bit bmp format as mentioned yesterday and elsewhere in this blog and pages.
To illustrate the action of the mask, take a look at this image:; first we see the actual mask image, and this is superimposed in the other images as a pink border around it - everything inside that pink border is colour #010101, the third image showing how the rest of the underlying number strip is hidden from view - finally we are left with just the number 5 showing through - I've left the pink border in for illustration, but of course all you would see on the screen would be that number five, and by sliding the number strip up and down, we can make it show any digit we want.
This number strip is going to be "masked by" a small, 12x14 pixel image. This image will be solid colour #010101 or rgb(1,1,1) if you prefer that notation. This is the masking colour, and when creating a mask image, we use this colour to specify where we want the underlying image to "show through". In this case the whole mask will be the area we want "showing through", so we are simply using the mask's boundary to define where the numbers' boundary should be.

We could also have an image the whole screen size, paint it black and just have a 12x14 box where we wanted the image to show through too, but this over-complicates things and makes it more difficult for you to move your elements around on the screen if you decide you'd rather have it at the bottom rather than the top of the screen, so I wouldn't advise it - the main point here is to paint all the bits of your mask rgb(1,1,1) gray and only those bits of your main image will show through.

Not to be patronising, but I must admit I've found this a little difficult to explain, so I hope you've got the concept by now.

Rather than creating a number strip of our own, for now we'll just use the one from the default HSI. The dimensions for this are a 12x14 mask and a 12x168 number strip. These are called HSIb_character_mask.bmp and HSIb_figure_scale.bmp respectively.

> From yesterday's post, we already know that we are going to need an <Element>, it's going to contain a <Position>, an <Image> and it is also going to contain a <Shift> to move the numbers up and down. We also know that, because we are going to use a <Shift> we are going to need the to have an <Axis> as a point of reference. It makes little sense to reference our element by the centre of the number strip, however, because they are going to be moving up and down; in our case we want the point of reference to be in relation to the mask.

Adding the mask image in to what we already know gives us the following:

<Element>
  <Position X="5" Y="5"/>
  <MaskImage Name="HSIb_character_mask.bmp" ImageSizes="12,14,12,14">
    <Axis X="0" Y="0"/>

  </MaskImage>
  <Image Name="HSIb_figure_scale.bmp" ImageSizes="12,168,12,168">
  </Image>
  <Shift>
    <Value>(A:sim/something/something)</Value>
  </Shift>
</Element>


There are two important bits missing. First is that we need to replace the expression, which currently reads "(A:sim/something/something)", with something sensible to make the numbers move up and down. The second is that in doing so, we're going to need to make the image move 14 times for each number; that is to say that without some sort of mechanism to scale the movement, all we'd expect to see is the number 0 floating slightly up and down because it is only moving one pixel for each digit's increase!

We'll leave the expression bit until last, and discuss this "scaling" mechanism. There are a few ways we could actually achieve this result; using the <Scale> element could be one of them, but that isn't the best way as it requires you to use a "constant" and thus calculate that constant every time you make a change.

We could also just adjust the expression; remember from yesterday that expressions are where you do all your maths and logic, but expressions are in Reverse Polish Notation, and as we're generally used to "normal" mathematical notation they seem complicated to our intuition already without adding in bits to multiply by 14 - better to keep expressions as simple as possible.

The method we're actually going to use is called a <Nonlinearity>, which is a fancy word for "not linear because of this". You can use this to make nonlinear speed "dials" as are found on many aircraft in conjunction with a <Rotate> (although I still run into problems understanding how myself, so we'll gloss over that application for now!), but as we want to know, we can also use them in conjunction with <Shift> and this is what we want to do now. Let's add our <Nonlinearity> and then I'll explain how it works:

<Element>
  <Position X="5" Y="5"/>
  <MaskImage Name="HSIb_character_mask.bmp" ImageSizes="12,14,12,14">
    <Axis X="0" Y="0"/>

  </MaskImage>
  <Image Name="HSIb_figure_scale.bmp" ImageSizes="12,168,12,168">
    <Nonlinearity>
      <Item Value="0" X="0" Y="140"/>
      <Item Value="9" X="0" Y="14"/>
    </Nonlinearity>   </Image>
  <Shift>
    <Value>(A:sim/something/something)</Value>
  </Shift>
</Element>

You may have expected the numbers in the number strip to have 0 at the top and 9 at the bottom, and it's worth taking a moment to explain why this is not the case. We could have laid things out this way, and our <Nonlinearity> would then have different values to reflect this - but this, if the number strip was seen in motion, would lead to the strip going up when the number values were going down and vice versa.

For this reason we make the image such that the numbers appear "backward", so if we are currently at the 2 and the value rises to 3 we would see the strip go up. Similarly if we were at 9 and the number descended to 1 we would see the strip going down, reading 8 then 7 then 6 and so forth.

The <Nonlinearity> has two <Item> elements. Each of these has three attributes, Value, X and Y. The Value is in reference to the <Value> inside our <Shift>, so when our expression says '0', the nonlinearity will convert this to X="0" Y="140" and our expression saying '9' would lead to a position of X="0" Y="14". You'll notice that this is the position in the number strip where the associated digit is placed; number 9 appears 14 pixels down from the top (after the blank space) and number 0 appears 140 pixels from the top (before the other blank space).

Note that the <Nonlinearity> will automatically scale numbers in between these values, so if the expression were to evaluate to 5, the actual position used would be X="0" Y="84" - it's also worth noting that all points in between are catered for, so if you had a value between 4 and 5, say 4.3, you would see the top edge of the 4 and the bottom edge of the 5 - this is how you create those "rolling number" displays.

Now that we have this, all that remains is the expression. The first thing we'll need is the actual X-Plane DataRef to use, rather than this nonsensical "something/something" business. To find this, we could either scan through the DataRefs.txt file in your plugins directory, or we could simply check how they'd done it in one of the standard gauges, the latter giving us:

(A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot)

But this gives us the whole airspeed - unless our indicated airspeed was less than 10, the number strip would move too far down to be visible. We need to add something to this expression so that the value it outputs is just one digit, and we'll start with the first digit of our readout: the hundreds.

This is the bit where I have to explain Reverse Polish Notation, isn't it? This wikipedia entry explains a much more in-depth manner than I can, so please do read that for more information, and I will try and explain in as brief a manner as I can.

As the name suggests, RPN is "reverse notation" of maths. If we consider the simple sum "1+1", we would write this in RPN as "1 1 +". First you have the operands, the subjects of your sum, and then afterwards the operator, which is "+" in this case for addition. We are so used to writing "1+1" that this seems almost nonsensical, but I assure you that doing it this way makes a lot more sense to the computer than it does to you, indeed this is precisely how you do mathematics on a computer: you must have the two operands before you can use the operator on them. Converting from our "native" notation to this computer-ready format would take valuable processing power, and especially where you could potentially have 15 or more of these FIP screens we want the computer to do as little as possible so that you don't take valuable performance away from your flight sim itself.

Let's consider a more complicated expression; in our native notation we'd say this: "1+10*100+1000", and by this we'd actually mean "1+(10*100)+1000". I've put the parentheses here to show how the 10*100 bit is done before the additions. 1+(10*100)+1000=2001 whereas (1+10)*100+1000=2100!

Using RPN we make this order of prescedence implicit; the arrangement of the sum defines which operation should take place first. It's also important to realise that there can be more than two operands - each time you list an operand rather than an operator the value gets pushed to the stack, which you can think of as a list of such values. Every time you use an operator that requires one operand, it will use the last one in the stack, or if it requires two operands, it uses the last two. Therefore we can rearrange the 1+10*100+1000 expression in RPN in various different ways:

1 10 100 * + 1000 +
1000 1 10 100 * + +
10 100 * 1 + 1000 +

In case that is making your head spin, let's break down how the first of these three examples (there are more!) works: We want to do the multiplication first, so it is the first operator we come across reading from left to right. This multiplication uses the last two items in the stack, 10 and 100, and replaces these with the result, 1000. As the stack now consists of "1 1000", we then use a "+" to add them together and the stack would then be "1001". Now we add '1000' operand, making the stack "1001 1000" and finally we add them together with the "+" operator, giving the result of 2001. If you dare, have a look at the second and third examples with this in mind and see if you can describe how they equate to the same.

That's the best way of describing RPN that I can think of, and you probably now can see why it's hard to explain! Back to our example, then, we actually need a way of "extracting" the number of hundreds from a given number in (A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot) - Although some people will understand this easily I will belabor the point along the way for those of you who do not.

(A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot) 100 div

This expression gives us the number of hundreds, if we make the assumption that our speed will never be higher than 999 knots, which I think is a reasonable assumption! We've used the div operator, which performs an integer division.

This will make the display "jump" from one number to another, rather than sliding smoothly, which is what we want to happen - when we get on to the single units I'll explain how we make them roll.

Integer divison, if you don't know, will take two numbers and divide them (just as if I'd written "/") but only give the integer portion of the number: "999 100 / " would give us "9.99" whereas "999/100 div" gives us "9". The reason we made the assumption that we'd never get a number greater than 999 is that just as "1234 100 /" would give us "12.34", "1234 100 div" would give us "12". This would be "off the chart" for our number strip.

We'll need to bear this affect in mind in a moment when when we come to the second digit of the readout (the "tens"), so let's complete what we've just described into our <Element> definition:

<Element>
  <Position X="5" Y="5"/>
  <MaskImage Name="HSIb_character_mask.bmp" ImageSizes="12,14,12,14">
    <Axis X="0" Y="0"/>

  </MaskImage>
  <Image Name="HSIb_figure_scale.bmp" ImageSizes="12,168,12,168">
    <Nonlinearity>
      <Item Value="0" X="0" Y="140"/>
      <Item Value="9" X="0" Y="14"/>
    </Nonlinearity>   
  </Image>
  <Shift>
    <Value>(A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot) 100 div</Value>
  </Shift>
</Element>


This'll give you the readout you require, the hundreds of knots we're currently doing in the top left corner (allowing for a 5 pixel margin; placing things right on the edge can look funny on FIP screens due to it's painted bezel over the LCD). Not much use as a pilot though, you're pretty likely to know that you're doing somewhere between 0 and 99, 100 and 199, 200 and 299 knots intuitively anyway, and that's the readout we're getting. We need the tens next.

This isn't just a matter of "10 div" instead of "100" div, because as described earlier, this would cause the numbers to shoot out of range for anything above 99 knots. We will need to subtract the hundreds from the value first - this is fairly straightforward, as we already know that "100" div gets us this number to subtract. We just need to multiply that by 100 again and take that away from the value before we do our "10 div", thus removing the hundreds and then just giving us the tens (note I've abbreviated "(A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot)" to "(Value)" to make it fit on one line):

(Value) (Value) 100 div 100 * - 10 div

Let's break that down that operation:

  1. Push (Value) to the stack, stack now reads "(Value)" - this will remain in the stack until the end of our expression as we will be subtracting the number of hundreds away from this stack item.
  2. Push (Value) to the stack again, because we need another copy of it to work with and subtract from the previous one. Stack now reads "(Value) (Value)".
  3. Push 100 to the stack so that we can divide our last stack item by it, Stack now reads "(Value) (Value) 100"
  4. Operator div does integer division, stack now reads "(Value) (Value 100 div)"
  5. Push 100 to the stack again so that we can multiply the last value in the stack by it, stack now reads "(Value) (Value 100 div) 100"
  6. Do this multiplication using the "*" operator, stack now reads "(Value) (Value 100 div 100 *)"
  7. Do the all-important subtraction using the "-" operator, stack now reads "(Value)", where (Value) is now the modified number of knots without the hundreds, so if you had 299 knots (Value) would be 99. 
  8. Push 10 to the stack, just as we did before for the hundreds; stack now reads "(Value) 10".
  9. Perform an integer divison using the div operator, stack and final result now read "(Value)" which will be the integer value of tens that we want
So for our second element of this readout, we would need this:

  <Element>
    <Position X="17" Y="5"/>
    <MaskImage Name="HSIb_character_mask.bmp" ImageSizes="12,14,12,14">
      <Axis X="0" Y="0"/>
    </MaskImage>
    <Image Name="HSIb_figure_scale.bmp" ImageSizes="12,168,12,168">
      <Nonlinearity>
        <Item Value="0" X="0" Y="140"/>
        <Item Value="9" X="0" Y="14"/>
      </Nonlinearity>
    </Image>
    <Shift>
      <Value>(A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot) (A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot) 100 div 100 * - 10 div</Value>
    </Shift>
  </Element>


Notice that we've added 12 to the <Position> so that it will appear exactly one "character unit" to the right of our original "hundreds" <Element>. This leads us on to the final element, the units. I am going to explain how to make a "spinner" effect for this, where the number strip slides smoothly between numbers rather than jumping from one integer value to the next.

I'm not going to break down the expression this time, I'm just going to tell you that it's this:

(Value) (Value) 10 div 10 * -

You'll notice that this works similarly to the previous "tens column", but there's no need to do the integer division here because firstly we already have subtracted the tens, hundreds and indeed any higher unit from the value and will be left with the "units"; because we have not used integer division at the end this time, however, we won't necessarily get an integer, and it is this that causes the smooth scrolling effects. So we now have the following for our final digit:

  <Element>
    <Position X="29" Y="5"/>
    <MaskImage Name="HSIb_character_mask.bmp" ImageSizes="12,14,12,14">
      <Axis X="0" Y="0"/>
    </MaskImage>
    <Image Name="HSIb_figure_scale.bmp" ImageSizes="12,168,12,168">
      <Nonlinearity>
        <Item Value="0" X="0" Y="140"/>
        <Item Value="9" X="0" Y="14"/>
      </Nonlinearity>
    </Image>
    <Shift>
      <Value>(A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot) (A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot) 10 div 10 * -</Value>
    </Shift>
  </Element>


The problem, which you may not immediately notice due to the speed at which this number tends to move while you're flying, is that during the transition from a 9 to a 0 or from a 0 to a 9, the blanks at the beginning and end of the file are displayed.

This may or may not be an issue for you, but I don't like it - the answer here would be to insert another 14 characters at the top and bottom of the number strip, and put an extra 0 before the 9 and an extra 9 after the 0; I wasn't intending on doing an image, but to illustrate how it works I think it's best - take a look to the left and you'll see what the modified file looks like next to the original.

With the appropriate modifications (assuming our second number strip is called "HSIb_figure2_scale.bmp") that would give us this:

  <Element>
    <Position X="29" Y="5"/>
    <MaskImage Name="HSIb_character_mask.bmp" ImageSizes="12,14,12,14">
      <Axis X="0" Y="0"/>
    </MaskImage>
    <Image Name="HSIb_figure2_scale.bmp" ImageSizes="12,192,12,192">
      <Nonlinearity>
        <Item Value="0" X="0" Y="156"/>
        <Item Value="9" X="0" Y="28"/>
      </Nonlinearity>
    </Image>
    <Shift>
      <Value>(A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot) (A:sim/cockpit2/gauges/indicators/airspeed_kts_pilot) 10 div 10 * -</Value>
    </Shift>
  </Element>


The extra numbers at the top and bottom will now be shown instead of the blanks (although this will make it a little more awkward if we decide to use those blanks), and the gauge will appear to move through the 0 to 9 transition smoothly.

As a final point of note, we could add " %" to our expression - this would use the modulo operator "%", which gives just the integer portion of the number and cause this elements to behave like the others, jumping from one integer value to the next - this is what I will actually use as an example from here, but I did want to show you how to do it without the "%" mostly as a precursor to my next post.

So we've now created our speed readout, simple as it is. I'm going to take a break here (I've spent two days writing this post), and will be back later with a bit of an extra (and less in-depth) look at how we'll display our altitude in a different way to the speed, as it will be five digits long instead of three and the last two digits (the tens and units) will be changing so fast we could do with a simpler and more meaningful way to portray them.

In the mean time, a good exercise would be to see how far you can get with this on your own, for I have already given you all the tools necessary - then a bit later/tomorrow you can see how I did it and compare - of course this isn't mandatory, and I will cover the reasoning behind taking a slightly different approach then.

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!