This example draws a 2-d label over the center of gravity of the user’s aircraft. It demonstrates how to implement 2-d OpenGL drawing over the screen (using a drawing callback) that matches locations in the 3-d world without using 3-d drawing callbacks. This technique works with OpenGL, Vulkan, or Metal drivers for X-Plane but requires X-Plane 11.50 or newer for datarefs.
We sometimes call this technique “coach marks” because it involves drawing markings over the 3-d world that match the 3-d world in location but do not interact with the depth buffer.
#include "XPLMDisplay.h"
#include "XPLMGraphics.h"
#include "XPLMDefs.h"
#include "XPLMUtilities.h"
#include "XPLMDataAccess.h"
#include <string.h>
/* This example plugin demonstrates how to use a 2-d drawing callback to draw
* to the screen in a way that matches the 3-d coordinate system. Add-ons that
* need to add 3-d labels, coach marks, or other non-3d graphics that "match"
* the real world can use this technique to draw on with Metal and Vulkan. */
// Datarefs for the aircraft position.
static XPLMDataRef s_pos_x = NULL;
static XPLMDataRef s_pos_y = NULL;
static XPLMDataRef s_pos_z = NULL;
// Transform matrices - we will use these to figure out where we shuold should have drawn.
static XPLMDataRef s_matrix_wrl = NULL;
static XPLMDataRef s_matrix_proj = NULL;
static XPLMDataRef s_screen_width = NULL;
static XPLMDataRef s_screen_height = NULL;
// 4x4 matrix transform of an XYZW coordinate - this matches OpenGL matrix conventions.
static void mult_matrix_vec(float dst[4], const float m[16], const float v[4])
{
dst[0] = v[0] * m[0] + v[1] * m[4] + v[2] * m[8] + v[3] * m[12];
dst[1] = v[0] * m[1] + v[1] * m[5] + v[2] * m[9] + v[3] * m[13];
dst[2] = v[0] * m[2] + v[1] * m[6] + v[2] * m[10] + v[3] * m[14];
dst[3] = v[0] * m[3] + v[1] * m[7] + v[2] * m[11] + v[3] * m[15];
}
// This drawing callback will draw a label to the screen where the
static int DrawCallback1(XPLMDrawingPhase inPhase, int inIsBefore, void * inRefcon)
{
// Read the ACF's OpengL coordinates
float acf_wrl[4] = {
XPLMGetDataf(s_pos_x),
XPLMGetDataf(s_pos_y),
XPLMGetDataf(s_pos_z),
1.0f };
float mv[16], proj[16];
// Read the model view and projection matrices from this frame
XPLMGetDatavf(s_matrix_wrl,mv,0,16);
XPLMGetDatavf(s_matrix_proj,proj,0,16);
float acf_eye[4], acf_ndc[4];
// Simulate the OpenGL transformation to get screen coordinates.
mult_matrix_vec(acf_eye, mv, acf_wrl);
mult_matrix_vec(acf_ndc, proj, acf_eye);
acf_ndc[3] = 1.0f / acf_ndc[3];
acf_ndc[0] *= acf_ndc[3];
acf_ndc[1] *= acf_ndc[3];
acf_ndc[2] *= acf_ndc[3];
float screen_w = XPLMGetDatai(s_screen_width);
float screen_h = XPLMGetDatai(s_screen_height);
float final_x = screen_w * (acf_ndc[0] * 0.5f + 0.5f);
float final_y = screen_h * (acf_ndc[1] * 0.5f + 0.5f);
// Now we have something in screen coordinates, which we can then draw a label on.
XPLMDrawTranslucentDarkBox(final_x - 5, final_y + 10, final_x + 100, final_y - 10);
float colWHT[] = { 1.0, 1.0, 1.0 };
XPLMDrawString(colWHT, final_x, final_y, "TEST STRING 1", NULL, xplmFont_Basic);
return 1;
}
PLUGIN_API int XPluginStart(char * outName, char * outSig, char * outDesc)
{
strcpy(outName,"Example label drawing");
strcpy(outSig,"com.laminar.example_label_drawing");
strcpy(outDesc,"A plugin that shows how to draw a 3-d-referenced label in 2-d");
XPLMRegisterDrawCallback(DrawCallback1, xplm_Phase_Window, 0, NULL);
s_pos_x = XPLMFindDataRef("sim/flightmodel/position/local_x");
s_pos_y = XPLMFindDataRef("sim/flightmodel/position/local_y");
s_pos_z = XPLMFindDataRef("sim/flightmodel/position/local_z");
// These datarefs are valid to read from a 2-d drawing callback and describe the state
// of the underlying 3-d drawing environment the 2-d drawing is layered on top of.
s_matrix_wrl = XPLMFindDataRef("sim/graphics/view/world_matrix");
s_matrix_proj = XPLMFindDataRef("sim/graphics/view/projection_matrix_3d");
// This describes the size of the current monitor at the time we draw.
s_screen_width = XPLMFindDataRef("sim/graphics/view/window_width");
s_screen_height = XPLMFindDataRef("sim/graphics/view/window_height");
return 1;
}
PLUGIN_API int XPluginEnable()
{
return 1;
}
PLUGIN_API void XPluginStop()
{
}
PLUGIN_API void XPluginDisable()
{
}
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMessage, void * inParam )
{
}
The following sample code, modeled after the X‑Plane 11 Window API sample, will create a “modern”-style window. Note that as of X‑Plane 11.20, control over windows in VR is quite limited—you can move a window in and out of VR and change the window’s size, but that’s about it.
Note that some of the APIs used here are new to X-Plane 11.20b5—if you get a runtime error, ensure you’re on the latest release.
This sample is based on the XPLM300 window sample. It demonstrates the use of the modelview, viewport, and projection matrix datarefs (new to X-Plane 11.10) for applying scissoring to new-style plugin-created windows.
If you build the plugin, but comment out the call the glScissor(), you’ll see the fill pattern goes all the way to the edge of the green rectangle.
#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 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) { }
PLUGIN_API int XPluginStart(
char * outName,
char * outSig,
char * outDesc)
{
strcpy(outName, "GravitySamplePlugin");
strcpy(outSig, "xpsdk.examples.gravitysampleplugin");
strcpy(outDesc, "A test plug-in that demonstrates the gravity features of 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);
// Set the window bounds such that we stretch the full *width* of the global desktop, and cover the top 200 bx
params.left = global_desktop_bounds[0];
params.bottom = global_desktop_bounds[3] - 200;
params.right = global_desktop_bounds[2];
params.top = global_desktop_bounds[3];
params.visible = 1;
params.drawWindowFunc = draw;
params.handleMouseClickFunc = dummy_mouse_handler;
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_WindowLayerFlightOverlay; // stick our window beneath all floating windows (like the X-Plane 11 map)
params.decorateAsFloatingWindow = 0;
g_window = XPLMCreateWindowEx(¶ms);
XPLMSetWindowPositioningMode(g_window, xplm_WindowPositionFree, -1);
// As the X-Plane window resizes, glue our left and right edges to the sides of the screen
// (causing our width to grow and shrink to match the window size), but keep a constant
// height for our window (with the same y position relative to the window's top).
XPLMSetWindowGravity(g_window, 0, 1, 1, 1);
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)
{
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 */
);
int b[4];
XPLMGetWindowGeometry(in_window_id, &b[0], &b[3], &b[2], &b[1]);
// Draw our window's translucent background overlay
XPLMDrawTranslucentDarkBox(b[0], b[3], b[2], b[1]);
// Display the window bounds (centered within the window)
char scratch_buffer[150];
sprintf(scratch_buffer, "Window bounds: %d %d %d %d", b[0], b[1], b[2], b[3]);
float col_white[] = {1.0, 1.0, 1.0};
int text_width = XPLMMeasureString(xplmFont_Proportional, scratch_buffer, strlen(scratch_buffer));
float text_midpoint_x = (b[2] + b[0]) / 2;
XPLMDrawString(col_white, text_midpoint_x - text_width / 2, (b[3] + b[1]) / 2, scratch_buffer, NULL, xplmFont_Proportional);
}
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;
}
}