topic: Plugin SDK

Writing Plugins in Languages Other Than C

As of X-Plane 11.30, the X-Plane SDK is only ships with official support for Delphi and C language bindings.

However, there are a number of unofficial, third-party “wrappers” for writing plugins in other language.

Note that we make every effort to keep this page up to date, but we can’t make any guarantees. (You can send to additions & corrections to Tyler at X-Plane.com, though.)

 

Leave a comment

Three Plane-Maker Tricks You Should Know

Here are three obscure Plane-Maker/X-Plane features that can save you time if you develop complex aircraft.

Plane-Maker Will Copy Your Instruments

You may know that in Plane-Maker, you make your own copies of X-Plane’s PNG files to customize the look of the instruments.  But did you know that Plane-Maker will copy the images for you?

Select an instrument and type Control-P (which is the default for the command binding “pln/panel/copy_instrument_png”).  Plane-Maker will copy all PNGs for that instrument into your aircraft’s cockpit or cockpit_3d folder.  This can save you the time spent wading through X-Plane’s cockpit folder to find the right PNG files.

X-Plane Can Make a Panel Image for UV-Mapping

When you are making a 3-d cockpit, you use the 3-d panel as a texture.  But how do you know how to UV-map this texture in your cockpit?  Often the panel background (panel.png) is blank.

X-Plane can make a snapshot of your panel for you, in the exact size you need to UV map.  Use Control-Alt-Shift-Space (Control-Option-Shift-Space for Mac) to run the “sim/operation/make_panel_previews” command in X-Plane.  It will make a PNG file in your aircraft’s Cockpit/-PANELS- folder called Panel_preview.png – it’s just like Panel.png but with the instruments drawn in – perfect for UV mapping.

Plane-Maker Will Tell You What’s Wrong

That sad face icon in the top bar of the Plane-Maker panel editor enables “warning” mode.  In warning mode, every instrument that has a potential problem gets a red outline.  Select one instrument with a red outline and in the upper left corner of the panel you’ll see a description of what’s wrong.

This picture on the left is from Plane-Maker editing a 3-d panel. (That’s why it is just a “packed” set of instruments with no background; this panel is used as a texture for a 3-d cockpit – each instrument is UV-mapped into the right location.)

The air-speed indicator has been flagged as having a problem, and the text shows it.  In this case, the lit texture has an alpha channel, which causes the lit texture to draw incorrectly.  Fix the texture and the warning will go away.

I strongly recommend checking all Plane-Maker “red boxes” on your plane – most of the warnings will tell you about problems that would otherwise be very hard to detect.

Leave a comment

OpenAL

This tech note explains how to use OpenAL in an X-Plane Plugin.

An Overview of OpenAL Issues

OpenAL is a multichannel audio API targeted at games, modeled after the OpenGL API. OpenAL can be hardware accelerated, although often the user’s implementation will be software. OpenAL makes it relatively easy to:

  • Play multiple sounds at once.
  • Control the pitch and volume of those sounds.
  • Loop or concatenate sounds.
  • Manipulate the sound in 3-d; OpenAL will calculate stereo and attenuation effects.

OpenAL also supports extensions; some implementations may have support for filters and effects.

For the purpose of understanding how OpenAL interacts with X-Plane and the plugin system, OpenAL supports the following opaque objects:

  • A “device” is a connection to a renderer (and possibly hardware) that will play audio.
  • A “context” provides a container for all OpenAL sounds (and other objects), associated with one device. In theory, a device may have more than one context. (This is discussed below.) A context is similar to an OpenGL context.
  • Each context has one “listener” object that represents where the listener is positioned in 3-d space.
  • Each context has one or more “source” objects that represent audio being created in 3-d space.
  • The context has some number of buffer objects that contain actual audio data. (One or more buffers are played through a source.)

OpenAL use by X-Plane

X-Plane’s use of OpenAL varies by version.

  • X-Plane 9 uses OpenAL for sound production on Linux and OS X.
  • X-Plane 10 will use OpenAL on all three platforms.

OpenAL will never be set up on a platform not supported (e.g. Windows + X-Plane 9).

If X-Plane does use OpenAL for sound on a given platform, there is still no guarantee that OpenAL will be present; X-Plane is capable of running even if the sound system is unavailable. OpenAL may be unavailable due to a local machine configuration problem, or the user intentionally disabling it with the –no_sound configuration.

While there is no guarantee of OpenAL on the platforms where X-Plane uses it, it is reasonable to skip sound code if X-Plane’s sound is not functioning; as this happens when there is a configuration problem or user preference for no sound.

When X-Plane does use OpenAL, it will:

  • Open one device and create one context.
  • Create several sources and buffers for sound playback in that context.
  • Manage those sources.

X-Plane will leave the listener at 0,0,0 and not alter global sound settings.

Using OpenAL via a Private Context

One way to use OpenAL is by creating your own context. You would:

  • Create your own device and context.
  • Set your context to current any time you must make OpenAL calls.
  • Reset the old context when done.

There are some advantages of this technique:

  • Since the context is yours, you can do anything to it, no restrictions. You can manipulate the listener, change global context properties, or add context-wide effects without affecting anything else.
  • Since you create the context, you can select a specific device.

There is one major limitation:

  • Some implementations of OpenAL on Windows do not support multiple contexts, so this technique may fail if X-Plane uses OpenAL (X-Plane 10) or any other plugin uses OpenAL.

This technique is generally safe to use on Mac and Linux, and has been the recommended technique for using OpenAL.

For an example, see: OpenAL Example

Using OpenAL via Context Sharing

You can also use OpenAL by sharing X-Plane. or any other plugin’s context. This technique has a few advantages:

  • On platforms where X-Plane uses OpenAL (X-Plane 9 Mac/Lin, X-Plane 10) you can ignore context setup entirely and simply use X-Plane’s context.
  • This technique will work even if the OpenAL implementation only one context.

There are some disadvantages:

  • You can’t manipulate context globals or the listener; all 3-d sound must be done with source-relative positioning.
  • If the context has resource limits, you share these with X-Plane and all other plugins.
  • If X-Plane has opened the hardware, it may not have chosen the OpenAL device you want.

When sharing a context, there are two approaches to context sharing:

  1. Use the existing context if it exists.
  2. Create a context if none exists.

For an example of this second technique, see: OpenAL Shared Example.

The general context sharing rules are:

  • If the current context is null, there is no context to recycle.
  • If there is a current non-null context, you may use it, but do not change globals.
  • If you make a context and do not want anyone else to use it (e.g. you will use a custom listener position), always restore the current context to NULL.
  • If you want to share your context, leave it current.
  • Do not ever change the context from not-null to another context without putting it back. (That is, if someone else made a sharable context, it is too late for you to do so.)

OpenAL on Windows, X-Plane 10

On Windows, X-Plane does not require that OpenAL be installed system-wide, nor does it use the system-wide OpenAL installation. Instead, X-Plane ships with OpenAL-Soft and uses that implementation exclusively.

Starting with X-Plane 10.20, X-Plane will load OpenAL-soft before loading _any_ plugins. This means that, starting on X-Plane 10.20, OpenAL is always available on Windows, and will always have a context to share, even for plugins that are installed globally.

For X-Plane 10.00 – 10.11, globally installed plugins load before X-Plane and may load a different copy of OpenAL than the system one. This has the potential to cause serious audio conflicts between X-Plane and globally installed plugins.

Our advice: if you need to use OpenAL on a globally installed plugin on Windows, target X-Plane 10.20 as your base version once it goes final.

If you must use OpenAL and target older versions of X-Plane 10 with a globally installed plugin, install your preferred OpenAL DLL in your fat plugin’s folder, so that it is loaded. This ensures that you will only have to test this one configuration, and that your plugin will not be affected by what runtime the user has installed.

Comments Off on OpenAL

LuaJIT

LuaJIT 2.0 is a just-in-time compiler for the Lua scripting language; it is the preferred way to embed Lua in a plugin. This technote describes the integration issues between X-Plane 10.20 64-bit and LuaJIT. These problems are specific to 64-bit builds of X-Plane; 32-bit plugins and Windows/Linux 64-bit plugins are not affected.

LuaJIT 2.0 and Address Space

