
The following sample code creates a new, “modern” window styled like an X-Plane 11 window. It demonstrates a number of the new XPLM300 APIs, including:
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include <string.h>
#include <stdio.h>
#if IBM
#include <windows.h>
#endif
#if LIN
#include <GL/gl.h>
#elif __GNUC__
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif
#ifndef XPLM300
#error This is made to be compiled against the XPLM300 SDK
#endif
static XPLMWindowID g_window;
void draw(XPLMWindowID in_window_id, void * in_refcon);
int handle_mouse(XPLMWindowID in_window_id, int x, int y, int is_down, void * in_refcon);
void receive_main_monitor_bounds(int inMonitorIndex, int inLeftBx, int inTopBx, int inRightBx, int inBottomBx, void * refcon);
int dummy_mouse_handler(XPLMWindowID in_window_id, int x, int y, int is_down, void * in_refcon) { return 0; }
XPLMCursorStatus dummy_cursor_status_handler(XPLMWindowID in_window_id, int x, int y, void * in_refcon) { return xplm_CursorDefault; }
int dummy_wheel_handler(XPLMWindowID in_window_id, int x, int y, int wheel, int clicks, void * in_refcon) { return 0; }
void dummy_key_handler(XPLMWindowID in_window_id, char key, XPLMKeyFlags flags, char virtual_key, void * in_refcon, int losing_focus) { }
static const char * g_pop_out_label = "Pop Out";
static const char * g_pop_in_label = "Pop In";
static float g_pop_button_lbrt[4]; // left, bottom, right, top
static float g_position_button_lbrt[4]; // left, bottom, right, top
static int coord_in_rect(float x, float y, float * bounds_lbrt) { return ((x >= bounds_lbrt[0]) && (x < bounds_lbrt[2]) && (y < bounds_lbrt[3]) && (y >= bounds_lbrt[1])); }
PLUGIN_API int XPluginStart(
char * outName,
char * outSig,
char * outDesc)
{
strcpy(outName, "GuiSamplePlugin");
strcpy(outSig, "xpsdk.examples.guisampleplugin");
strcpy(outDesc, "A test plug-in that demonstrates the X-Plane 11 GUI plugin API.");
// We're not guaranteed that the main monitor's lower left is at (0, 0)... we'll need to query for the global desktop bounds!
int global_desktop_bounds[4]; // left, bottom, right, top
XPLMGetScreenBoundsGlobal(&global_desktop_bounds[0], &global_desktop_bounds[3], &global_desktop_bounds[2], &global_desktop_bounds[1]);
XPLMCreateWindow_t params;
params.structSize = sizeof(params);
params.left = global_desktop_bounds[0] + 50;
params.bottom = global_desktop_bounds[1] + 150;
params.right = global_desktop_bounds[0] + 350;
params.top = global_desktop_bounds[1] + 450;
params.visible = 1;
params.drawWindowFunc = draw;
params.handleMouseClickFunc = handle_mouse;
params.handleRightClickFunc = dummy_mouse_handler;
params.handleMouseWheelFunc = dummy_wheel_handler;
params.handleKeyFunc = dummy_key_handler;
params.handleCursorFunc = dummy_cursor_status_handler;
params.refcon = NULL;
params.layer = xplm_WindowLayerFloatingWindows;
params.decorateAsFloatingWindow = 1;
g_window = XPLMCreateWindowEx(¶ms);
XPLMSetWindowPositioningMode(g_window, xplm_WindowPositionFree, -1);
XPLMSetWindowGravity(g_window, 0, 1, 0, 1); // As the X-Plane window resizes, keep our size constant, and our left and top edges in the same place relative to the window's left/top
XPLMSetWindowResizingLimits(g_window, 200, 200, 500, 500); // Limit resizing our window: maintain a minimum width/height of 200 boxels and a max width/height of 500
XPLMSetWindowTitle(g_window, "Sample Window");
return (g_window != NULL);
}
PLUGIN_API void XPluginStop(void)
{
// Since we created the window, we'll be good citizens and clean it up
XPLMDestroyWindow(g_window);
g_window = NULL;
}
PLUGIN_API void XPluginDisable(void) { }
PLUGIN_API int XPluginEnable(void) { return 1; }
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam) { }
void draw(XPLMWindowID in_window_id, void * in_refcon)
{
char scratch_buffer[150];
float col_white[] = {1.0, 1.0, 1.0};
XPLMSetGraphicsState(
0 /* no fog */,
0 /* 0 texture units */,
0 /* no lighting */,
0 /* no alpha testing */,
1 /* do alpha blend */,
1 /* do depth testing */,
0 /* no depth writing */
);
// We draw our rudimentary button boxes based on the height of the button text
int char_height;
XPLMGetFontDimensions(xplmFont_Proportional, NULL, &char_height, NULL);
// We'll change the text of the pop-in/pop-out button based on our current state
int is_popped_out = XPLMWindowIsPoppedOut(in_window_id);
const char * pop_label = is_popped_out ? g_pop_in_label : g_pop_out_label;
int l, t, r, b;
XPLMGetWindowGeometry(in_window_id, &l, &t, &r, &b);
// Draw our buttons
{
// Position the pop-in/pop-out button in the upper left of the window (sized to fit the
g_pop_button_lbrt[0] = l + 10;
g_pop_button_lbrt[3] = t - 15;
g_pop_button_lbrt[2] = g_pop_button_lbrt[0] + XPLMMeasureString(xplmFont_Proportional, pop_label, strlen(pop_label)); // *just* wide enough to fit the button text
g_pop_button_lbrt[1] = g_pop_button_lbrt[3] - (1.25f * char_height); // a bit taller than the button text
// Position the "move to lower left" button just to the right of the pop-in/pop-out button
const char * position_btn_text = "Move to Lower Left";
g_position_button_lbrt[0] = g_pop_button_lbrt[2] + 30;
g_position_button_lbrt[1] = g_pop_button_lbrt[1];
g_position_button_lbrt[2] = g_position_button_lbrt[0] + XPLMMeasureString(xplmFont_Proportional, position_btn_text, strlen(position_btn_text));
g_position_button_lbrt[3] = g_pop_button_lbrt[3];
// Draw the boxes around our rudimentary buttons
float green[] = {0.0, 1.0, 0.0, 1.0};
glColor4fv(green);
glBegin(GL_LINE_LOOP);
{
glVertex2i(g_pop_button_lbrt[0], g_pop_button_lbrt[3]);
glVertex2i(g_pop_button_lbrt[2], g_pop_button_lbrt[3]);
glVertex2i(g_pop_button_lbrt[2], g_pop_button_lbrt[1]);
glVertex2i(g_pop_button_lbrt[0], g_pop_button_lbrt[1]);
}
glEnd();
glBegin(GL_LINE_LOOP);
{
glVertex2i(g_position_button_lbrt[0], g_position_button_lbrt[3]);
glVertex2i(g_position_button_lbrt[2], g_position_button_lbrt[3]);
glVertex2i(g_position_button_lbrt[2], g_position_button_lbrt[1]);
glVertex2i(g_position_button_lbrt[0], g_position_button_lbrt[1]);
}
glEnd();
// Draw the button text (pop in/pop out)
XPLMDrawString(col_white, g_pop_button_lbrt[0], g_pop_button_lbrt[1] + 4, (char *)pop_label, NULL, xplmFont_Proportional);
// Draw the button text (reposition)
XPLMDrawString(col_white, g_position_button_lbrt[0], g_position_button_lbrt[1] + 4, (char *)position_btn_text, NULL, xplmFont_Proportional);
}
// Draw a bunch of informative text
{
// Set the y position for the first bunch of text we'll draw to a little below the buttons
int y = g_pop_button_lbrt[1] - 2 * char_height;
// Display the total global desktop bounds
{
int global_desktop_lbrt[4];
XPLMGetScreenBoundsGlobal(&global_desktop_lbrt[0], &global_desktop_lbrt[3], &global_desktop_lbrt[2], &global_desktop_lbrt[1]);
sprintf(scratch_buffer, "Global desktop bounds: (%d, %d) to (%d, %d)", global_desktop_lbrt[0], global_desktop_lbrt[1], global_desktop_lbrt[2], global_desktop_lbrt[3]);
XPLMDrawString(col_white, l, y, scratch_buffer, NULL, xplmFont_Proportional);
y -= 1.5 * char_height;
}
// Display our bounds
if(XPLMWindowIsPoppedOut(in_window_id)) // we are in our own first-class window, rather than "floating" within X-Plane's own window
{
int window_os_bounds[4];
XPLMGetWindowGeometryOS(in_window_id, &window_os_bounds[0], &window_os_bounds[3], &window_os_bounds[2], &window_os_bounds[1]);
sprintf(scratch_buffer, "OS Bounds: (%d, %d) to (%d, %d)", window_os_bounds[0], window_os_bounds[1], window_os_bounds[2], window_os_bounds[3]);
XPLMDrawString(col_white, l, y, scratch_buffer, NULL, xplmFont_Proportional);
y -= 1.5 * char_height;
}
else
{
int global_bounds[4];
XPLMGetWindowGeometry(in_window_id, &global_bounds[0], &global_bounds[3], &global_bounds[2], &global_bounds[1]);
sprintf(scratch_buffer, "Window bounds: %d %d %d %d", global_bounds[0], global_bounds[1], global_bounds[2], global_bounds[3]);
XPLMDrawString(col_white, l, y, scratch_buffer, NULL, xplmFont_Proportional);
y -= 1.5 * char_height;
}
// Display whether we're in front of our our layer
{
sprintf(scratch_buffer, "In front? %s", XPLMIsWindowInFront(in_window_id) ? "Y" : "N");
XPLMDrawString(col_white, l, y, scratch_buffer, NULL, xplmFont_Proportional);
y -= 1.5 * char_height;
}
// Display the mouse's position info text
{
int mouse_global_x, mouse_global_y;
XPLMGetMouseLocationGlobal(&mouse_global_x, &mouse_global_y);
sprintf(scratch_buffer, "Draw mouse (global): %d %d\n", mouse_global_x, mouse_global_y);
XPLMDrawString(col_white, l, y, scratch_buffer, NULL, xplmFont_Proportional);
y -= 1.5 * char_height;
}
}
}
int handle_mouse(XPLMWindowID in_window_id, int x, int y, XPLMMouseStatus is_down, void * in_refcon)
{
if(is_down == xplm_MouseDown)
{
const int is_popped_out = XPLMWindowIsPoppedOut(in_window_id);
if (!XPLMIsWindowInFront(in_window_id))
{
XPLMBringWindowToFront(in_window_id);
}
else if(coord_in_rect(x, y, g_pop_button_lbrt)) // user clicked the pop-in/pop-out button
{
XPLMSetWindowPositioningMode(in_window_id, is_popped_out ? xplm_WindowPositionFree : xplm_WindowPopOut, 0);
}
else if(coord_in_rect(x, y, g_position_button_lbrt)) // user clicked the "move to lower left" button
{
// If we're popped out, and the user hits the "move to lower left" button,
// we need to move them to the lower left of their OS's desktop space (units are pixels).
// On the other hand, if we're a floating window inside of X-Plane, we need
// to move to the lower left of the X-Plane global desktop (units are boxels).
void (* get_geometry_fn)(XPLMWindowID, int *, int *, int *, int *) = is_popped_out ? &XPLMGetWindowGeometryOS : &XPLMGetWindowGeometry;
int lbrt_current[4];
get_geometry_fn(in_window_id, &lbrt_current[0], &lbrt_current[3], &lbrt_current[2], &lbrt_current[1]);
int h = lbrt_current[3] - lbrt_current[1];
int w = lbrt_current[2] - lbrt_current[0];
void (* set_geometry_fn)(XPLMWindowID, int, int, int, int) = is_popped_out ? &XPLMSetWindowGeometryOS : &XPLMSetWindowGeometry;
// Remember, the main monitor's origin is *not* guaranteed to be (0, 0), so we need to query for it in order to move the window to its lower left
int bounds[4] = {0}; // left, bottom, right, top
if(is_popped_out)
{
XPLMGetScreenBoundsGlobal(&bounds[0], &bounds[3], &bounds[2], &bounds[1]);
}
else
{
XPLMGetAllMonitorBoundsOS(receive_main_monitor_bounds, bounds);
}
set_geometry_fn(in_window_id, bounds[0], bounds[1] + h, bounds[0] + w, bounds[1]);
}
}
return 1;
}
void receive_main_monitor_bounds(int inMonitorIndex, int inLeftBx, int inTopBx, int inRightBx, int inBottomBx, void * refcon)
{
int * main_monitor_bounds = (int *)refcon;
if(inMonitorIndex == 0) // the main monitor
{
main_monitor_bounds[0] = inLeftBx;
main_monitor_bounds[1] = inBottomBx;
main_monitor_bounds[2] = inRightBx;
main_monitor_bounds[3] = inTopBx;
}
}

