FlightGear: Using canvas in a 3d instrument

FlightGear: Using canvas in a 3d instrument admin 10 April, 2016 - 16:17

Introduction

Currently the usual way of making an instrument in FlightGear is to draw a 3D model, texture it appropriate and animate the individual elements. For needles this usually means a rotation, and for drums a texture map translation. This workflow is well understood and works really well and what I’m presenting here isn’t intended to replace this.

Canvas allows us to render a 2D element onto a 3D quad. So what we can do is to draw the instrument in SVG (using Inkscape) and then just animate the elements.

Supporting classes

I’ve written canvas_instrument.nas which provides the basic building blocks for a single instrument; together with canvas_altimeter.nas as an example. All files are attached to this post

Implementation Steps

Step 1 – prepare the 3d Model

Create an instrument that is just the box, face and controls. this could look like this.

By convention the 2d quad for canvas must be named CanvasInstrumentFace, so the altimeter would be CanvasAltimeterFace.

The XML file that goes along with this will only have the usual lighting animations.

Step 2 – Create an SVG file

Create an SVG file. Ensure that the elements that are to be animated are in identifiable SVG elements (groups, layers) as these will need to be located within the SVG file once loaded.

This could look something like this:

Step 3 – add the Nasal classes

Modify the aircraft -set file to include canvas_instrument.nas and if you’re using this example also canvas_altimeter.nas

I put mine in the aircraft object like this:

        <aircraft>
            <file>Aircraft/F-15/Nasal/canvas_instrument.nas</file>
            <file>Aircraft/F-15/Nasal/canvas_altimeter.nas</file>

Step 4 – implement your instrument animations

The canvas_altimeter.nas demonstrates how to create an instrument. Basically most of the work is performed in the base class (CanvasInstrument), and all that is needed is a few lines to determine the specifics.

CanvasInstrument constructor

CanvasInstrument.new has the following parameters:

  1. SVG filepath
  2. Instrument Name
  3. x translation
  4. y translation.
#
#
# Subclass the canvas instrument to create an altimeter. This is how all instruments should be done.
# The update_items property is required to handle the update of items that are animated, using
# the PropertyUpdateManager class to manage this (to optimise performance)
var CanvasAltimeter =
{
	new : func 
    {
	var obj = CanvasInstrument.new("Models/Cockpit/Instruments/ALT.svg", "Altimeter", 0,20);
        obj.hundreds = obj.get_element("100");
        obj.thousands = obj.get_element("1000");
        obj.tenthousands = obj.get_element("10000");
        obj.hundreds.setCenter(256,280);
        obj.thousands.setCenter(256,280);
        obj.tenthousands.setCenter(256,280);
#        obj.canvas.setColorBackground(0.36, 1, 0.3, 0.00);
        obj.update_items = [
            PropertyUpdateManager.new("instrumentation/altimeter/indicated-altitude-ft", 1.0, func(alt_feet)
                                      {
                                          obj.hundreds.setRotation(alt_feet*0.36*0.01744591);
                                          obj.thousands.setRotation(alt_feet*0.036*0.01744591);
                                          obj.tenthousands.setRotation(alt_feet*0.0036*0.01744591);
                                      })
        ];
	},
};
aircraft.alt = CanvasAltimeter.new();

NOTE: we are using the PropertyUpdateManager in this class to ensure best performance.

Each element that needs animation should have its own PropertyUpdateManager added to the update_items.

PropertyUpdateManager

This is a simple class that will call the code that it is given only when the property changes by the defined amount.

It takes the following parameters:

  1. Property name (string)
  2. amount the property must change by to cause an update
  3. func(v) that will do the update. the current value is passed into the func.

So to add the barometric pressure drum animation we would add a second element to the update_items, example as below.

       obj.update_items = [
            PropertyUpdateManager.new("instrumentation/altimeter/indicated-altitude-ft", 1.0, func(alt_feet)
                                      {
                                          obj.hundreds.setRotation(alt_feet*0.36*0.01744591);
                                          obj.thousands.setRotation(alt_feet*0.036*0.01744591);
                                          obj.tenthousands.setRotation(alt_feet*0.0036*0.01744591);
                                      }),
            PropertyUpdateManager.new("instrumentation/backup-altimeter/setting-inhg", 0.01, func(alt_feet)
                                      {
                                          obj.baro_disc.setRotation(alt_feet*0.36*0.01744591);
                                      })
        ];