LuaJIT 2.0 requires that all allocations used by JIT code be in the bottom 2 GB of address space. (This requirement comes from the use of signed 32-bit relative addressing for constants in the generated code.) To meet this requirement, LuaJIT has two modifications from a ‘standard’ Lua run-time:

  • The built-in Lua allocator attempts to grab memory from the lower 2 GB of address space using various OS-specific techniques. The allocator is a copy of dl-malloc sitting on top of this custom bottom-2-gb scheme.
  • lua_newstate is inoperative in the 64-bit version of LuaJIT 2.0. This stops client code from providing a custom allocator that ignores the < 2 GB rule (which would cause JIT-code crashes).

LuaJIT 2.0 and X-Plane

The problem: during normal operation, X-Plane’s normal memory use may consume some or all of the free memory in the bottom 2 GB, potentially exhausting it under high scenery engine load. Plugins utilizing Lua that are loaded late (e.g. in an airplane load) or need more memory mid-run may fail allocation since this critical “bottom 2 GB” of address space is taken.

X-Plane 64-bit sometimes solves this problem by pre-grabbing the entire LuaJIT address space for itself on startup and then providing this address space to Lua-based plugins on demand. A special set of messages can be sent from plugins to X-Plane to allocate and release memory.

You will need to modify LuaJIT to allow the custom allocator API on 64-bit builds. Contact Ben if you need a pre-compiled static library that has the custom allocator API re-enabled.

You can use the dataref sim/operation/prefs/misc/has_lua_alloc (type int, read only) to determine whether the custom allocator message API is available. If it is, you _must_ use it.

Sample Code to Use X-Plane’s LuaJIT Allocator

/* Include XPLM headers, etc. */

struct lua_alloc_request_t {
			void *	ud;
			void *	ptr;
			size_t	osize;
			size_t	nsize;
};

#define		ALLOC_OPEN		0x00A110C1
#define		ALLOC_REALLOC	0x00A110C2
#define		ALLOC_CLOSE		0x00A110C3
/* new in X-Plane 10.40 */
#define		ALLOC_LOCK		0x00A110C4
#define		ALLOC_UNLOCK		0x00A110C5
#define		ALLOC_LOCK_RO		0x00A110C6

/* lua interpreter */
lua_State* l;

static void *lj_alloc_create(void)
{
	struct lua_alloc_request_t r = { 0 };
	XPLMSendMessageToPlugin(XPLM_PLUGIN_XPLANE, ALLOC_OPEN,&r);
	return r.ud;	
}

static void  lj_alloc_destroy(void *msp)
{
	struct lua_alloc_request_t r = { 0 };
	r.ud = msp;
	XPLMSendMessageToPlugin(XPLM_PLUGIN_XPLANE, ALLOC_CLOSE,&r);
}

static void *lj_alloc_f(void *msp, void *ptr, size_t osize, size_t nsize)
{
	struct lua_alloc_request_t r = { 0 };
	r.ud = msp;
	r.ptr = ptr;
	r.osize = osize;
	r.nsize = nsize;
	XPLMSendMessageToPlugin(XPLM_PLUGIN_XPLANE, ALLOC_REALLOC,&r);
	return r.ptr;
}

void * ud;
lua_State * l;

/* when you need to init LUA... */

  XPLMDataRef lua_alloc_ref = XPLMFindDataRef("sim/operation/prefs/misc/has_lua_alloc");
  if(lua_alloc_ref && XPLMGetDatai(lua_alloc_ref))
  {
    /* X-Plane has an allocator for us - we _must_ use it. */
    ud = lj_alloc_create();
    if (ud == NULL)
    {
       /* handle error */
    }
    l = lua_newstate(lj_alloc_f, ud);
  }
  else
  {
    /* use the default allocator. */
    l = luaL_newstate();
  }

/* when you need to clean up lua */

  XPLMDataRef lua_alloc_ref = XPLMFindDataRef("sim/operation/prefs/misc/has_lua_alloc");
  if(lua_alloc_ref && XPLMGetDatai(lua_alloc_ref))
  {
    lua_close(l);
    lj_alloc_destroy(ud);
  }
  else
  {
    lua_close(l);
  }

Test Materials for LuaJIT 2.0.1 64-bit

The official version of LuaJIT does not allow custom allocators in 64-bit builds. (They did this to prevent people from accidentally allocating high memory.) To wire LuaJIT to X-Plane’s allocator (which does provide low memory as needed), a modified build of LuaJIT is needed.

Here is a link to the source for LuaJIT with the custom 64-bit allocator re-enabled:

Windows builds are compiled with MSVC2010; the runtime has been set to /MT to avoid a DLL dependency on the MSVC runtime (which is not required for X-Plane itself!) Mac builds are compiled on OS X 10.6 and Linux on Ubuntu 10.04.

To test LuaJIT allocation, get X-Plane 10.22 beta 1 or newer; 10.22 contains 64-bit LuaJIT allocation support for all three operating systems.

Viewing Lua Memory Usage

You can show you current total bytes allocated by the lua-allocation-service:

To view the allocations, use DataRefEditor – pick “view stats” and then filter for “lua”. You will see the bytes go up and then back down when garbage collection kicks in; if the bytes go up continuously, you may have a leak!

Memory Protection for Lua

Starting with X-Plane 10.40, you can request that X-Plane temporarily lock access to the memory allocated for your plugin for Lua use. Two new messages (ALLOC_LOCK = 0xA110C4 and ALLOC_UNLOCK = 0xA110C5) make the change, e.g.

void lock_lua_memory()
{
    XPLMSendMessageToPlugin(XPLM_PLUGIN_XPLANE, ALLOC_LOCK, NULL);
}

void unlock_lua_memory()
{
    XPLMSendMessageToPlugin(XPLM_PLUGIN_XPLANE, ALLOC_UNLOCK, NULL);
}

void lock_read_only_lua_memory()
{
    XPLMSendMessageToPlugin(XPLM_PLUGIN_XPLANE, ALLOC_LOCK_RO, NULL);
}

Your memory starts unlocked; when you send a lock message, the underlying VM pages allocated for use by Lua are set to not be readable or writable; any code that then accesses them will crash x-plane at the point of access.

The purpose of this lock is to detect memory corruption immediately; while the lock is on, code that is corrupting lua memory will crash with a back-trace pointing to the offending code.

Note that the lock:

  • Must be off for you to run any lua API calls, including executing Lua code, reading from the interpreter, or creating/destroying Lua contexts.
  • Locks/unlocks only your plugins memory.
Comments Off on LuaJIT

Moving the Plane

Positioning Aircraft in X-Plane

This note describes how to position the user’s aircraft or multiplayer aircraft in X-Plane. Positioning the user’s aircraft can be broadly divided into two cases:

  1. Positioning the user’s aircraft once while the physics engine is running. Example: positioning the user for an ILS approach in a training situation.
  2. Positioning the user’s aircraft continuously while the physics engine is not running. Example: visualizing flight data recorder information.

Generally most datarefs for positioning the user’s aircraft are contained in sim/flightmodel/position/.

A Note on Precision

All datarefs in this section are listed on the web page as either type int, float, or double. When a dataref is listed as float, it is internally stored in 32-bit floating point format. When a dataref is listed as double on the web page, it is internally stored in 64-bit double precision floating point format, but is accessible as both a float and a double for convenience. You may use either the double or float routines to write the dataref, depending on the precision you need.

Generally you will need to use double precision to work with latitude and longitude precisely. Because the local coordinate system moves to keep the aircraft near the origin, single precision should be adequate for cartesian XYZ values. The routines XPLMWorldToLocal and XPLMLocalToWorld work in double precision on the latitude-longitude side.

Positioning the User’s Aircraft While the Physics Engine is Running

While the physics engine is running, you may position the user’s aircraft; it will instantly move and then continue in the direction it was flying (or the direction you specify). Do not continuously reposition the user’s aircraft with the physics engine running; your positioning commands and the physics engine will thrash the aircraft around.

Positioning the Aircraft in Space

To place the user’s aircraft, you position it in local coordinates (see ScreenCoordinates). This is a cartesian axis in meters. Use these datarefs:

sim/flightmodel/position/local_x
sim/flightmodel/position/local_y
sim/flightmodel/position/local_z

You can read or write these datarefs at any time. You cannot write these datarefs:

sim/flightmodel/position/latitude
sim/flightmodel/position/longitude
sim/flightmodel/position/elevation
sim/flightmodel/position/y_agl