The sample below exercises the XPLM300 map layer API. Note that it depends on the image below (map-sample-image.png) being located in your X-Plane/Resources/plugins directory. If the image is missing, you will see a “missing resource” warning in your Log.txt file (and of course the icon drawing functionality of the map layer will instead draw nothing).

#include "XPLMMap.h"
#include "XPLMGraphics.h"
#include <string.h>
#include <stdio.h>
#if IBM
#include <windows.h>
#endif
#if LIN
#include <GL/gl.h>
#elif __GNUC__
#include <OpenGL/gl.h>
#else
#include <GL/gl.h>
#endif
XPLMMapLayerID g_layer = NULL;
static int coord_in_rect(float x, float y, const float bounds_ltrb[4]) { return ((x >= bounds_ltrb[0]) && (x < bounds_ltrb[2]) && (y >= bounds_ltrb[3]) && (y < bounds_ltrb[1])); }
void createOurMapLayer(const char * mapIdentifier, void * refcon);
static void prep_cache( XPLMMapLayerID layer, const float * inTotalMapBoundsLeftTopRightBottom, XPLMMapProjectionID projection, void * inRefcon);
static void draw_markings( XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon);
static void draw_marking_icons( XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon);
static void draw_marking_labels(XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon);
static void will_be_deleted( XPLMMapLayerID layer, void * inRefcon);
PLUGIN_API int XPluginStart(
char * outName,
char * outSig,
char * outDesc)
{
strcpy(outName, "MapPlugin");
strcpy(outSig, "xpsdk.examples.mapplugin");
strcpy(outDesc, "A test plug-in that demonstrates and exercises the X-Plane 11 map API.");
return 1;
}
PLUGIN_API void XPluginStop(void)
{
// Clean up our map layer: if we created it, we should be good citizens and destroy it before the plugin is unloaded
if(g_layer)
{
// Triggers the will-be-deleted callback of the layer, causing g_layer to get set back to NULL
XPLMDestroyMapLayer(g_layer);
}
}
PLUGIN_API int XPluginEnable(void)
{
// We want to create our layer in the standard map used in the UI (not other maps like the IOS).
// If the map already exists in X-Plane (i.e., if the user has opened it), we can create our layer immediately.
// Otherwise, though, we need to wait for the map to be created, and only *then* can we create our layers.
if(XPLMMapExists(XPLM_MAP_USER_INTERFACE))
{
createOurMapLayer(XPLM_MAP_USER_INTERFACE, NULL);
}
// Listen for any new map objects that get created
XPLMRegisterMapCreationHook(&createOurMapLayer, NULL);
return 1;
}
PLUGIN_API void XPluginDisable(void) { }
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam) { }
void createOurMapLayer(const char * mapIdentifier, void * refcon)
{
if(!g_layer && // Confirm we haven't created our markings layer yet (e.g., as a result of a previous callback), or if we did, it's been destroyed
!strcmp(mapIdentifier, XPLM_MAP_USER_INTERFACE)) // we only want to create a layer in the normal user interface map (not the IOS)
{
XPLMCreateMapLayer_t params;
params.structSize = sizeof(XPLMCreateMapLayer_t);
params.mapToCreateLayerIn = XPLM_MAP_USER_INTERFACE;
params.willBeDeletedCallback = &will_be_deleted;
params.prepCacheCallback = &prep_cache;
params.showUiToggle = 1;
params.refcon = NULL;
params.layerType = xplm_MapLayer_Markings;
params.drawCallback = &draw_markings;
params.iconCallback = &draw_marking_icons;
params.labelCallback = &draw_marking_labels;
params.layerName = "Markings";
// Note: this could fail (return NULL) if we hadn't already confirmed that params.mapToCreateLayerIn exists in X-Plane already
g_layer = XPLMCreateMapLayer(¶ms);
}
}
int s_num_cached_coords = 0;
#define MAX_COORDS (360 * 180)
float s_cached_x_coords[MAX_COORDS]; // The map x coordinates at which we will draw our icons; only the range [0, s_num_cached_coords) are valid
float s_cached_y_coords[MAX_COORDS]; // The map y coordinates at which we will draw our icons; only the range [0, s_num_cached_coords) are valid
float s_cached_lon_coords[MAX_COORDS]; // The real latitudes that correspond to our cached map (x, y) coordinates; only the range [0, s_num_cached_coords) are valid
float s_cached_lat_coords[MAX_COORDS]; // The real latitudes that correspond to our cached map (x, y) coordinates; only the range [0, s_num_cached_coords) are valid
float s_icon_width = 0; // The width, in map units, that we should draw our icons.
void prep_cache(XPLMMapLayerID layer, const float * inTotalMapBoundsLeftTopRightBottom, XPLMMapProjectionID projection, void * inRefcon)
{
// We're simply going to cache the locations, in *map* coordinates, of all the places we want to draw.
s_num_cached_coords = 0;
for(int lon = -180; lon < 180; ++lon)
{
for(int lat = -90; lat < 90; ++lat)
{
float x, y;
const float offset = 0.25; // to avoid drawing on grid lines
XPLMMapProject(projection, lat + offset, lon + offset, &x, &y);
if(coord_in_rect(x, y, inTotalMapBoundsLeftTopRightBottom))
{
s_cached_x_coords[s_num_cached_coords] = x;
s_cached_y_coords[s_num_cached_coords] = y;
s_cached_lon_coords[s_num_cached_coords] = lon + offset;
s_cached_lat_coords[s_num_cached_coords] = lat + offset;
++s_num_cached_coords;
}
}
}
// Because the map uses true cartographical projections, the size of 1 meter in map units can change
// depending on where you are asking about. We'll ask about the midpoint of the available bounds
// and assume the answer won't change too terribly much over the size of the maps shown in the UI.
const float midpoint_x = (inTotalMapBoundsLeftTopRightBottom[0] + inTotalMapBoundsLeftTopRightBottom[2]) / 2;
const float midpoint_y = (inTotalMapBoundsLeftTopRightBottom[1] + inTotalMapBoundsLeftTopRightBottom[3]) / 2;
// We'll draw our icons to be 5000 meters wide in the map
s_icon_width = XPLMMapScaleMeter(projection, midpoint_x, midpoint_y) * 5000;
}
void draw_markings(XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon)
{
// The arbitrary OpenGL drawing done for our markings layer.
// We will simply draw a green box around the icon; the icon itself will be enqueued when we get a callback to draw_marking_icons().
XPLMSetGraphicsState(
0 /* no fog */,
0 /* 0 texture units */,
0 /* no lighting */,
0 /* no alpha testing */,
1 /* do alpha blend */,
1 /* do depth testing */,
0 /* no depth writing */
);
glColor3f(0, 1, 0); // green
const float half_width = s_icon_width / 2;
const float half_height = half_width * 0.6667; // our images are in a 3:2 aspect ratio, so the height is 2/3 the width
for(int coord = 0; coord < s_num_cached_coords; ++coord)
{
const float x = s_cached_x_coords[coord];
const float y = s_cached_y_coords[coord];
if(coord_in_rect(x, y, inMapBoundsLeftTopRightBottom))
{
// Draw the box around the icon (we use half the width and height, since the icons will be *centered* at this (x, y)
glBegin(GL_LINE_LOOP);
{
glVertex2f(x - half_width, y + half_height);
glVertex2f(x + half_width, y + half_height);
glVertex2f(x + half_width, y - half_height);
glVertex2f(x - half_width, y - half_height);
}
glEnd();
}
}
}
void draw_marking_icons(XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon)
{
for(int coord = 0; coord < s_num_cached_coords; ++coord)
{
const float x = s_cached_x_coords[coord];
const float y = s_cached_y_coords[coord];
if(coord_in_rect(x, y, inMapBoundsLeftTopRightBottom))
{
#define SAMPLE_IMG "Resources/plugins/map-sample-image.png"
if(coord % 2)
{
XPLMDrawMapIconFromSheet(
layer, SAMPLE_IMG,
0, 0, // draw the image cell at (s, t) == (0, 0) (i.e., the bottom left cell in the sample image)
2, 2, // our sample image is two image cell wide, and two image cells tall
x, y,
xplm_MapOrientation_Map, // Orient the icon relative to the map itself, rather than relative to the UI
0, // Zero degrees rotation
s_icon_width);
}
else
{
// Draw the image at cell (s, t) == (1, 1) (i.e., the top right cell in the sample image)
XPLMDrawMapIconFromSheet(layer, SAMPLE_IMG, 1, 1, 2, 2, x, y, xplm_MapOrientation_Map, 0, s_icon_width);
}
}
}
}
void draw_marking_labels(XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon)
{
if(zoomRatio >= 18) // don't label when zoomed too far out... everything will run together in a big, illegible mess
{
for(int coord = 0; coord < s_num_cached_coords; ++coord)
{
const float x = s_cached_x_coords[coord];
const float y = s_cached_y_coords[coord];
if(coord_in_rect(x, y, inMapBoundsLeftTopRightBottom))
{
char scratch_buffer[150];
sprintf(scratch_buffer, "%0.2f / %0.2f Lat/Lon", s_cached_lat_coords[coord], s_cached_lon_coords[coord]);
// The text will be centered at the (x, y) we pass in. But, instead of drawing the label in the center
// of the icon, we'd really like the text to be shifted down *beneath* the icon we drew,
// so we'll subtract some amount from the y coordinate
const float icon_bottom = y - s_icon_width / 2;
const float text_center_y = icon_bottom - (mapUnitsPerUserInterfaceUnit * icon_bottom / 2); // top of the text will touch the bottom of the icon
XPLMDrawMapLabel(layer, scratch_buffer, x, text_center_y, xplm_MapOrientation_Map, 0);
}
}
}
}
void will_be_deleted(XPLMMapLayerID layer, void * inRefcon)
{
if(layer == g_layer)
g_layer = NULL;
}
No project download is available. This is a code snippet and not a complete project.
#define APL 1
#define IBM 0
#define LIN 0
#define XPLM200 1
#include <stdio.h>
#include <string.h>
#include <XPLM/XPLMGraphics.h>
#include <XPLM/XPLMDisplay.h>
#include <OpenGL/gl.h>
// Our texture dimensions. Textures MUST be powers of 2 in OpenGL - if you don't need that much space,
// just round up to the nearest power of 2.
#define WIDTH 128
#define HEIGHT 128
// This is our texture ID. Texture IDs in OpenGL are just ints...but this is a global for the life of our plugin.
static int g_tex_num = 0;
// We use this memory to prep the buffer. Note that this memory DOES NOT have to be global - the memory is FULLY
// read by OpenGL before glTexSubImage2D or glTexImage2D return, so you could use local or temporary storage, or
// change the image AS SOON as the call returns! 4 bytes for R,G,B,A 32-bit pixels.
static unsigned char buffer[WIDTH*HEIGHT*4];
static int my_draw_tex(
XPLMDrawingPhase inPhase,
int inIsBefore,
void * inRefcon)
{
// A really dumb bitmap generator - just fill R and G with x and Y based color watch, and the B and alpha channels
// based on mouse position.
int mx, my, sx, sy;
XPLMGetMouseLocation(&mx, &my);
XPLMGetScreenSize(&sx,&sy);
unsigned char * c = buffer;
for(int y = 0; y < HEIGHT; ++y)
for(int x = 0; x < WIDTH; ++x)
{
*c++ = x * 255 / WIDTH;
*c++ = y * 255 / HEIGHT;
*c++ = mx * 255 / sx;
*c++ = my * 255 / sy;
}
XPLMBindTexture2d(g_tex_num,0);
// Note: if the tex size is not changing, glTexSubImage2D is faster than glTexImage2D.
glTexSubImage2D(GL_TEXTURE_2D,
0, // mipmap level
0, // x-offset
0, // y-offset
WIDTH,
HEIGHT,
GL_RGBA, // color of data we are seding
GL_UNSIGNED_BYTE, // encoding of data we are sending
buffer);
// The drawing part.
XPLMSetGraphicsState(
0, // No fog, equivalent to glDisable(GL_FOG);
1, // One texture, equivalent to glEnable(GL_TEXTURE_2D);
0, // No lighting, equivalent to glDisable(GL_LIGHT0);
0, // No alpha testing, e.g glDisable(GL_ALPHA_TEST);
1, // Use alpha blending, e.g. glEnable(GL_BLEND);
0, // No depth read, e.g. glDisable(GL_DEPTH_TEST);
0); // No depth write, e.g. glDepthMask(GL_FALSE);
glColor3f(1,1,1); // Set color to white.
int x1 = 20;
int y1 = 20;
int x2 = x1 + WIDTH;
int y2 = y1 + HEIGHT;
glBegin(GL_QUADS);
glTexCoord2f(0,0); glVertex2f(x1,y1); // We draw one textured quad. Note: the first numbers 0,1 are texture coordinates, which are ratios.
glTexCoord2f(0,1); glVertex2f(x1,y2); // lower left is 0,0, upper right is 1,1. So if we wanted to use the lower half of the texture, we
glTexCoord2f(1,1); glVertex2f(x2,y2); // would use 0,0 to 0,0.5 to 1,0.5, to 1,0. Note that for X-Plane front facing polygons are clockwise
glTexCoord2f(1,0); glVertex2f(x2,y1); // unless you change it; if you change it, change it back!
glEnd();
}
PLUGIN_API int XPluginStart(char * name, char * sig, char * desc)
{
strcpy(name,"Texture example");
strcpy(sig,"xpsdk.test.texture_example");
strcpy(desc,"Shows how to use textures.");
// Initialization: allocate a textiure number.
XPLMGenerateTextureNumbers(&g_tex_num, 1);
XPLMBindTexture2d(g_tex_num,0);
// Init to black for now.
memset(buffer,0,WIDTH*HEIGHT*4);
// The first time we must use glTexImage2D.
glTexImage2D(
GL_TEXTURE_2D,
0, // mipmap level
GL_RGBA, // internal format for the GL to use. (We could ask for a floating point tex or 16-bit tex if we were crazy!)
WIDTH,
HEIGHT,
0, // border size
GL_RGBA, // format of color we are giving to GL
GL_UNSIGNED_BYTE, // encoding of our data
buffer);
// Note: we must set the filtering params to SOMETHING or OpenGL won't draw anything!
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
XPLMRegisterDrawCallback(my_draw_tex, xplm_Phase_Gauges, 0, NULL);
return 1;
}
PLUGIN_API void XPluginStop(void)
{
XPLMUnregisterDrawCallback(my_draw_tex,xplm_Phase_Gauges, 0, NULL);
XPLMBindTexture2d(g_tex_num,0);
GLuint t=g_tex_num;
glDeleteTextures(1,&t);
}
PLUGIN_API int XPluginEnable(void)
{
return 1;
}
PLUGIN_API void XPluginDisable(void)
{
}
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID from, long msg, void * p)
{
}
No project download is available. This is a code snippet and not a complete project.
These filters are from XSquawkBox – they are tested and in a shipping plugin.
These filters take the form of a widget function and a helper function that attaches them – a simple way to specialize widget behavior.
Protecting from null characters
Older versions of the SDK sometimes sent null characters to text fields, crashing the sim. This small snippet protects a widget from this. This is unneeded for newer versions of X-Plane.
static int WidgetFunc_EatNullChars(
XPWidgetMessage inMessage,
XPWidgetID inWidget,
long inParam1,
long inParam2)
{
if (inMessage == xpMsg_KeyPress)
{
char theChar = KEY_CHAR(inParam1);
if (theChar == 0)
return 1; // Eat the null char!
}
return 0;
}
void ProtectTextWidget(XPWidgetID inWidgetID)
{
XPAddWidgetCallback(inWidgetID, WidgetFunc_EatNullChars);
}
Cut Copy and Paste with the OS
This attaches control V, C, and X as cut/copy/patste. It works on Mac and Windows.
bool XSBGetTextFromClipboard(std::string& outText)
{
#if IBM
HGLOBAL hglb;
LPTSTR lptstr;
bool retVal = false;
static XPLMDataRef hwndDataRef = XPLMFindDataRef("sim/operation/windows/system_window");
HWND hwndMain = (HWND) XPLMGetDatai(hwndDataRef);
if (!IsClipboardFormatAvailable(CF_TEXT))
return false;
if (!OpenClipboard(hwndMain))
return false;
hglb = GetClipboardData(CF_TEXT);
if (hglb != NULL)
{
lptstr = (LPSTR)GlobalLock(hglb);
if (lptstr != NULL)
{
outText = lptstr;
GlobalUnlock(hglb);
retVal = true;
}
}
CloseClipboard();
return retVal;
#endif
#if APL
ScrapRef scrap;
if (::GetCurrentScrap(&scrap) != noErr)
return false;
SInt32 byteCount = 0;
OSStatus status = ::GetScrapFlavorSize(scrap, kScrapFlavorTypeText, &byteCount);
if (status != noErr)
return false;
outText.resize(byteCount);
return (::GetScrapFlavorData(scrap, kScrapFlavorTypeText, &byteCount, &*outText.begin() ) == noErr);
#endif
}
bool XSBSetTextToClipboard(const std::string& inText)
{
#if IBM
LPTSTR lptstrCopy;
HGLOBAL hglbCopy;
static XPLMDataRef hwndDataRef = XPLMFindDataRef("sim/operation/windows/system_window");
HWND hwndMain = (HWND) XPLMGetDatai(hwndDataRef);
if (!OpenClipboard(hwndMain))
return false;
EmptyClipboard();
hglbCopy = GlobalAlloc(GMEM_MOVEABLE, sizeof(TCHAR) * (inText.length() + 1));
if (hglbCopy == NULL)
{
CloseClipboard();
return false;
}
lptstrCopy = (LPSTR)GlobalLock(hglbCopy);
strcpy(lptstrCopy, inText.c_str());
GlobalUnlock(hglbCopy);
SetClipboardData(CF_TEXT, hglbCopy);
CloseClipboard();
return true;
#endif
#if APL
ScrapRef scrap;
if (::ClearCurrentScrap() != noErr) return false;
if (::GetCurrentScrap(&scrap) != noErr) return false;
return ::PutScrapFlavor( scrap, kScrapFlavorTypeText, kScrapFlavorMaskNone, inText.size(), &*inText.begin()) == noErr;
#endif
}
static int WidgetFunc_CutCopyPaste(
XPWidgetMessage inMessage,
XPWidgetID inWidget,
long inParam1,
long inParam2)
{
if (inMessage == xpMsg_KeyPress)
{
char theChar = KEY_VKEY(inParam1);
XPLMKeyFlags flags = KEY_FLAGS(inParam1);
if ((flags & (xplm_DownFlag + xplm_ControlFlag)) == (xplm_DownFlag + xplm_ControlFlag))
{
long selStart = XPGetWidgetProperty(inWidget, xpProperty_EditFieldSelStart, NULL);
long selEnd = XPGetWidgetProperty(inWidget, xpProperty_EditFieldSelEnd, NULL);
long strLen = XPGetWidgetDescriptor(inWidget, NULL, 0);
std::string txt;
txt.resize(strLen);
XPGetWidgetDescriptor(inWidget, &*txt.begin(), txt.size()+1);
if (theChar == XPLM_VK_V)
{
std::string scrap;
if (XSBGetTextFromClipboard(scrap) && !scrap.empty())
{
if ((selEnd > selStart) && (selStart >= 0) && (selEnd <= strLen))
{
txt.replace(selStart, selEnd - selStart, scrap);
XPSetWidgetDescriptor(inWidget, txt.c_str());
XPSetWidgetProperty(inWidget, xpProperty_EditFieldSelStart, selStart + scrap.size());
XPSetWidgetProperty(inWidget, xpProperty_EditFieldSelEnd, selStart + scrap.size());
} else if ((selStart >= 0) && (selStart <= strLen)) {
txt.insert(selStart, scrap);
XPSetWidgetDescriptor(inWidget, txt.c_str());
XPSetWidgetProperty(inWidget, xpProperty_EditFieldSelStart, selStart + scrap.size());
XPSetWidgetProperty(inWidget, xpProperty_EditFieldSelEnd, selStart + scrap.size());
}
}
return 1;
}
if ((theChar == XPLM_VK_C) || (theChar == XPLM_VK_X))
{
if ((selStart >= 0) && (selStart < selEnd) && (selEnd <= strLen))
{
std::string scrap = txt.substr(selStart, selEnd - selStart);
if (XSBSetTextToClipboard(scrap) && (theChar == XPLM_VK_X))
{
txt.erase(selStart, selEnd - selStart);
XPSetWidgetDescriptor(inWidget, txt.c_str());
XPSetWidgetProperty(inWidget, xpProperty_EditFieldSelEnd, selStart);
}
}
return 1;
}
}
}
return 0;
}
void AttachCutCopyPaste(XPWidgetID inWidget)
{
XPAddWidgetCallback(inWidget, WidgetFunc_CutCopyPaste);
}
No project download is available. This is a code snippet and not a complete project.
This code snippet can be used to determine if a sphere at a location in the current OpenGL coordinates is visible.
- Call setup_cull_info once per drawing callback…this queries OpenGL to get the visibility info from OpenGL’s state. Between callbacks the camera may have moved.
- Call sphere_is_visible once for each visibility check you want to make – r = radius of the sphere in meters; if the sphere whose center is XYZ and radius is r is completely off screen, the function returns false, otherwise is returns true.
- Be careful about tuning cull checks vs. drawing; you only want to cull large chunks of geometry, so that the fps boost from not drawing justifies the CPU time spent culling.
- You don’t need to cull individual OBJs drawn with the XPLMDrawObject API – this API culls for you. But you might want to cull many objects at once, by culling a sphere that contains a whole set of objects.
- This code works in all projection matrices, ortho and frustum, including oblique frustums.
struct cull_info_t { // This struct has everything we need to cull fast!
float model_view[16]; // The model view matrix, to get from local OpenGL to eye coordinates.
float nea_clip[4]; // Four clip planes in the form of Ax + By + Cz + D = 0 (ABCD are in the array.)
float far_clip[4]; // They are oriented so the positive side of the clip plane is INSIDE the view volume.
float lft_clip[4];
float rgt_clip[4];
float bot_clip[4];
float top_clip[4];
};
static void setup_cull_info(cull_info_t * i)
{
float m[16];
// First, just read out the current OpenGL matrices...do this once at setup because it's not the fastest thing to do.
glGetFloatv(GL_MODELVIEW_MATRIX ,i->model_view);
glGetFloatv(GL_PROJECTION_MATRIX,m);
// Now...what the heck is this? Here's the deal: the clip planes have values in "clip" coordinates of: Left = (1,0,0,1)
// Right = (-1,0,0,1), Bottom = (0,1,0,1), etc. (Clip coordinates are coordinates from -1 to 1 in XYZ that the driver
// uses. The projection matrix converts from eye to clip coordinates.)
//
// How do we convert a plane backward from clip to eye coordinates? Well, we need the transpose of the inverse of the
// inverse of the projection matrix. (Transpose of the inverse is needed to transform a plane, and the inverse of the
// projection is the matrix that goes clip -> eye.) Well, that cancels out to the transpose of the projection matrix,
// which is nice because it means we don't need a matrix inversion in this bit of sample code.
// So this nightmare down here is simply:
// clip plane * transpose (proj_matrix)
// worked out for all six clip planes. If you squint you can see the patterns:
// L: 1 0 0 1
// R: -1 0 0 1
// B: 0 1 0 1
// T: 0 -1 0 1
// etc.
i->lft_clip[0] = m[0]+m[3]; i->lft_clip[1] = m[4]+m[7]; i->lft_clip[2] = m[8]+m[11]; i->lft_clip[3] = m[12]+m[15];
i->rgt_clip[0] =-m[0]+m[3]; i->rgt_clip[1] =-m[4]+m[7]; i->rgt_clip[2] =-m[8]+m[11]; i->rgt_clip[3] =-m[12]+m[15];
i->bot_clip[0] = m[1]+m[3]; i->bot_clip[1] = m[5]+m[7]; i->bot_clip[2] = m[9]+m[11]; i->bot_clip[3] = m[13]+m[15];
i->top_clip[0] =-m[1]+m[3]; i->top_clip[1] =-m[5]+m[7]; i->top_clip[2] =-m[9]+m[11]; i->top_clip[3] =-m[13]+m[15];
i->nea_clip[0] = m[2]+m[3]; i->nea_clip[1] = m[6]+m[7]; i->nea_clip[2] = m[10]+m[11]; i->nea_clip[3] = m[14]+m[15];
i->far_clip[0] =-m[2]+m[3]; i->far_clip[1] =-m[6]+m[7]; i->far_clip[2] =-m[10]+m[11]; i->far_clip[3] =-m[14]+m[15];
}
static int sphere_is_visible(const cull_info_t * i, float x, float y, float z, float r)
{
// First: we transform our coordinate into eye coordinates from model-view.
float xp = x * i->model_view[0] + y * i->model_view[4] + z * i->model_view[ 8] + i->model_view[12];
float yp = x * i->model_view[1] + y * i->model_view[5] + z * i->model_view[ 9] + i->model_view[13];
float zp = x * i->model_view[2] + y * i->model_view[6] + z * i->model_view[10] + i->model_view[14];
// Now - we apply the "plane equation" of each clip plane to see how far from the clip plane our point is.
// The clip planes are directed: positive number distances mean we are INSIDE our viewing area by some distance;
// negative means outside. So ... if we are outside by less than -r, the ENTIRE sphere is out of bounds.
// We are not visible! We do the near clip plane, then sides, then far, in an attempt to try the planes
// that will eliminate the most geometry first...half the world is behind the near clip plane, but not much is
// behind the far clip plane on sunny day.
if ((xp * i->nea_clip[0] + yp * i->nea_clip[1] + zp * i->nea_clip[2] + i->nea_clip[3] + r) < 0) return false;
if ((xp * i->bot_clip[0] + yp * i->bot_clip[1] + zp * i->bot_clip[2] + i->bot_clip[3] + r) < 0) return false;
if ((xp * i->top_clip[0] + yp * i->top_clip[1] + zp * i->top_clip[2] + i->top_clip[3] + r) < 0) return false;
if ((xp * i->lft_clip[0] + yp * i->lft_clip[1] + zp * i->lft_clip[2] + i->lft_clip[3] + r) < 0) return false;
if ((xp * i->rgt_clip[0] + yp * i->rgt_clip[1] + zp * i->rgt_clip[2] + i->rgt_clip[3] + r) < 0) return false;
if ((xp * i->far_clip[0] + yp * i->far_clip[1] + zp * i->far_clip[2] + i->far_clip[3] + r) < 0) return false;
return true;
}
No project download is available. This is a code snippet and not a complete project.
Note: this is not necessary to build a 2-d instrument on the 3-d panel; you can use the datarefs:
to recover the 2-d click location that was made to the 3-d panel.
Here are the important points:
- The input of the routine is a mouse location in 2-d, like you would get from a click callback in a window.
- This routine MUST be called while the OpenGL matrices are set up to _draw_ in 3-d!! So you cannot call this from your window – you must call it from a real 3-d drawing callback. Thus you need to save the 2-d click until the next drawing callback, then use it.
- This routine uses glGetXXXX which are not fast calls. Try to call this only once per frame and save the results. If you need to call this more than once, try to factor out common glGet calls. (However you must actually get the matrices each frame – when the camera moves they’ll be different.)
- The output of this click is two 3-d points. One is the location of the user’s camera in the 3-d world (camera_xyz) and the other is a point along the line of site from the user’s eye to the mouse (mouse_xyz). To determine what was clicked, you must intersect your 3-d geometry with this 3-d line. Make sure that you use the closest entity that is clicked, and make sure that you only click entities that are on the same side of camera_xyz as mouse_xyz!
- You will need two matrix routines: invertMatrix (to invert a 4×4 matrix) and multMatrixVec4d to transform a 4-component vector by a matrix. These are standard matrix operations, so there’s a ton of sample code out there – you’ll have to find some that meets your license requirements. A lot of Mesa is available under the BSD/X11 license and provides source for useful matrix operations.
(This snippet is based on the code used to click3-d objects, but has not been tested since reformatting, so there may be typos.)
int click_to_3d(GLdouble mouse_xy[2],
GLdouble camera_xyz[3],
GLdouble mouse_xyz[3])
{
GLdouble modelM[16]; // Our current model view matrix
GLdouble projM [16]; // Our current projection matrix
GLdouble modelI[16]; // The inverse of the model view matrix
GLint viewport[4]; // The current viewport
GLdouble cam_eye[4]={0.0,0.0,0.0,1.0}; // The camera loc in eye coords
GLdouble cam_mv[4]; // The camera loc in model-view coords
// This code reads all of the matrix state from OpenGL. It is the ONLY code that must be run
// while OpenGL is set up in the coordinate system that you want to use.
glGetDoublev (GL_MODELVIEW_MATRIX ,modelM);
glGetDoublev (GL_PROJECTION_MATRI ,projM);
glGetIntegerv(GL_VIEWPORT ,viewport);
// First we use glu to convert from "window" to world coordinates. Please note we pass a
// Z as 0. Z = 0 in window coordinates means a point on the near clipping plane. So we
// are converting the mouse point on the near clipping plane to world coordinates.
if(gluUnProject(mouse_xy[0],mouse_xy[1],0.0,modelM,projM,viewport,&mouse_xyz[0],&mouse_xyz[1],&mouse_xyz[2]))
// We also need to calculate the inverse of the modelview matrix. While unlikely, both
// of these can return false, which would indicate that the OpenGL matrices are so weird
// that we cannot figure out where we clicked.
if(invertMatrix(modelI,modelM))
{
// We take our eye point and transform it from eye to modelview coordinates. This is the
// location of the viewer's eye in our current coordinate system. (Yeah we should know it
// from setting up OGL, but this takes into account every transform that might be
// done - so it's real easy and safe.)
multMatrixVec4d(cam_mv,modelI,cam_eye);
// Note for OGL nerds: we really do need a 4-component matrix for the multiply above because
// the W coordinate can be used to encode translatiosn in the model-view matrix. But we do not
// need to do a perspective-divide; the model-view matrix should be only composed of transforms
// and rotations, not projections!
camera_xyz[0] = cam_mv[0];
camera_xyz[1] = cam_mv[1];
camera_xyz[2] = cam_mv[2];
return 1;
}
return 0;
}
No project download is available. This is a code snippet and not a complete project.
This code calculates the offsets to add to a center point to draw a billboard, by calculating the orientation of the user’s screen (“eye space” in OpenGL terms) but returning the results in the OpenGL local coordinates that you have to use to draw.
The Code
// This routine returns up to 3 camera directions: which way is "right", "up" and which way is the camera pointing ("look")
// in OpenGL coordinates. In other words, this is which way the user's SCREEN is pointing in OpenGL "local" coordinates.
// (If the user is facing true north at the origin and is not rolled, these functiosn would be trivially easy because
// right would be 1,0,0, up would be 0,1,0 and look would be 0,0,1. (NOTE: the look vector points TO the user, not
// FROM the user.)
//
// To draw a billboard centered at C, you would use these coordinates:
//
// c-rgt+up---c+rgt+up
// | |
// | C |
// c-rgt-up---c+rgt-up
//
static void camera_directions(
float * out_rgt, // Any can be NULL
float * out_up ,
float * out_look)
{
float m[16];
glGetFloatv(GL_MODELVIEW_MATRIX, m);
// Roughly speaking, a modelview matrix is made up more or less like this:
// [ EyeX_x EyeX_y EyeX_z a
// EyeY_x EyeY_y EyeY_z b
// EyeZ_x EyeZ_y EyeZ_z c
// um don't look down here ]
// where a, b, c are translations in _eye_ space. (For this reason, a,b,c is not
// the camera location - sorry!)
if(out_rgt) {
out_rgt[0] = m[0];
out_rgt[1] = m[4];
out_rgt[2] = m[8];
}
if(out_up) {
out_up[0] = m[1];
out_up[1] = m[5];
out_up[2] = m[9];
}
if(out_look) {
out_up[0] = m[2];
out_up[1] = m[6];
out_up[2] = m[10];
}
}
Sample Usage
How to draw the billboard? Something like this would work:
void draw_billboard(float x, float y, float z)
{
float r[3], u[3];
camera_directions(r,u,NULL);
glBegin(GL_LINE_LOOP);
glVertex3f(x-r[0]-u[0],y-r[1]-u[1],z-r[2]-u[2]);
glVertex3f(x+r[0]-u[0],y+r[1]-u[1],z+r[2]-u[2]);
glVertex3f(x+r[0]+u[0],y+r[1]+u[1],z+r[2]+u[2]);
glVertex3f(x-r[0]+u[0],y-r[1]+u[1],z-r[2]+u[2]);
glEnd();
}
Note that this is just an example; since camera_directions reads the GL matrices, you would want to call it only once per draw callback, not once per billboard!
Usage For “Look” Vectors
One possible usage of the look vector is to measure alignment between the camera and drawing and then fade. For example, to fade a prop disc you could take the dot product of the look vector and the normal vector of the prop disc – as the dot product becomes zero, fade the prop disc.
No project download is available. This is a code snippet and not a complete project.
This is how to write custom array dataref handlers. This example is for integer arrays. Key points:
- The get function returns the size of the array when NULL is passed as the return data ptr.
- The get function returns the actual number of items returned when the data ptr is not NULL.
- Both the get and set functions are responsible for checking out-of-bounds and making sure that they don’t run off the end of the array! The plugin system does not check this for you, and code calling your array may not check either.
(For example, if a user sets a generic instrument to read the 50th item in your array, and your array is only 25 items, X-
Plane will not detect this – it will simply ask you for the 50th item and you must not crash.
#define ARRAY_DIM 10
static int g_my_array[ARRAY_DIM] = { 0 };
long my_get_array(void * refcon, int * out_values, int in_offset, int in_max)
{
int n, r;
// If the array ptr is null, the caller just wants to know the total item count!
if(out_values == NULL)
return ARRAY_DIM;
// Calculate the number of items to return. We must limit by both the end of
// our array and the total number the caller asked for - whichever is less.
r = ARRAY_DIM - in_offset;
if(r > in_max) r = in_max;
// Now copy the actual items from our array to the returned memory.
for(n = 0; n < r; ++n) out_values[n] = g_my_array[n + in_offset]; return r; } void my_set_array(void *refcon, int * in_values, int in_offset, int in_max) { int n, r; // Calculate the number of items to copy in. This is the lesser of the number // the caller writes and the end of our array. r = ARRAY_DIM - in_offset; if (r > in_max) r = in_max;
// Copy the actual data.
for(n = 0; n < r; ++n)
g_my_array[n+ in_offset] = in_values[n];
}ues[n];
}
// Custom Commands
// Register Custom DataRefs in DataRefEditor.
//
// This plugin adds a few lines of code and a flight loop callback to the Custom Commands Control
// Custom DataRef example. The flight loop callback sends the message to DataRefEditor to
// register your custom dataref in DataRefEditor. Returning 0 from the flight loop sends the message
// only once. A similar flight loop is required for each of you custom datarefs.
//
// Content added by BlueSideUpBob.
#define XPLM200 = 1; // This example requires SDK2.0
#include "XPLMPlugin.h"
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include "XPLMProcessing.h"
#include "XPLMDataAccess.h"
#include "XPLMMenus.h"
#include "XPLMUtilities.h"
#include "XPWidgets.h"
#include "XPStandardWidgets.h"
#include "XPLMScenery.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MSG_ADD_DATAREF 0x01000000 // Add dataref to DRE message
XPLMDataRef gCounterDataRef = NULL; // Our custom dataref
int gCounterValue; // Our custom dataref's value
float RegCounterDataRefInDRE(float elapsedMe, float elapsedSim, int counter, void * refcon); // Declare callback to register dataref
int GetCounterDataRefCB(void* inRefcon);
void SetCounterDataRefCB(void* inRefcon, int outValue);
XPLMCommandRef CounterUpCommand = NULL; // Our two custom commands
XPLMCommandRef CounterDownCommand = NULL;
int CounterUpCommandHandler(XPLMCommandRef inCommand, // Our two custom command handlers
XPLMCommandPhase inPhase,
void * inRefcon);
int CounterDownCommandHandler(XPLMCommandRef inCommand,
XPLMCommandPhase inPhase,
void * inRefcon);
PLUGIN_API int XPluginStart(
char * outName,
char * outSig,
char * outDesc)
{
// Plugin Info
strcpy(outName, "CustomCommandsAndDataRefs");
strcpy(outSig, "BlueSideUpBob.Example.CustomCommandsAndDataRef");
strcpy(outDesc, "This example illustrates creating and using custom commands to control a custom DataRef.");
// Create our custom integer dataref
gCounterDataRef = XPLMRegisterDataAccessor("BSUB/CounterDataRef",
xplmType_Int, // The types we support
1, // Writable
GetCounterDataRefCB, SetCounterDataRefCB, // Integer accessors
NULL, NULL, // Float accessors
NULL, NULL, // Doubles accessors
NULL, NULL, // Int array accessors
NULL, NULL, // Float array accessors
NULL, NULL, // Raw data accessors
NULL, NULL); // Refcons not used
// Find and intialize our Counter dataref
gCounterDataRef = XPLMFindDataRef ("BSUB/CounterDataRef");
XPLMSetDatai(gCounterDataRef, 0);
XPLMRegisterFlightLoopCallback(RegCounterDataRefInDRE, 1, NULL); // This FLCB will register our custom dataref in DRE
// Create our commands; these will increment and decrement our custom dataref.
CounterUpCommand = XPLMCreateCommand("BSUB/CounterUpCommand", "Counter Up");
CounterDownCommand = XPLMCreateCommand("BSUB/CounterDownCommand", "Counter Down");
// Register our custom commands
XPLMRegisterCommandHandler(CounterUpCommand, // in Command name
CounterUpCommandHandler, // in Handler
1, // Receive input before plugin windows.
(void *) 0); // inRefcon.
XPLMRegisterCommandHandler(CounterDownCommand,
CounterDownCommandHandler,
1,
(void *) 0);
return 1;
}
PLUGIN_API void XPluginStop(void)
{
XPLMUnregisterDataAccessor(gCounterDataRef);
XPLMUnregisterCommandHandler(CounterUpCommand, CounterUpCommandHandler, 0, 0);
XPLMUnregisterCommandHandler(CounterDownCommand, CounterDownCommandHandler, 0, 0);
XPLMUnregisterFlightLoopCallback(RegCounterDataRefInDRE, NULL); // Don't forget to unload this callback.
}
PLUGIN_API void XPluginDisable(void)
{
}
PLUGIN_API int XPluginEnable(void)
{
return 1;
}
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFromWho,
long inMessage,
void * inParam)
{
}
int GetCounterDataRefCB(void* inRefcon)
{
return gCounterValue;
}
void SetCounterDataRefCB(void* inRefcon, int inValue)
{
gCounterValue = inValue;
}
int CounterUpCommandHandler(XPLMCommandRef inCommand,
XPLMCommandPhase inPhase,
void * inRefcon)
{
// If inPhase == 0 the command is executed once on button down.
if (inPhase == 0)
{
gCounterValue++;
if(gCounterValue > 10) {gCounterValue = 10;}
}
// Return 1 to pass the command to plugin windows and X-Plane.
// Returning 0 disables further processing by X-Plane.
return 0;
}
int CounterDownCommandHandler(XPLMCommandRef inCommand,
XPLMCommandPhase inPhase,
void * inRefcon)
{
if (inPhase == 1)
{
gCounterValue--;
if(gCounterValue < -10) {gCounterValue = -10;}
}
return 0;
}
// This single shot FLCB registers our custom dataref in DRE
float RegCounterDataRefInDRE(float elapsedMe, float elapsedSim, int counter, void * refcon)
{
XPLMPluginID PluginID = XPLMFindPluginBySignature("xplanesdk.examples.DataRefEditor");
if (PluginID != XPLM_NO_PLUGIN_ID)
{
XPLMSendMessageToPlugin(PluginID, MSG_ADD_DATAREF, (void*)"BSUB/CounterDataRef");
}
return 0; // Flight loop is called only once!
}
// *************************************************
// DRAW TERRAIN OBJECT
//
// This example illustrates drawing a terrain object that moves in relation to the aircraft. In this case
// the object is located directly forward of the cockpit.
//
// For this example to work it is necessary to create an object in the aircraft folder. Example:
// "Aircraft/General Aviation/FP404/objects/TestObject.obj". See line 143.
//
// To turn the object on and off it is nececssary to place generic triggers on the panel of your aircraft keyed
// to the command: BSUB/ShowObject/TestObject and BSUB/HideObject/TestObject.
//
// Blue Side Up,
// Bob.
//
// Bob@RogerThat.ca
//
// *************************************************
#include "XPLMPlugin.h"
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include "XPLMProcessing.h"
#include "XPLMDataAccess.h"
#include "XPLMMenus.h"
#include "XPLMUtilities.h"
#include "XPWidgets.h"
#include "XPStandardWidgets.h"
#include "XPLMScenery.h"
#include
#include
#include
#include
char gBob_debstr[128];
XPLMDataRef gHeadingDataRef = NULL;
XPLMDataRef gPitchDataRef = NULL;
XPLMDataRef gRollDataRef = NULL;
XPLMDataRef CofG_refx = NULL;
XPLMDataRef CofG_refy = NULL;
XPLMDataRef CofG_refz = NULL;
XPLMCommandRef ShowObjectCommand = NULL;
XPLMCommandRef HideObjectCommand = NULL;
int ShowObjectCommandHandler(XPLMCommandRef inCommand,
XPLMCommandPhase inPhase,
void * inRefcon);
int HideObjectCommandHandler(XPLMCommandRef inCommand,
XPLMCommandPhase inPhase,
void * inRefcon);
// Used by the Draw Objects test
XPLMObjectRef gTestObject=NULL;
const float Pi = 3.14159265358979;
const float RadToDeg = 180.0 / Pi;
const float DegToRad = 1.0 / RadToDeg;
// Prototype for Draw Object tests
int DrawTestObject(XPLMDrawingPhase inPhase,
int inIsBefore,
void * inRefcon);
PLUGIN_API int XPluginStart(
char * outName,
char * outSig,
char * outDesc)
{
// Plugin Info
strcpy(outName, "DrawTerrainObject");
strcpy(outSig, "BlueSideUpBob.Example.DrawTerrainObject");
strcpy(outDesc, "Draw, hide, and show a terrain object that moves along with aircraft.");
// Create the test commands; these will Show/Hide the test object.
ShowObjectCommand = XPLMCreateCommand("BSUB/ShowObject/TestObject", "Show TestObject");
HideObjectCommand = XPLMCreateCommand("BSUB/HideObject/TestObject", "Hide TestObject");
// Register our custom commands
XPLMRegisterCommandHandler(ShowObjectCommand, // in Command name
ShowObjectCommandHandler, // in Handler
1, // Receive input before plugin windows.
(void *) 0); // inRefcon.
XPLMRegisterCommandHandler(HideObjectCommand, // in Command name
HideObjectCommandHandler, // in Handler
1, // Receive input before plugin windows.
(void *) 0); // inRefcon.
// Get the aicraft position
CofG_refx = XPLMFindDataRef("sim/flightmodel/position/local_x");
CofG_refy = XPLMFindDataRef("sim/flightmodel/position/local_y");
CofG_refz = XPLMFindDataRef("sim/flightmodel/position/local_z");
gHeadingDataRef = XPLMFindDataRef ("sim/flightmodel/position/psi");
gRollDataRef = XPLMFindDataRef ("sim/flightmodel/position/phi");
gPitchDataRef = XPLMFindDataRef ("sim/flightmodel/position/theta");
// This used to draw the test object.
XPLMRegisterDrawCallback( DrawTestObject, xplm_Phase_Objects, 0, 0 );
return 1;
}
PLUGIN_API void XPluginStop(void)
{
XPLMUnregisterCommandHandler(ShowObjectCommand, ShowObjectCommandHandler, 0, 0);
XPLMUnregisterCommandHandler(HideObjectCommand, HideObjectCommandHandler, 0, 0);
}
PLUGIN_API void XPluginDisable(void)
{
}
PLUGIN_API int XPluginEnable(void)
{
return 1;
}
PLUGIN_API void XPluginReceiveMessage(
XPLMPluginID inFromWho,
long inMessage,
void * inParam)
{
}
int ShowObjectCommandHandler( XPLMCommandRef inCommand,
XPLMCommandPhase inPhase,
void * inRefcon)
{
if (inPhase == 0)
{
gTestObject = XPLMLoadObject("Aircraft/General Aviation/FP404Master/objects/TestObject.obj");
if (gTestObject == NULL)
{
sprintf(gBob_debstr,"Test Object not found, inPhase %d \n", inPhase);
XPLMDebugString(gBob_debstr);
}
}
// Return 1 to pass the command to plugin windows and X-Plane.
// Returning 0 disables further processing by X-Plane.
// In this case we might return 0 or 1 because X-Plane does not duplicate our command.
return 0;
}
int HideObjectCommandHandler( XPLMCommandRef inCommand,
XPLMCommandPhase inPhase,
void * inRefcon)
{
if (inPhase == 0)
{
// Unload the imported object
XPLMUnloadObject(gTestObject);
}
return 0;
}
// Function for Draw Object tests
int DrawTestObject( XPLMDrawingPhase inPhase,
int inIsBefore,
void * inRefcon)
{
static float LongitudinalOffset = -20.0;
static float VerticalOffset = 2.0;
float AircraftHeading;
// Draw the imported object. Only do this if we have everything we need.
if (gTestObject && CofG_refx && CofG_refy && CofG_refz)
//Locate the test object by its heading.
{
AircraftHeading = XPLMGetDataf(gHeadingDataRef);
if (AircraftHeading <= 90)
{
XPLMDrawInfo_t locations[1] = { 0 };
locations[0].structSize = sizeof(XPLMDrawInfo_t);
locations[0].x = XPLMGetDatad(CofG_refx) - LongitudinalOffset * sin (AircraftHeading * DegToRad);
locations[0].y = XPLMGetDatad(CofG_refy) + VerticalOffset;
locations[0].z = XPLMGetDatad(CofG_refz) + LongitudinalOffset * cos (AircraftHeading * DegToRad);
locations[0].pitch = XPLMGetDataf(gPitchDataRef);
locations[0].heading = AircraftHeading;
locations[0].roll = XPLMGetDataf(gRollDataRef);
// Draw the object
XPLMDrawObjects(gTestObject, 1, locations, 0, 0);
return 1;
}
if (AircraftHeading <= 180)
{
XPLMDrawInfo_t locations[1] = { 0 };
locations[0].structSize = sizeof(XPLMDrawInfo_t);
locations[0].x = XPLMGetDatad(CofG_refx) - LongitudinalOffset * sin ((180 - AircraftHeading) * DegToRad);
locations[0].y = XPLMGetDatad(CofG_refy) + VerticalOffset;
locations[0].z = XPLMGetDatad(CofG_refz) - LongitudinalOffset * cos ((180 - AircraftHeading) * DegToRad);
locations[0].pitch = XPLMGetDataf(gPitchDataRef);
locations[0].heading = AircraftHeading;
locations[0].roll = XPLMGetDataf(gRollDataRef);
// Draw the object
XPLMDrawObjects(gTestObject, 1, locations, 0, 0);
return 1;
}
if (AircraftHeading <= 270)
{
XPLMDrawInfo_t locations[1] = { 0 };
locations[0].structSize = sizeof(XPLMDrawInfo_t);
locations[0].x = XPLMGetDatad(CofG_refx) + LongitudinalOffset * cos ((270 - AircraftHeading) * DegToRad);
locations[0].y = XPLMGetDatad(CofG_refy) + VerticalOffset;
locations[0].z = XPLMGetDatad(CofG_refz) - LongitudinalOffset * sin ((270 - AircraftHeading) * DegToRad);
locations[0].pitch = XPLMGetDataf(gPitchDataRef);
locations[0].heading = AircraftHeading;
locations[0].roll = XPLMGetDataf(gRollDataRef);
// Draw the object
XPLMDrawObjects(gTestObject, 1, locations, 0, 0);
return 1;
}
if (AircraftHeading <= 360)
{
XPLMDrawInfo_t locations[1] = { 0 };
locations[0].structSize = sizeof(XPLMDrawInfo_t);
locations[0].x = XPLMGetDatad(CofG_refx) + LongitudinalOffset * sin ((360 - AircraftHeading) * DegToRad);
locations[0].y = XPLMGetDatad(CofG_refy) + VerticalOffset;
locations[0].z = XPLMGetDatad(CofG_refz) + LongitudinalOffset * cos ((360 - AircraftHeading) * DegToRad);
locations[0].pitch = XPLMGetDataf(gPitchDataRef);
locations[0].heading = AircraftHeading;
locations[0].roll = XPLMGetDataf(gRollDataRef);
// Draw the object
XPLMDrawObjects(gTestObject, 1, locations, 0, 0);
return 1;
}
}
return 1;
}