Howto:3D Aircraft Models
This howto explains how to add 3D aircraft models to FlightGear, and how to animate and position those models. No C++ programming is required, but the user will need some knowledge of FlightGear's property system and XML markup, and will need to understand the coordinate system FlightGear uses for its models:
- distances are in metres
- angles are in degrees
- the x-axis runs lengthwise, towards the back
- the y-axis runs sideways, towards the right
- the z-axis runs upwards
- heading is a rotation around the z-axis, where positive is clockwise viewed from above
- pitch is a rotation around the y-axis, where positive is clockwise viewed from the left
- roll is a rotation around the x-axis, where positive is clockwise viewed from behind
Loading the model
The property /sim/model/path in the main FlightGear property tree controls what model will be loaded; it takes a string value giving the relative path of the model from $FG ROOT.
The easiest way to load a new model is to set the property at startup with the --prop: command line option; for example, to use a 3D aircraft model that you have installed in $FG_ROOT/Models/my-cessna.ac, you could invoke FlightGear like this (under Unix-like systems):
(Note: Normally all textures used by the model must appear in the same directory. If my-cessna.ac uses the textures cessna01.rgb and cessna02.rgb, you should also install those textures in $FG_ROOT/Models/. It is howerever possible to specify a path (relative to the model path) to specify where the textures could be found.)
When you want to set a 3D model permanently as the default for an aircraft rather than specifying it on the command line, you need to edit an aircraft settings file. In the $FG_ROOT/Aircraft/ directory there is a series of files ending in -set.xml, such as c172-set.xml, dc3-yasim-set.xml, and beech99-uiuc-set.xml. When you start FlightGear with the --aircraft option, it reads the properties from one of these files; for example
Loads the properties from $FG_ROOT/Aircraft/dc3-yasim-set.xml into the main FlightGear property tree. These files are in the same XML property-list format as $FG_ROOT/preferences.xml and the FlightGear save files. There may be many XML files with different startup conditions, sounds, panels, 3D models, etc. for any single aircraft type, so you are best off copying an existing one, renaming it, then changing the value inside the path element inside model inside sim:
<PropertyList> <sim> <model> <path>Models/my-cessna.ac</path> <texture-path>./Textures</texture-path> </model> </sim> </PropertyList>
So far, all of the examples have had the /sim/model/path property point directly at the 3D model file (Models/my-cessna.ac); however, if you want to be able to reposition or animate the model, you need to point to an intermediate XML file instead, and then put the repositioning and animation information into the XML file. Here's a simple example of a 3D-model wrapper file, with no repositioning or animation information:
<PropertyList> <path>my-cessna.ac</path> <texture-path>./Textures</texture-path> </PropertyList>
Like the -set.xml files, this file is in XML property list format, but the properties in it are not added to the main FlightGear property tree; they're used only while loading the model. The following sections will explain how to add repositioning and animation information to the file; for now, the only property to worry about is path: it provides the relative path to the actual 3D file from the XML wrapper file (not from FG_ROOT!). Usually, you should put the wrapper file in the same directory as the 3D file, and then have /sim/model/path point to the wrapper file, either on the command line
or in the -set.xml file:
<PropertyList> <sim> <model> <path>Models/my-cessna.xml</path> </model> </sim> </PropertyList>
Repositioning the Model
Often, an aircraft model not designed specifically for FlightGear will not be positioned or oriented correctly; for example, it might be too far off the ground, and the nose might point to the side or even straight up.
Inside the XML wrapper file (not the main FlightGear property tree), there are six properties that allow you to tweak the default position and orientation of the model:
The distance to reposition the model along the x-axis.
The distance to reposition the model along the y-axis.
The distance to reposition the model along the z-axis.
The angle by which to rotate the model around the z-axis.
The angle by which to rotate the model around the x-axis.
The angle by which to rotate the model around the y-axis.
For example, if you wanted to use the 3D model my-cessna.ac but found that the nose was pointing to the right instead of straight-ahead and the wheels were 1.5 metres off the ground, you could reorient it in the XML wrapper file like this:
<PropertyList> <path>my-cessna.ac</path> <offsets> <pitch-deg>0</pitch-deg> <heading-deg>270</heading-deg> <roll-deg>0</roll-deg> <x-m>0</x-m> <y-m>0</y-m> <z-m>-1.5</z-m> </offsets> </PropertyList>
It usually takes a bit of experimentation to get the model positioned correctly. Also note that if your values are zero the offsets do not have to be included. All are demonstrated above to show the format.
Animating the Model
See Howto: Animate models for the main article about this subject.
Now for the interesting part. FlightGear allows you to animate models by having parts rotate or spin in response to property changes: for example, the propellers can spin when the engine is on and the elevators can move up and down with your controller. There is no fixed limit on what parts can be animated: the only requirements are that the part is named in the 3D model file, and that there is a property in the main tree that you can use to get the positioning information. When animating your model, it is very helpful to find an aircraft with parts similar to yours and use it as an example. Cut and paste the code into your wrapper file and then edit to suit.
Currently, there are four types of animation recognized:
* none * spin * rotate * translate
These are sufficient for animating the main control surfaces of many standard-configuration aircraft. none is a no-op; spin rotates the object around an axis with a known rotational velocity (not worrying about the exact position), rotate rotates the object around an axis to an exact position and translate moves an object along an axis.
Every animation appears inside an animation element, and contains a type property and at least one object-name property:
<animation> <type>rotation</type> <object-name>Rudder</object-name> </animation>
The object name must match exactly the object name used in the 3D file (including case). You may include more than one object name to apply the same transformation to more than one object, assuming that they rotate around exactly the same line:
<animation> <type>rotation</type> <object-name>LeftElevator</object-name> <object-name>RightElevator</object-name> </animation>
Each animation must be associated with exactly one property from the main FlightGear property tree (remember that the properties in the wrapper file are not part of the main tree), using property to provide the property name:
<animation> <type>rotation</type> <object-name>Rudder</object-name> <property>controls/rudder</property> </animation>
Note the omission of the leading slash '/' when reffering to the property. This assures that when the model is used for AI traffic the animations will follow that of the AI controller instead of that of the user.
This example rotates the rudder according to the value of the controls/rudder property (or at least, it will when we specify the center and axis of rotation below); however, controls/rudder is normalized from -1.0 to 1.0, and we probably want to rotate the rudder more than that; as a result, we need to use the factor property to do scaling. For example, if the rudder on the actual aircraft rotates 18 degrees in each direction, we would use a factor of 18 to scale the rudder position from -18 degrees to 18 degrees:
<animation> <type>rotation</type> <object-name>Rudder</object-name> <property>controls/rudder</property> <factor>18</factor> </animation>
If you wanted to reverse the direction of rudder movement, you would use a factor of -18.
There is also an offset property that can be useful for starting the rotation from a point other than center. For example, let's say that you want the rudder to start 1% to the left rather than dead center; you could specify that like this:
<animation> <type>rotation</type> <object-name>Rudder</object-name> <property>controls/rudder</property> <offset>-0.01</offset> <factor>18</factor> </animation>
The offset is applied before the factor.
Finally, there are min and max properties that can constrain the amount of rotation in degrees, as in this (very complicated) example for the Cessna 310 landing gear:
<animation> <type>rotate</type> <object-name>NoseWheel</object-name> <property>gear/gear/position-norm</property> <factor>120</factor> <offset>-1</offset> <min>-90</min> <max>0</max> <center> <x-m>-2.28</x-m> <y-m>0.0</y-m> <z-m>-0.65</z-m> </center> <axis> <x>0</x> <y>1</y> <z>0</z> </axis> </animation>
In this example, the gear position (from 0.0 for fully retracted to 1.0 for fully extended) is multiplied by a factor of 120 and an offset of -1, then clamped to between -90 and 0. In the 3D model, the gear is extended by default, so we end up with the following rotations through the gear's range of movement:
The gear does not move at all during the first 1/4 of the position-norm value, giving the doors a chance to open and close in a separate animation. Obviously, this would be easier to manage with an interpolation table (see farther below).
For a spin animation, the property provides a value in revolutions per minute (RPM) rather than an absolute position in degrees, and offset is not used. You can still use factor to scale the property value if it is not in RPM.
Now, it is necessary to specify the axis of rotation for the object, its virtual hinge. This is often the hardest part, requiring a lot of trial-and-error when the axis of rotation is not lined up with the x-, y-, or z- axis (think of ailerons on a swept wing with a non-zero dihedral angle). You need to provide two groups of information: a point through which the axis of rotation passes, and the direction in which the axis is moving.
For the point through which the axis passes, you use the /center/x-m, /center/y-m, and /center/z-m properties to specify a position in metres, using the aircraft's coordinate system. Note that this is the system before repositioning: if the original model was pointing sideways, then your fuselage will run along the y-axis rather than the x-axis. Here is an example for a rudder:
<animation> <type>rotate</type> <object-name>Rudder</object-name> <property>controls/rudder</property> <factor>18</factor> <center> <x-m>5.45</x-m> <y-m>0.0</y-m> <z-m>0.0</z-m> </center> </animation>
In this example, the point is right on the y and z axes, but 5.45 metres along the x axis (i.e. towards the back of the plane).
Finally, in addition to the center point, it's necessary to specify the direction of the axis that passes through it, using the properties /axis/x, /axis/y, and axis/z. These are unitless values showing the rate of change in each direction; for example, a straight up-and-down rotational axis could be specified like this:
<axis> <x>0</x> <y>0</y> <z>1</z> </axis>
or like this:
<axis> <x>0</x> <y>0</y> <z>1000000</z> </axis>
Since there's 0 movement along the other two axes, it doesn't matter. On the other hand, let's say that the rudder hinge sloped back 5% because of a swept tail. In that case, the x-axis would have to show some movement as well:
<axis> <x>0.05</x> <y>0</y> <z>1</z> </axis>
That could just as easily be written as
<axis> <x>1</x> <y>0</y> <z>20</z> </axis>
as long as the ratios are the same. For a complicated rotation axis, like that for an aileron on a swept-back wing with a significant dihedral angle, you'll have to specify movement along all three axes:
<axis> <x>0.15</x> <y>1.00</y> <z>0.01</z> </axis>
Here's a complete example, showing the animation for the rudder on the Cessna 172:
<animation> <type>rotate</type> <object-name>Rudder</object-name> <property>controls/rudder</property> <factor>18</factor> <center> <x-m>5.45</x-m> <y-m>0.0</y-m> <z-m>0.0</z-m> </center> <axis> <x>0.72</x> <y>0.0</y> <z>1.0</z> </axis> </animation>
And here's an example of a spin, using the Cessna 172 propeller:
<animation> <type>spin</type> <object-name>Propeller</object-name> <property>engines/engine/rpm</property> <center> <x-m>0</x-m> <y-m>0</y-m> <z-m>-.25</z-m> </center> <axis> <x>1.0</x> <y>0.0</y> <z>0.0</z> </axis> </animation>
You can get a lot of your initial measurements and name or rename objects by viewing the model in a 3D editor like Blender or AC3D, so they can be animated in FlightGear. In the end, you'll most likely have to do a little tweaking by trial and error until everything looks right.
There is also a second method to do this animation you may find easier.
<axis> <x1-m> 8.02</x1-m> <y1-m> 14.38</y1-m> <z1-m> -0.34</z1-m> <x2-m> 11.85</x2-m> <y2-m> 20.58</y2-m> <z2-m> 0.36</z2-m> </axis>
Using this method you select the two ends of the axis where x1, y1 and z1 are the locations of one end of the object, while x2, y2 and z2 are for the other end. So for a rudder, you would use the upper point and lower point where the rudder is connected to the vertical stabilizer. Then the object will rotate between those two points. The center tags can and should be removed, as they are no longer needed. Rather than using the center of the object and then picking the axis it rotates around, you instead define the two ends of the axis.
Here's a complete example, showing the animation for the rudder on the Airbus A320. Also note that interpolation is used to replace the factor and min/max properties:
<animation> <type>rotate</type> <object-name>Rudder.L</object-name> <object-name>Rudder.R</object-name> <property>surface-positions/rudder-pos-norm</property> <interpolation> <entry><ind>-1.0</ind><dep>-25.0</dep></entry> <entry><ind>0.0</ind><dep>0.0</dep></entry> <entry><ind>1.0</ind><dep>25.0</dep></entry> </interpolation> <axis> <x1-m> 14.8887</x1-m> <y1-m> 0</y1-m> <z1-m> -0.306402</z1-m> <x2-m> 17.3891</x2-m> <y2-m> 0</y2-m> <z2-m> 5.5629</z2-m> </axis> </animation>
In the above example interpolation is used to tell the rudder how much to move in either direction. Interpolation can also be used to set up the timing for animations.
In this example which is for the right main landing gear door of the A320 the door is normally closed. If the gear is to be raised the door will pop open wait for the gear to rotate into the bay and then the door will shut.
<animation> <type>rotate</type> <object-name>MainGearDr2</object-name> <property>/gear/gear/position-norm</property> <interpolation> <entry><ind>0.0</ind><dep>0.0</dep></entry> <entry><ind>0.2</ind><dep>84.0</dep></entry> <entry><ind>0.8</ind><dep>84.0</dep></entry> <entry><ind>1.0</ind><dep>0.0</dep></entry> </interpolation> <axis> <x1-m>-2.1418</x1-m> <y1-m>-0.398918</y1-m> <z1-m>-4.6747</z1-m> <x2-m>-0.469801</x2-m> <y2-m>-0.398918</y2-m> <z2-m>-4.67559</z2-m> </axis> </animation>
This document will likely be out of date by the time you read it. Look at the actual XML wrapper files (currently in $FG_ROOT/Aircraft/aircraft-type/Models/) to look at how FlightGear is doing things now. In the future, we'll be adding other animation types, including selecting among different versions of the same object (such as a translucent propeller disk for high RPM), non-rotational transformations, scaling, and conditionals (i.e. draw engine exhaust only above a certain velocity).
David Megginson, 11 March 2002, edited for currency, Jon Bourgeois, 16 Sep 2009