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:
- sim/graphics/view/click_3d_x
- sim/graphics/view/click_3d_y
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];
}