X-Plane will calculate the latitude/longitude and elevations (AGL and MSL) for you from the local cartesian coordinates. If you need to place the aircraft based on latitude/longitude information, use the function XPLMWorldToLocal to convert coordinates.

Example: add 10 from sim/flightmodel/position/local_y; the aircraft hops 10 meters up in the air and then continues.

Orienting the Aircraft in Space

The aircraft’s orientation is described by three rotations, “psi” (heading), “theta” (pitch), and “phi” (roll). However when the physics model is on, things are a bit more complicated. The datarefs

sim/flightmodel/position/psi
sim/flightmodel/position/theta
sim/flightmodel/position/phi

are read by the graphics engine to draw the aircraft. They are written by the physics model. The data ref

sim/flightmodel/position/q

is read and written by the physics model and is the master copy of the aircraft’s orientation when the physics model is in. Every frame, the physics model updates q and then copies the values to psi, theta and phi. (Warning: datarefs are case sensitive; lower case q here is the quaternion aircraft rotation, while Q is a rotational rate of the aircraft.)

The dataref q here is a quaternion, stored as an array of four floats. Quaternions are mathematical constructs that (among other things) can fully describe an arbitrary rotation in 3-dimensional space. If you want to reorient the aircraft, you must write to q. Like local_x, local_y, and local_z, this will affect the sim immediately and the sim will continue from that position.

The quaternion is ordered {1, i, j, k}. The axes correspond to:

Quaternion Euler OpenGL
i roll -z
j pitch x
k heading -y

The math to convert from Euler to quaternion is:

psi' = pi / 360 * psi
theta' = pi / 360 * theta
phi' = pi / 360 * phi
q[0] =  cos(psi') * cos(theta') * cos(phi') + sin(psi') * sin(theta') * sin(phi')
q[1] =  cos(psi') * cos(theta') * sin(phi') - sin(psi') * sin(theta') * cos(phi')
q[2] =  cos(psi') * sin(theta') * cos(phi') + sin(psi') * cos(theta') * sin(phi')
q[3] = -cos(psi') * sin(theta') * sin(phi') + sin(psi') * cos(theta') * cos(phi')

The following datarefs cannot be written:

sim/flightmodel/position/alpha
sim/flightmodel/position/beta
sim/flightmodel/position/hpath
sim/flightmodel/position/vpath
sim/flightmodel/position/magpsi

They will be calculated based on the orientation you set, the aircraft’s velocity, and the local wind conditions and magnetic variation.

Example: write {0.8, 0, 0, 0.6} to sim/flightmodel/position/q. The aircraft points roughly to the east-northeast.

Changing the Aircraft’s Velocity

While the sim is running, you can change the aircraft’s velocity, both linearly and rotationally. The linear velocity of the aircraft is controlled by three values:

sim/flightmodel/position/local_vx
sim/flightmodel/position/local_vy
sim/flightmodel/position/local_vz

This determines both the aircraft’s direction (in 3-d space) and its speed. Units are meters per second. This vector is in the world coordinate system; a velocity along the X axis moves the aircraft east no matter which way the aircraft is heading. The datarefs

sim/flightmodel/position/groundspeed
sim/flightmodel/position/indicated_airspeed
sim/flightmodel/position/indicated_airspeed2
sim/flightmodel/position/true_airspeed
sim/flightmodel/position/vh_ind
sim/flightmodel/position/vh_ind_fpm
sim/flightmodel/position/vh_ind_fpm2

are all derived from this one velocity vector. Please note that the values alpha, beta, hpath and vpath are also affected by this velocity vector; if the velocity vector points along the X axis but the aircraft is oriented along the Z axis, then the aircraft will be in a very heavy state of yaw.

Example: write 20 into sim/flightmodel/position/local_vy. The aircraft is launched up in the air.

You can also spin the aircraft by writing to the datarefs P, Q, and R, which are the rotation rates of the aircraft in degrees per second. Positive P rolls the aircraft to the right; Q pitches the aircraft up; positive R yaws the aircraft to the right.

sim/flightmodel/position/P
sim/flightmodel/position/Q
sim/flightmodel/position/R

You cannot write to the angular momentum datarefs N, M, and L; these are written by the flight model every frame.

You also cannot write to P_dot, Q_dot, or R_dot (angular accelerations) or the linear accelerations (local_ax, local_ay, and local_az).

Example: write a value of 20 to sim/flightmodel/position/Q. The aircraft rotates upward like Dr. Dre’s car.

WARNING: The interaction of P, Q, and R vs. Prad, Qrad and Rrad is pretty sim specific. I do not recommend attempting to change the rotation rate of the aircraft.

Applying Arbitrary Forces to the Aircraft (10.30 and later)

The datarefs mentioned here are only available in X-Plane 10.30 and later.

X-Plane 10.30 allows you to add arbitrary forces to the aircraft; the sum of all forces from X-Plane and plugins are considered in total to determine the behavior of the aircraft. (For example, if you apply a force pushing your aircraft to the right while it is on the ground, it may actually roll to the right, even if you push on the CG, because the tires will ‘stick’ and tip the airplane over.)

The datarefs for plugin forces are meant to be additive and shared between plugins:

  • Every frame, X-Plane will reset these datarefs to zero.
  • In your flight-loop callback, _add_ the amount of force you want to apply by reading the datarefs, adding, and writing the results back.
  • Because the results are additive, you can easily apply multiple distinct forces to the plane.
  • Because the results are additive, multiple plugins can add force at the same time. For example, an ACF plugin can implement a custom engine by adding force while a push-back plugin pushes back on the airplane.

The coordinate system for adding force is the aircraft coordinate system – location is in meters with 0,0,0 at the normal CG of the aircraft; if the CG has been customized by the user, you do not need to change your physics – X-Plane accounts for this automatically after considering plugin force.

There are three force datarefs:

sim/flightmodel/forces/fside_plug_acf
sim/flightmodel/forces/fnrml_plug_acf
sim/flightmodel/forces/faxil_plug_acf

The units are Newtons – fside is the X xis, fnrml is the Y axis, and faxil is the Z axis. Note that the positive Z axis points to the back of the plane, so most engines apply NEGATIVE force to faxil_plug_acf.

There are also 3 rotational moment datarefs – the units are newton-meters:

sim/flightmodel/forces/L_plug_acf
sim/flightmodel/forces/M_plug_acf
sim/flightmodel/forces/N_plug_acf

