Archives: Code Samples

X-Plane 11 Window API Sample

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(&params);
	
	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;
	}
}
55 Comments

X-Plane 11 Map API Sample

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(&params);
	}
}

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;
}
22 Comments

TextureDraw

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)
{
}

5 Comments

TextFieldFilters

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);
 }
Leave a comment

Fast Culling

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;
	
}
4 Comments

ClickingIn3D

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;
 }
1 Comment

Billboards

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.

2 Comments

ArrayDataRef

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];
}
Leave a comment

Register Custom DataRef in Dataref Editor

// 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!  
 }
1 Comment

Draw Terrain Object

// *************************************************
// 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 &amp;&amp; CofG_refx &amp;&amp; CofG_refy &amp;&amp; CofG_refz)
    //Locate the test object by its heading.
    { 
           AircraftHeading = XPLMGetDataf(gHeadingDataRef);
 					
            if (AircraftHeading &lt;= 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 &lt;= 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 &lt;= 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 &lt;= 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;
}
4 Comments