L is a roll moment (positive is right roll), M is pitch (positive is nose up) and N is yaw (positive = yaw right/clockwise from above the ACF. Because you can apply moments, you can apply forces to arbitrary locations on the aircraft. Given a force vector Fx, Fy, Fz applied at a location X,Y,Z, the additions to these six datarefs are:

fside+=Fx;L+= Fx*Y - Fy*X; 
fnrml+=Fy;M+= Fz*Y - Fy*Z;
faxil+=Fz;N+= Fz*X - Fx*Z;

This makes some sense intuitively: L (right-roll) increases when you apply a right force above the aircraft (Fx * Y) and decreases when you apply an up force to the right side of the aircraft (Fy * X).

Positioning the User’s Aircraft While the Physics Engine is Not Running

The dataref

sim/operation/override/override_planepath

Controls whether X-Plane’s physics engine is controlling the user’s aircraft. It contains one element for each aircraft. You can set the first element to one to disable the physics model.

WARNING: Do not use this dataref to disable X-Plane’s control of the AI aircraft; use the XPLMMultiplayer API XPLMDisableAIForPlane described below instead. Future SDKs may not support more than one element in sim/operation/override/override_planepath.

Once you disable the physics model, the following things change:

  • The quaternion rotation is no longer used or copied to psi, theta and phi. Instead, change the aircraft’s orientation directly by writing to psi, theta, and phi.
  • The aircraft will basically hold any position and orientation you set.
  • Derived variables like indicated_airspeed and alpha will not be updated.

Once you reenable the physics model, the aircraft will continue based on ‘q’, the quaternion rotation, not the orientation you wrote with psi, theta and phi.

Example: write 1 to sim/operation/override/override_planepath[0]. Then write 40 to sim/flightmodel/position/theta and 100 to sim/flightmodel/position/indicated_airspeed. The aircraft will pitch up 40 degrees and indicate 100 knots, but the stall horn will not go off. Then write 40 to sim/flightmodel/position/alpha. The stall horn will sound.

WARNING: The interaction with the instrument system when physics are disabled is a bit complex and may be subject to change.

Transitioning From Disabled to Enabled Flight Model

When you re-enable the flight model, the sim will continue based on the velocity vector and quaternion q. So if you have set up the aircraft based on phi, psi, and theta for orientation, and some kind of speed, you will need to set the velocity vector to give the aircraft speed along its current heading, and conform the quaternion q to the current rotations. Once you do this, re-enabling the flight model should be relatively smooth.

Moving the Aircraft A Long Way (Around the Earth)

(This is a paste from an email to the dev list…it needs cleanup.)

Starting with X-Plane 850 beta 9, plugins now can position the plane anywhere in the world. Here are some details (all datarefs are in sim/flightmodel/position/):

  1. You do not ever have to override the flight model to move the plane. If you alter local_x, local_y and local_z the plane jumps instantly.
  2. To move the plane a small amount, simply change local_x, local_y, and local_z.
  3. To move the plane a LONG way, you must change local_x, local_y and local_z as well as latitude and longitude. Use XPLMWorldToLocal or XPLMLocalToWorld to get the “other” coordinate system (depending on whether your positioning is based on cartesian or geographic coords). This will only work in 850 beta 9 and newer.
  4. Don’t position the plane inside the earth. Enough said. 😉
  5. You do not need to, should not, and cannot write to lat_ref and lon_ref. The sim will pick this value for you as it processes scenery.
  6. The orientation of the plane is in cartesian coordinates. So if you position the plane from latitude 45N to latitude 45S, the plane will be rotated 90 degrees relative to the horizon. This is because the horizon has rotated 90 degrees and the plane has not.
    1. The coordinate shift happens after you write your datarefs. So…you’ll need to calculate the change in attitude/rotation yourself (and write the ‘q’ variable – see the SDK tech notes for more on positioning the plane) as you write xyz, anticipating the horizon angle changing.
  7. In the future it may not be necessary to write lat/lon and xyz, but it won’t be harmful. Please always write CONSISTENT results – e.g. don’t write a lat/lon that are different from xyz (based on the current XPLMWorldToLocal and XPLMLocalToWorld conversions).

Controlling the Other Aircraft

X-Plane also features up to nineteen other multiplayer aircraft. Generally they are controlled with the sim/multiplayer/position datarefs. Before controlling the aircraft, your plugin should call XPLMAcquireAircraft. This gives your plugin exclusive rights to these multiplayer aircraft. You will lose control of the aircraft when your plugin is disabled, so call XPLMAcquireAircraft whenever your plugin is enabled. If XPLMAcquireAircraft fails, it probably means another plugin is using multiplayer aircraft.

Once you have control of the aircraft, you can disable X-Plane’s control over their position by calling XPLMDisableAIForPlane. The aircraft will now hold still until you reposition it.

Controlling AI Aircraft in X-Plane 6, and 7

(This also applies to X-Plane 8.00 – 8.40)

Up through v8.40, AI aircraft were controlled by simple calculations constituting an “AI Flight Model”. This can be overridden by setting the Sim/operation/override/override_planepath dataref for the aircraft you wish to control. When this dataref is set, the aircraft will not move on its own – you will need to provide a continuous set of positions and rotations, updated once per frame. These are controlled by sim/multiplayer/position/* datarefs found here.

Controlling AI Aircraft in X-Plane 8 and 9

AI aircraft feature a full flight model for the aircraft it is currently flying. The AI controls the aircraft via autopilot inputs. Just like v8.40, setting the Sim/operation/override/override_planepath dataref will override this behavior. As such, you will need to provide the same stream of positions and rotations for that aircraft, updated once per aircraft.

In v8.60, the AI aircraft feature a full flight model and are controlled in the same manner by the AI – via autopilot inputs. However, as of v8.60 we have a new dataref available, Sim/operation/override/override_plane_ai_autopilot. Setting this dataref allows us to disable the AI control of the aircraft, while retaining the full flight model. You are then able to control the aircraft by setting the auto-pilot datarefs for that aircraft, just as the AI would do on its own. See sim/multiplayer/autopilot/ and sim/multiplayer/controls/ (see Resources/plugins/DataRefs.txt for available datarefs).

Comments Off on Moving the Plane

OpenGL State

OpenGL State and X-Plane

There are three catagories of OpenGL state in X-Plane:

  1. Disposable State.
  2. Managed State.
  3. Unmanaged State.

This note covers the differences between these kinds of state and how to handle them in your plugin.

Disposable State

Disposable state is OpenGL state that X-Plane changes explicitly every time it makes an OpenGL call, and that you should assume is undefined at all times. The disposable state in X-Plane consists of the current vertex attributes:

  • The current color (glColor4f)
  • The current texture coordinates, for every texturing unit (glTexCoord2f).
  • The current normal vector (glNormal3f).
  • Any generic attributes (e.g. for shaders).

Managed State

X-Plane tracks some state internally to avoid thrashing OpenGL. To set this state, call the appropriate XPLM calls instead of calling OpenGL directly. The managed state is:

  • Fogging – glEnable(GL_FOG)
  • The number of 2d texture units – glEnable(GL_TEXTURE_2D) per unit.
  • Lighting – glEnable(GL_LIGHTING)
  • Alpha testing – glEnable(GL_ALPHA_TEST)
  • Alpha blending – glEnable(GL_BLEND)
  • Z-Buffer reading – glEnable(GL_DEPTH_TEST)
  • Z-Buffer writing – glDepthMask(GL_TRUE)
  • The currently bound texture – glBindTexture(GL_TEXTURE_2D, …)

This state is set using XPLMSetGraphicsState and XPLMBindTexture2d. Key points:

  • Only set managed state using XPLM calls, not OpenGL calls.
  • Do not assume the value of managed state when your plugin starts.

Unmanaged State

All other OpenGL states that are not listed are unmanaged – X-Plane sets it on an ad-hoc basis, sometimes relying on previous values. Make sure you leave all of this state as you found it.

Unmanaged state with unreliable/random values (you must set these – X-Plane will just leave random stuff around):

  • All vertex pointers/vertex formats.
  • Client array enables (except for GL_VERTEX_ARRAY, which is always enabled).
  • The current texture binding.

Unmanaged state with known base values (these are things you can ignore unless you need something special):

  • The currently bound VBO for arrays and array-elements will be 0.
  • The current transform stack will apply a coordinate based on the current drawing callback.
  • Blending function will be GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA, and the blend equation will be GL_FUNC_ADD.
  • The depth test will be GL_LEQUAL
  • The alpha test will be GL_GREATER,0
  • Stenciling will be off. The current surface may or may not have a stencil or depth buffer.
  • GL_VERTEX_ARRAY will be enabled in client state.
  • The front face will be defined as GL_CW, and GL_CULL_FACE will be enabled. (This is unusual for GL apps.)
  • The GL_UNPACK_ALIGNMENT and GL_PACK_ALIGNMENT pixel transfer alignments will be set to 1.

If any of the unmanaged state values change (in their initial input to your plugin) from these published values, please report a bug.

Correct Plugin Behavior

Correct Entering of a Draw Callback

When you enter a drawing callback, do not assume any OpenGL state is as you expect, except for unmanaged state with known default values.

Exiting of a Draw Callback

When you leave a drawing callback, make sure:

  • You have only set managed state via the above XPLM routines.
  • You have restored any unmanaged state you have set.

Calling Other Code From a Draw Callback

Do not call other plugins (by sending an interplugin message, etc.) from your draw callback. If you need to do this, please contact Sandy and Ben for a design-review…such behavior would be at the far edge of the plugin system’s design limitations, and would risk OpenGL breakage in future X-Plane versions.

When you call X-Plane SDK routines, you must assume that:

  • The disposable state will be destroyed when the routine returns.
  • The managed state will have changed and needs to be explicitly reset.
  • You may need to restore unmanaged state depending on the routine. Unmanaged state will be unperturbed across the SDK call from your perspective.

In other words, calling into the XPLM is like leaving your routine (albeit temporarily).

In particular, XPLMDrawString will almost always completely change the OpenGL managed state. XPLMDrawPlane is very dependent on graphics state and is recommended for advanced use only.

As of this time, only routines in XPLMGraphics, XPLMPlanes, and the widgets lib affect OpenGL state, but to be safe, code defensively. Do all of your drawing in atomic blocks. This will not only protect you from OpenGL state breakage, but probably improve your code performance by avoiding thrash.

Pitfalls

There was a bug introduced in X-Plane 802, fixed in X-Plane 810: the state relating to VBOs had junk values that had to be reset. So, if you use any kind of client-array or VBO drawing and your users may have X-Plane 802-806, make sure to explicitly reset this stuff before drawing. (Users with these builds should probably update X-Plane.) This is a workaround, not the normal procedure for unmanaged state.

Why is it like that?

The rest of this note discusses why the SDK OpenGL state works the way it does. Simply put, the design comes from X-Plane.

The OpenGL spec has always implied that:

  • State change is expensive.
  • The drivers won’t catch and optimize “stupid” state change (binding the same texture over and over).
  • Therefore applications should optimize state change.

This second point is because: the driver would waste CPU cycles detecting duplicate state when an application might be in a position to remove duplicate state changes at compile time or when precomputing a series of calls that will be made repeatedly.

(In fact, application writers historically haven’t been very good about optimization, so modern drivers from most game-type OGL cards do some anti-state-thrash optimization anyway.)

With that in mind, X-Plane categorizes state into the three categories based on performance:

  • When state is inexpensive to change (e.g. the current color via glColor) we simply set it ever time we need it and spend no cycles optimizing performance. OpenGL is relatively cheap for certain types of immediate state, particularly the per-vertex attributes.
  • When state is very rarely changed, we use a “set it, set it back” policy, with the default state being the one used most commonly in the code. For example, since virtually all X-Plane code uses GL_FILL for the polygon drawing mode, we simply leave it that way. The rare code that needs to do a wire-frame can set it and set it back. This means no state management code (for this type of state) for most of the app.
  • When state is both expensive and changes a lot, we use some inlined wrapper functions around the state that detect and skip duplicate state change. This is the “managed” state, and the XPLM routines wrap the internal X-Plane functions, assuring that our internal tracking is not out of sync with OpenGL.

(It should be noted that these managing routines do not call glIsEnabled – we have shadow variables. Generally querying state from the GL is a no-no and should be avoided in release code.)

Coping With OpenGL Errors

OpenGL provides an asynchronous sticky error system – the GL error is set to a non-zero value when an error is generated, and cleared when checked.

X-Plane will not check the GL error in a release build, and neither should you. Our suggestion is:

  • Set up your code to check the GL error after every block of GL operations (and certainly between any GL code and a return from your plugin’s routine back to the XPLM) in debug mode only. Use a macro to remove these gl error checks in a release build.
  • Thoroughly test your debug build in a debugger to catch GL errors.
Comments Off on OpenGL State

Plugins and Objects

This tech note describes how plugins can interact with objects.

Datarefs for Animation

Plugins can control the animation of an object using datarefs. The process is relatively simple:

  • The plugin defines a new dataref using XPLMRegisterDataAccessor. The dataref type should be one of: float, int, or double for non-array types, or float-array or int-array for array types.
  • The object references that dataref by name in an ANIM_ command. Dataref items are referenced using brackers, e.g. /myplugin/animation/bridge_length[3] would access item 3 of the dataref /myplugin/adnimation/bridge_length.

When the object is drawn (which is generally only when it is on screen), your dataref will be read to determine the current animation position.

Tip: pick dataref return values where 0.0 is a good default. If your plugin is not installed (and thus the dataref is not available) the animation code will use 0.0 as its value in calculating animation positions and rotations.

Commands for Animation

Starting with X-Plane 9 and the 2.0 SDK, X-Plane’s internal command-dispatching system is part of the plugin SDK and can be used to build animation functionality. In particular, in place of a dataref name in an object animation, you can instead use a command-name, e.g.

ANIM_trans 0 0 0   0 5 0    0 1   CMND=sim/engines/engage_starter_1

This uses the current state of a command (sim/engines/engage_starter_1) to be interpretted as an integer value, 0 if off and 1 if being pressed. (In this example, we translate up 5 meters in the Y direction when the engine 1 starter command is being held down by any source.)

This functionality is part of the OBJ and panel interpreters inside X-Plane, not part of the X-Plane plugin APIs. Basically it givse authors access to command state. Plugins looking to determine the status of a command should register a handler for the command, pass the command through, and note the command state change in their callbacks. (This means plugin code will run only when a command is pressed, not per-frame.)

Plugins should avoid registering datarefs that start with CMND=, because these datarefs will not be accessable in the panel and object subsystems.

Per-Object Variance

Some built-in datarefs that provide animation parameters vary their output based on which object is using them. For example, the particular heading of the crane dataref will be different for every object that uses it. This is done to keep scenery from moving in complete lock step.

The datarefs that have this behavior use the draw_object_x/y/z datarefs to create per-object variance — see the next section to use this technique in your own datarefs.

Determining Which Object Instance is Being Drawn

There may be many instances of an object in a scenery package. You do not have to provide unique OBJ files with different dataref names for each one; you can determine to some extent whch object is being drawn when your accessor is called.

The following float-type datarefs are available to tell you the location of the object that is being drawn at the instant your dataref is called:

sim/graphics/animation/draw_object_x
sim/graphics/animation/draw_object_y
sim/graphics/animation/draw_object_z
sim/graphics/animation/draw_object_psi

The units are meters in local coordinates for XYZ and heading from true north in degrees for psi. These values let you tell the various instances apart. These datarefs are invalid outside the process of drawing an object.

Some caveats:

  • When scenery is reloaded, object XYZ will change. I recommend watching the local reference point (sim/flightmodel/position/lat_ref and sim/flightmodel/position/lon_ref) to determine when scenery has shifted, then reset state. You can attempt to determine the object’s new position by converting from XYZ to lat/lon before a shift, then converting back, but be sure to use an epsilon-check, because the values will not be perfectly equal due to rounding errors.
  • Do not write code that assumes anything about the draw order of objects! When your dataref is called, do a fast hash of the draw_object datarefs to find your object and go from there. Do not assume that objects are drawn only once (we could have multi-pass rendering) and do not second guess our culling, which may not be perfectly efficient.
  • Do not expect exact matches of lat/lon/el after a scenery shift – the math has rounding error.

The code flow would look something like this:

  1. X-Plane’s culling code decides an object needs to be drawn, given the current camera location.
  2. X-Plane temporarily sets the values of draw_object_XXXX to the location of the current object.
  3. X-Plane’s object drawing loop starts.
  4. When X-Plane’s object drawing loop hits an animation, it asks the SDK to fetch the animation value, which results in a call to a dataref published by your plugin.
  5. Now your plugin’s GetDataf callback is being called.
  6. Your plugin then calls XPLMGetDataf on the draw_object_XXXX datarefs to determinine WHICH instance of an object is being drawn.
  7. Your plugin then returns from its GetDataf callback a dataref animation value that is unique to this INSTANCE of the particular OBJ.
  8. X-Plane then animates that particular instance of an OBJ with the returned value.

The simplest form of this is to use the object location as a “seed” value for periodic or random motion, so that each object has a different seed value. (Please note that this will produce a “jump” at scenery load time – for consistent behavior across scenery shifts, consider using a quantized version of latitude and longitude.)

Object Sequence Numbers

Object Sequence Numbers are not part of the X-Plane scenery SDK yet – this section is included primarily to explain some of the behaviors of the animation datarefs.

When an OBJ is placed in X-Plane, X-Plane tracks a few variables:

  • The location of the object (in XYZ cartesian space) – this is usually generated by “dropping” the object straight down until it hits terrain.
  • The heading of the object as a rotation from true north.
  • Which OBJ file is used to draw the object.
  • The object’s sequence number.

The sequence number is an integer attached to each placement.

  • Most objects have 0 for a sequence number, that is, most of the time this feature is simply not used.
  • There is no plugin access to sequence numbers, nor can they be set using DSF files.
  • However, some animation datarefs will vary their behavior based on the sequence number.

When an apt.dat file is built into an airport in the sim, the airport code places objects from the library based on apt.dat data. (For example, given a runway’s approach lighting system type, a sequence of objects are placed to form the lighting structure.)

A number of airport lighting elements require sequenced or coordinated behavior between multiple objects — for example, the rabbit must flash in succession. To achieve this, the airport code will put a sequence number on each object that is part of the approach light structure (as an example).

The lighting datarefs then examine this sequence number and produce flashes at the right coordinated time.

What this means for plugins and modelers: sequence numbers are only available in certain contexts. What this means for authors is that if you use a light animation dataref for a purpose other than its indended one, the sequence number may not be set correctly, producing incorrect behavior. For example, if you use the rabbit light dataref for anything other than approach lights that have a rabbit, the rabbit will not flash in a coordinated manner because the objects must be placed with sequence numbers.

In the future we may provide an interface for accessing sequence numbers or instance data, but for now consider this a warning that some of the “special purpose” lights in the dataref list really are special purpose!!

(You can create the equivalent of sequence numbers by examining the object draw x/y/z datarefs to understand which object is being drawn, but your plugin would have to translate this 3-d info into some form of organization.)

Datarefs for Custom Lights

A custom light in an OBJ file can use a dataref – here’s how it works:

  • The OBJ file contains 9 parameters for the custom light and an optional dataref
  • When the object is drawn, the dataref (of type float-array) is called. The array passed in contains the defaults from the OBJ file.
  • The dataref can then CHANGE or REPLACE all, some, or none of the 9 parameters.
  • The modified values are used to draw.

Thus a dataref can:

  • Generate light values.
  • Customize light values.
  • Use the default light values as “input” parameters to configure the light.

In the case of our own datarefs, often we use the RGBA color of the light to specify its properties, and the dataref replaces the RGBA values with white and some level of alpha (to turn the light on and off).

The nine parameters are:

  1. color (red) from 0 to 1.
  2. color (green) from 0 to 1.
  3. color (blue) from 0 to 1.
  4. alpha level from 0 to 1. Use alpha 0 to hide the light.
  5. light size. This is in, um, “light size units”, that is, the size corresponds only to some internal sim code, not any real world unit. Light billboards are not sized in real-world meters – their size doesn’t change at the same rate as objects as you zoom out.
  6. texture coordinate – left, from 0 to 1.
  7. texture coordinate – bottom, from 0 to 1.
  8. texture coordinate – right, from 0 to 1.
  9. texture coordinate – top, from 0 to 1.

Thus the dataref can change the color, the texture, even the size of a light.

In a typical design that we use, we leave the texture and size coordinates unchanged (so the OBJ can decide this) and use the RGBA as 4 input parameters, calculating whether the light is on (and how bright), writing this to alpha, and setting RGB back to 1 so the light is not tinted. (The texture of the light can provide any “color” needed.)

Parameter Order for Custom Spill Lights

Custom spill lights in an OBJ, created via LIGHT_SPILL_CUSTOM, also work via a 9-float writable dataref, and again the params are in the array when your plugin is called and you can modify them. The parameter order is:

  1. color (red) from 0 to 1.
  2. color (green) from 0 to 1.
  3. color (blue) from 0 to 1.
  4. alpha level from 0 to 1. Use alpha 0 to hide the light.
  5. light size in meters. This is the radius from the center of the light to complete extinction of the lighting effect.
  6. cone radius. This is stored as the cosine half of the cone width, or use 1.0 for omni-directional lights.
  7. cone direction (X)
  8. cone direction (Y)
  9. cone direction (Z)

The last 3 numbers -must- form a -normalized- vector, that is, X*X+Y*Y+Z*Z == 1.0.

Authors Warning: X-Plane will normalize your dx,dy,dz when reading your OBJ, so you cannot pass -arbitrary- data through these params. If your OBJ8 values are meant to ‘feed’ the plugin, pass the data in RGBA.

Light Equations for Directional Lights

Some lights use a 4-part vector to specify direction in RGBA. In this case, RGB form a vector (R = X, G = Y, B = Z) and A forms a constant term that is added. Note that the vector formed by RGB does NOT have to be normalized…if it is not, the light may be overly bright or dark.

Some lights use a 3-part vector to identify direction in RGB. In this case, the behavior is the same as a above, except a constant term is picked by X-Plane to guarantee that the light’s maximum brightness is 1.0. Example: if you use the light vector (0,2,0) where length = 2, x-plane picks a constant term of -1.0. Thus the light is equivalent to the 4-part light (0,2,0,-1).

When a light uses two components (RG) then the direction is pre-determined (usually 0,0,1). The red component scales the dot product and the green component is added — in other words, this is the same as having a non-normalized RGB in the above example. Example: the 2-component light (2,-1) has the implicit direction (0,0,1), so it is the same as the 4-component light (0,0,2,-1).

In both cases, the total value is clamped to 0…1, so:

  • If the scaling parameter is large and the offset is negative, the light will be more tightly focused, and dark (below 0.0) for a while.
  • If the scaling parameter is small and the offset is positive, the light will be less tightly focused.
  • If the scaling parameter is really small or 0 and the offset is positive, a light can be omnidirectional, or have some element that is bright all the time.
Comments Off on Plugins and Objects

Screen Coordinates

This tech-note describes the various coordinate systems in X-Plane.

Coordinates Overview

X-Plane draws in 3-d and 2-d. The “world” is drawn in 3-d; this includes the world, objects in the world, other planes, and the cockpit object for 3-d cockpits. The 2-d panel and all windows are drawn in 2-d. If a plugin makes windows, they are drawn in 2-d coordinates; if you use a drawing phase, the coordinate system varies with the drawing phase.

X-Plane requires a minimum of 1024×768 to run, but can run in a larger screen. The following rules apply to how X-Plane uses the extra screen space:

  • For 3-d drawing, the field of view represents horizontal field of view; this degree of view goes from the left to right edge of your screen, at any resolution. Vertical field of view is chosen to maintain a consistent aspect ratio. (This does not meant that the ratio of FOVs is the same as the aspect ratio of pixels on the screen.)

Angles are not ratios. Imagine a screen that’s twice as high as it is wide with a horizontal FOV of 90 deg, and put yourself at the point of the viewing pyramid. Your horizontal FOV is 90 deg, but your vertical FOV is ‘only’ about 126.87 deg. X-Plane does this: float fovratio = tandeg(fov/2); viewingpyramid.horizontal = fovratio; viewingpyramid.vertical = fovratio*screen.height/screen.width; — Jonathan

  • For 2-d drawing of dialog boxes and the startup screen, the middle 1024 horizontal pixels and bottom 768 pixels are used; the rest of the area to the sides and above this area are filled with blue. This does not affect plugins since plugins cannot draw when a dialog box is up.
  • For 2-d windows, the coordinate system runs from 0,0 in the lower left, to width, height in the upper right, no matter what the screen size. You can use the datarefs sim/graphics/view/window_width and sim/graphics/view/window_height to determine the full size of the x-plane window.

The panel is a bit different; the panel is expanded as much as possible such that the entire panel is still visible horizontally, and at least 768 pixels of panel are visible vertically. This can be a bit counterintuitive…on a case-by-case basis:

  • If you run at 1024×768 or any other 4:3 aspect ratio, the panel takes the whole screen. Extra-tall panels scroll.
  • If you run at a wider resolution than 4:3 extra space is added to the side of the screen to the left and right of the panel.
  • If your run at a taller resolution thatn 4:3, extra space is added above the panel. If the panel is taller than 768 pixels tall, some of the panel that was out of view may come into view. If your aspect ratio is taller than 1:1, the panel will not scroll at all, because no panel is taller than it is wide.

In summary, the panel is zoomed to maximize space on the screen while guaranteeing that at least 1024×768 of the panel bitmap is visible, so the actual behavior is a function of aspect ratio.

3-D Coordinate System

X-Plane employs a local cartesian 3-d coordinate system for all 3-d drawing. This cartesian coordinate system is typically set up as follows:

  • The unit for all axes is meters (e.g. a 1x1x1 box will appear to be 1 meter tall relative to other scenery).
  • The origin 0,0,0 is on the surface of the earth at sea level at some “reference point”.
  • The +X axis points east from the reference point.
  • The +Z axis points south from the reference point.
  • The +Y axis points straight up away from the center of the earth at the reference point.

A few pitfalls of this coordinate system: the Y axis is not synonymous with up; this divergence increases as you go away from the reference point. Most parts of x-plane compensate for this, but there are still some shortcuts, typically for performance reasons.

True north is only the same as the negative Z axis for the point 0,0,0. As you move east or west, true north’s heading (expressed as a rotation around the +Y axis) will change slightly.

The reference point is generally the center of the mapped scenery, which is __not__ the same as sim/flightmodel/position/lat_ref and sim/flightmodel/position/lon_ref. Only use the lat_ref and lon_ref data references to detect a coordinate system shift.

Do not program your own coordinate transformations! The only safe way to change coordinate systems is to use XPLMWorldToLocal and XPLMLocalToWorld.

Aircraft Coordinates

A given aircraft in X-Plane can be thought to have its own coordinate system with the same units (meters) as world coordinate systems, but positioned differently:

  • The origin is at the default center of gravity for the aircraft.
  • The X axis points to the right side of the aircraft.
  • The Y axis points up.
  • The Z axis points to the tail of the aircraft.

You can draw in aircraft coordinates using these OpenGL transformations:

glTranslatef(local_x, local_y, local_z);
glRotatef	(-heading, 0.0,1.0,0.0);	
glRotatef	(-pitch,-1.0,0.0,0.0);
glRotatef	(-roll, 0.0,0.0,1.0);

where local_x, local_y, and local_z is the plane’s location in “local” (OpenGL) coordinates, and pitch, heading, and roll are the Euler angles for the plane. Be sure to use glPushMtarix and glPopMatrix to restore the coordinate system.

You can manually transform a point from airplane to world coordinates using the following formula:

INPUTS: (x_plane,y_plane,z_plane) = source location in airplane coordinates.  
        phi = roll, psi = heading, the = pitch.  
        (local_x, local_y, local_z) = plane's location in the world 
OUTPUTS:(x_wrl, y_wrl, z_wrl) = transformed location in world.
x_phi=x_plane*cos(phi) + y_plane*sin(phi)
y_phi=y_plane*cos(phi) - x_plane*sin(phi)
z_phi=z_plane
x_the=x_phi
y_the=y_phi*cos(the) - z_phi*sin(the)
z_the=z_phi*cos(the) + y_phi*sin(the)
x_wrl=x_the*cos(psi) - z_the*sin(psi) + local_x
y_wrl=y_the                           + local_y
z_wrl=z_the*cos(psi) + x_the*sin(psi) + local_z

This is in fact 3 2-d rotations plus an offset.

2-D Windows and Panels

For the purpose of user interface, you may assume that the region from 0,0 to 1024,768 is always visible on the screen, because this is the minimum X-Plane resolution. However you should not make assumptions about where wider screen real-estate has been added (e.g. in negative X area or on the right side). The less you put in code about the coordinate system the better.

Currently correct panel positioning is extremely tricky. The dataref sim/graphics/view/panel_scroll_pos gives an offset from the panel’s uppermost position (zero) downward, to a max value of 256 for the tallest panel on the shortest screen (1024 panel on 768 tall screen).

For X-Plane through 8.06 and earlier, the coordinate 0,0 is at the left edge of the visible panel. For X-Plane 8.10 and later, 0,0 is at the lower left corner of the screen.

Note: currently it is extremely difficult to do precise alignment with the panel or handle larger resolutions. A future set of datarefs will provide comprehensive insulated coordinate system information.

As a final note, if you need to have comprehensive access to the screen in a known manner and do not need to align with Austin’s graphics or mouse clicks, you can simply change the projection matrix entirely, based on a physical window from 0,0 to sim/graphics/view/window_width and sim/graphics/view/window_height. However, this does not provide an easy way to get mapped mouse clicks.

Using the New Panel Datarefs

X-Plane 8.15 introduces a series of new datarefs that simplifies interaction between plugins and X-Plane’s panel. Here’s the concept:

  • Coordinates are given in terms of rectangles (left, bottom, right and top) that describe the edges of a given useful rectangle on screen.
  • The panel rectangle is the rectangle covered by the panel.png bitmap from the current plane. Please note that the height of this panel is variable up to 1024 pixels, so the aspect ratio of this rectangle is variable.
  • The visible rectangle is the area on screen that the user can see. Usually this rectangle is smaller than the panel (for a scrolling panel, for example), but in the case of the user running at extra-high resolution, the visible rectangle may be larger than the panel rectangle.
  • There are essentially two coordinate systems: __window__ coordinates are the coordinate system used by the XPLMDisplay APIs, drawing callbacks before and after windows, and XPLMGetMouseLocation. __panel__ cordinates are the coordinate system in effect when the panel and gauge drawing callbacks are called.

While at this instant (XP815) the two coordinates are the same, this is __not__ at all guaranteed. The useful rectangles are provided in two different coordinate systems; you should use the ones appropriate to the APIs/drawing callbacks you are using! Because the rectangles are provided in two coordinate systems it is also possible for you to convert coordinate systems using these datarefs.

Besides coordinate mapping and correct positioning of your graphics to correspond to the panel you can also determine the panel scroll position and what parts of the panel are visible at a given instant.

Here are the datarefs:

The total panel in panel coordinates.

sim/graphics/view/panel_total_pnl_l
sim/graphics/view/panel_total_pnl_b
sim/graphics/view/panel_total_pnl_r
sim/graphics/view/panel_total_pnl_t

The visible panel in panel coordinates.

sim/graphics/view/panel_visible_pnl_l
sim/graphics/view/panel_visible_pnl_b
sim/graphics/view/panel_visible_pnl_r
sim/graphics/view/panel_visible_pnl_t

The total panel in window coordinates.

sim/graphics/view/panel_total_win_l
sim/graphics/view/panel_total_win_b
sim/graphics/view/panel_total_win_r
sim/graphics/view/panel_total_win_t

The visible panel in window coordinates.

sim/graphics/view/panel_visible_win_l
sim/graphics/view/panel_visible_win_b
sim/graphics/view/panel_visible_win_r
sim/graphics/view/panel_visible_win_t

All datarefs are non-writable floating point. Note: the panel position may be in fractional pixels. Your plugin is responsible for reducing the positions to integers as needed to deal with texture linear or nearest filtering.

Here are a few important warnings about these datarefs:

  • The panel-coordinate datarefs are only valid from a panel or gauge drawing callback! This is because the coordinate system used in drawing the panel may be different for the 2-d or 3-d object! So they really return the “panel coordinates now” and are invalid if no panel coordinate system is established.
  • Similarly the window coordinate datarefs are only valid from XPLM windows or the window-related drawing phases, for similar reasons to above.
  • Do not assume the difference between the left and right sides or top and bottom sides of the panels is 1024! Be prepared to scale your coordinates to make your drawing larger or smaller in OpenGL units as needed. We provide the entire rectangle so that you can detect both a translation and a scaling in both the X and Y dimensions!
  • Panel draw callbacks may be called more than once per flight loop! When the panel texture is larger than the screen, the panel draw callback will be called multiple times with different scroll positions to composite together the panel. So expect the panel datarefs to change even between different drawing callbacks!

Here is some sample code that draws boxes showing the various panel boundaries: DrawPanelBounds.

Comments Off on Screen Coordinates

Drawing Rules

The Four Basic Drawing Environments

X-Plane has four fundamental drawing environments:

  • The 3-d world
  • The 2-d cockpit
  • The 3-d cockpit
  • The 2-d user interface

A warning about these environments: there isn’t a clear correlation between the current view and what drawing phases are called. There is also not a clear correlation between the current view and whether the 3-d cockpit will be drawn. Rather these things are a function of the specific view and specific plane. Your best bet is to draw in response to certain drawing phases.

General Rules

Some basic rules to follow for safe drawing in X-Plane. Follow these rules to increase the likelihood of your plugin working in future sim versions.

OpenGL state:

  • Make no assumptions about OpenGL state when you enter your plugin.
  • Unless you change OpenGL state with an XPLM routine, leave OpenGL state the way you found it.
  • OpenGL state may change when calling XPLM routines, including reading data-refs!
  • Try to do drawing in large blocks to avoid having to deal with the above state problems frequently.
  • Lighting and fog are only trustworthy in 3-d world viewing.

Drawing callbacks:

  • Do not assume a correlation between drawing callback and sim frame-rate.
  • Use the elapsed time to determine animation.
  • If you have expensive calculations, try to move them to a processing callback, or do them “on demand” (e.g. once the first time they are required per sim frame). Otherwise multiple calls to your drawing callback may thrash your code.
  • Try to structure drawing to not depend on the sim’s current view; some views may be composites of several view-like states.
  • Read datarefs that affect drawing from within the drawing callback. Do not cache dataref values. Some view-related datarefs are only valid from within a view callback.
  • Do not use the “first” and “last” callbacks (either for 3-d or the panel). See specifics on drawing phases later in this technote.

Drawing to the 3-d world

The drawing-hook API was designed for use with X-Plane 6; a decade later, X-Plane’s underlying drawing has changed radically. These guidelines describe how to cope with modern (X-Plane 9, 10) versions of the sim.

To reliably draw in 3-d in X-Plane 9 or 10, follow the following guidelines:

  • Do not use the ‘before’ feature of drawing hooks to try to bypass or cancel drawing; always draw after the sim.
  • Use the aircraft phase to draw in 3-d (and draw after the x-plane aircraft).
  • For normal aircraft drawing hook will be called twice per frame. The first time will be to draw solid geometry. Use this time only if you intend to call XPLMObject. The second time will be for non-solid geometry – do any custom drawing yourself in this second call. (When X-Plane 10.30 comes out, a new dataref sim/graphics/view/plane_render_type will identify whether we are in the solid phase (1) or blended (2).
  • You can identify drawing calls made to compute shadows by looking at sim/graphics/view/world_render_type – this will be 0 for normal rendering, 1 for reflections (called only if your plugin enables the capability for reflection drawing) and 3 for shadows. When being called for shadows, you only need to output depth.

You can see an example of code that handles these cases correctly here:

//github.com/wadesworld2112/libxplanemp/blob/nextgen_csl/src/XPMPMultiplayer.cpp

Drawing in the 2-d cockpit

Use the xplm_Phase_Gauges phase to add elements to the 2-d panel. Try to avoid using the xplm_Phase_Panel phase (which draws the background) – replacing the background is probably not a good idea.

Note that the xplm_Phase_Gauges phase is called one or more times to prep the 3-d panel texture, or one or more times to draw the 2-d panel.

Use the view rect datarefs from the gauges callback to determine the right location to draw. Do not cache these – they can change per callback!

The xplm_Phase_Gauges drawing phase is a relatively safe one to use – it is only called when 2-d instruments are needed for some purpose, and the view rects will indicate which ones. (Gauges can be needed for the 2-d panel, interior 3-d panel, or even 3-d panel visible from outside through the cockpit windows. You can use the view_is_external dataref to simplify drawing if you need to reduce the framerate cost of drawing your panel.)

Drawing in the 3-d cockpit

Right now there is no good way to draw in the 3-d cockpit. We recommend either:

  • Draw to the 2-d panel’s opaque area (from xplm_Phase_Gauges) and use the panel texture. (You cannot draw into transparent areas.)
  • Use an animated 3-d cockpit object and drive animation datarefs from your plugin.

Drawing to the 2-d user interface

Use an XPLMWindow where possible. Use the before/after xplm_Phase_Window as needed.

Specifics on drawing phases:

xplm_Phase_FirstScene     Do not use
xplm_Phase_Terrain        Use to disable 3-d drawing mostly for framerate
xplm_Phase_Airports       Cosider using “objects” phase instead
xplm_Phase_Vectors        DEPRECATED – this phase is not called in X-Plane 8.
xplm_Phase_Objects        Use this phase for drawing 3-d clutter
xplm_Phase_Airplanes      Use this phase to hide all airplanes or draw onto the airplanes
xplm_Phase_LastScene      Do not use
xplm_Phase_FirstCockpit   Do not use
xplm_Phase_Panel          Use with caution – consider gauges phase instead
xplm_Phase_Gauges         Use to modify 2-d or 3-d panel
xplm_Phase_Window         Use for UI when XPLMWindow isn’t adequate
xplm_Phase_LastCockpit    Do not use

Comments Off on Drawing Rules

Deferred Initialization

X-Plane plugins are first loaded early in the X-Plane init sequence. This means that there are limitations on what you can do.

What’s Available

  • You can register any plugin callbacks you need to during either XPluginStart or XPluginEnable.
  • You can get info about your plugin (like its path on disk) from XPluginStart or XPluginEnable.
  • You can submit a list of aircraft to control. They will be loaded later when x-plane gets to loading aircraft.

What’s off limits

Most parts of the system are not available. Do not use:

  • The nav database APIs.
  • Anything that depends on an aircraft being loaded, scenery being loaded, or the flight having sane values.
  • Don’t use XPLMWorldToLocal or XPLMLocalToWorld…because the scenery engine is not loaded, there is no local coordinate system!
  • Do not assume other plugins are loaded from your XPluginStart. They may load later.
  • Do not assume other plugins are enabled from your XPluginEnable. They may be enabled later. This means that you can resolve datarefs, but may not be able to read them (since a read will fail if the plugin providing the data is disabled).
  • Do not load aircraft or command scenery changes during startup!
  • Preferences are not loaded!

Working around boot issues

Remember, XPluginEnable may be called during sim operations or during boot. The following technique will both allow you to defer processing until the sim is fully loaded and allow you to detect boot.

From your XPluginStart routine, register a processing callback to run on the first frame using XPLMRegisterFLightLoopCallback with an interval of -1.

When Not to Defer

There is one case where it is important not to defer initialization: if your plugin is distributed in an aircraft package and registers datarefs that drive animated OBJs (attached to the aircraft), you must register your datarefs from XPluginStart. Don’t defer!

Here’s why this is necessary: the sim will load your plugin, and then load the OBJs (which will do an XPLMFindDataRef to resolve datarefs in the OBJ file), and only when all loading is finished can the sim run and flight loop callbacks run. If you defer initialization, your datarefs won’t be available when the OBJ file looks for them.

Example

XPLMRegisterFlightLoopCallback(DeferredInitNewAircraftFLCB, -1, NULL);

As soon as x-plane is fully loaded and trying to run for real, you get a callback. Do any loading now that you couldn’t do before. Set a flag indicating that boot time is over; the next time you get your XPluginEnable call (if the user disables and enables your plugin), you’ll know that it is safe to load immediately.

float DeferredInitNewAircraftFLCB(float elapsedMe, float elapsedSim, int counter, void * refcon)
{
	static int MyProgramFLCBStartUpFlag = 0;
	if ( MyProgramFLCBStartUpFlag == 0 )
	{
		MyProgramFLCBStartUpFlag = 1; // Flag tells init has already been completed
		StartHeadX = XPLMGetDataf(CurrentHeadX); //Set startup parameters here.
		
		// ....
		
		DeltaTheta = 0.0;
		XPLMSetFlightLoopCallbackInterval (MyProgramFLCB, 0.01, 1, NULL);
	}
	return 0; // Returning 0 stops DeferredInitFLCB from being looped again.
}

Problems With C/C++ Global Initialization

Global and file-static data are initialized before your XPluginStart call! So not only can you not pre-initialize global variables if you need deferred initialization, but you can’t make any XPLM calls from global initializers. Here is an example of some offending C code:

#include "XPLMDataRefs.h"

// file static -- too early
static XPLMDataRef my_dref = XPLMFindDataRef("sim/operation/pause");
// true global -- too early
XPLMDataRef my_dref = XPLMFindDataRef("sim/operation/pause");

If you have a C++ object, the same rules apply to constructors if the object is instantiated globally, like this:

// wrapper obj around dataref
class dataref_obj {
public:
 dataref_obj(const char * s) { dref_ = XPLMFindDataRef(s); }
 XPLMDataRef dref_;
};

// these are too early too
static dataref_obj static_obj(XPLMFindDataRef("sim/operation/pause");
dataref_obj static_obj(XPLMFindDataRef("sim/operation/pause");

Declaring your object inside XPluginStart is legal, but often not useful.

int XPluginStart(char*,char*,char*)
{
  dataref_obj my_pause(XPLMFindDataRef("sim/operation/pause");
}

because the obj goes away when XPluginStart ends. One way around this is to use dynamic allocation – this gives you control of when the object is created.

// global
dataref_obj * my_obj = NULL;	// safe - no XPLM calls

int XPluginStart(...)
{
  my_obj = new dataref_obj("sim/operation/pause");
}

void XPluginStop()
{ 
 delete my_obj;
}
Comments Off on Deferred Initialization