NatNet: Creating a Native (C++) Client Application

Back to the Main pageBack to the NatNet SDK page


This guide covers essential points to developing a native client application using the NatNet SDK. The guideline uses sample codes in the SampleClient.cpp application in the \NatNet SDK\Sample folder, please refer to this project as an additional reference.

Warning2.png

SDK/API Support Disclaimer

We provide developer tools to enable OptiTrack customers across a broad set of applications to utilize their systems in the ways that best suit them. Our Motive API through the NatNet SDK and Camera SDK is designed to enable experienced software developers to integrate data transfer and/or system operation with their preferred systems and pipelines. Sample projects are provided alongside each tool, and we strongly recommend the users to reference or use the samples as reliable starting points. The following list specifies the range of support that will be provided for the SDK tools:
  • Using the SDK tools requires background knowledge on software development; therefore, we do not provide support for basic project setup, compiling, and linking when using the SDK/API to create your own applications.
  • Although we ensure the SDK tools and their libraries work as intended, we do not provide support for custom developed applications that have been programmed or modified by users using the SDK tools.
  • Ticketed support will be provided for licensed Motive users using the Motive API and/or the NatNet SDK tools from the included libraries and sample source codes only.
  • The Camera SDK is a free product, and therefore we do not provide free ticketed support for it.
  • For other questions, please check out the NaturalPoint forums. Very often, similar development issues get reported and solved there.


1. Import Library[edit]


a. Link the Library[edit]

When developing a native NatNet client application, NatNetLib.dll file needs to be linked to the project and placed alongside its executable in order to utilize the library classes and functions. Make sure the project is linked to DLL files with matching architecture (32-bit/64-bit).


b. Include the Header Files[edit]

After linking the library, include the header files within your application and import required library declarations. The header files are located in the NatNet SDK/include folder.
#include "NatNetTypes.h"
#include "NatNetClient.h"

2. Connect[edit]


a. Create a Client Object[edit]

Connection to a NatNet server application is accomplished through an instance of NatNetClient object. The client object is instantiated by calling the NatNetClient constructor with desired connection protocol (Multicast/Unicast) as its argument. Designate a desired connection protocol and instantiate the client object. In the SampleClient example, this step is done within the CreateClient function.
  • ConnectionType_Multicast = 0
  • ConnectionType_Unicast = 1


[C++] SampleClient.cpp : Instantiating NatNetClient


ConnectionType g_connectionType = ConnectionType_Multicast;
//ConnectionType g_connectionType = ConnectionType_Unicast;
int CreateClient(ConnectionType connectionType)
{
    // release previous server
    if(theClient)
    {
        theClient->Uninitialize();
        delete theClient;
    }

    // create NatNet client
    theClient = new NatNetClient(connectionType);


b. Connect to Server[edit]

Now that you have instantiated a NatNetClient object, connect the client to the server application at the designated IP address by calling the NatNetClient::Initialize method. The Connect method requires two input arguments: one for the local IP address that the client is running on and one for the server IP address that the data is streamed to. It is important that the client connects to appropriate IP addresses; otherwise, the data will not be received.
Once the connection is established, you can use methods within the NatNetClient object to send commands and query data.


[C++] SampleClient.cpp : Connect to the Server


//Connecting the created client class to a server application
char ClientIPAddress[128] = "127.0.0.1";
char ServerIPAddress[128] = "127.0.0.1";
// Init Client and connect to NatNet server
// to use NatNet default port assignments
int retCode = theClient->Connect(szMyIPAddress, szServerIPAddress);

// to use a different port for commands and/or data:
//int retCode = theClient->Initialize(szMyIPAddress, szServerIPAddress, MyServersCommandPort, MyServersDataPort);
if (retCode != ErrorCode_OK)
{
	printf("Unable to connect to server.  Error code: %d. Exiting", retCode);
	return ErrorCode_Internal;
}
else
{
	// get # of analog samples per mocap frame of data
	// ...


c. Confirm Connection[edit]

Now that the NatNetClient object is connected, let’s confirm the connection by querying the server for its descriptions. This can be obtained by calling the NatNetClient::GetServerDescription method and the information gets saved in the provided instance of sServerDescriptions. This is also demonstrated in the CreateClient function of the SampleClient project.


[C++] SampleClient.cpp : Request Server Description


// print server info
sServerDescription ServerDescription;
memset(&ServerDescription, 0, sizeof(ServerDescription));
theClient->GetServerDescription(&ServerDescription);

if(!ServerDescription.HostPresent)
{
	printf("Unable to connect to server. Host not present. Exiting.");
	return 1;
}

//Outputting server information.
printf("[SampleClient] Server application info:\n");

printf("Application: %s (ver. %d.%d.%d.%d)\n", ServerDescription.szHostApp, ServerDescription.HostAppVersion[0],
					ServerDescription.HostAppVersion[1],ServerDescription.HostAppVersion[2],
					ServerDescription.HostAppVersion[3]);

printf("NatNet Version: %d.%d.%d.%d\n", ServerDescription.NatNetVersion[0], ServerDescription.NatNetVersion[1],
					ServerDescription.NatNetVersion[2], ServerDescription.NatNetVersion[3]);

printf("Client IP:%s\n", szMyIPAddress);
printf("Server IP:%s\n", szServerIPAddress);
printf("Server Name:%s\n\n", ServerDescription.szHostComputerName);


You can also confirm connection by sending a NatNet remote command to the server. NatNet commands are sent by calling the NatNetClient::SendMessageAndWait method with supported NatNet Command as one of its input arguments. The following sample sends a command for querying the number of analog sample for each of the mocap frames. If the client is successfully connected to the server, this method will save the data and return 0.


[C++] SampleClient.cpp : Send NatNet Commands


// send/receive test request
printf("[SampleClient] Sending Test Request\n");
void* pResult;
int ret = 0;
int nBytes = 0;

// Querying configured system frame rate from the connected server
ret = theClient->SendMessageAndWait("AnalogSamplesPerMocapFrame", &pResult, &nBytes);

if (ret == ErrorCode_OK)
{
	analogSamplesPerMocapFrame = *((int*)pResult);
	printf("Analog Samples Per Mocap Frame : %d", analogSamplesPerMocapFrame);
}


3. Get DataDescriptions[edit]


a. Fetching Data Description[edit]

Now that the client application is connected, data descriptions for the streamed capture session can be obtained from the server. This can be done by calling the NatNetClient::GetDataDescriptions method and saving the descriptions list into an instance of sDataDescriptions. From this instance, the client application can figure out how many assets are in the scene as well as their descriptions.
This is done by the following line in the SampleClient project:


[C++] SampleClient.cpp : Get Data Descriptions


// Retrieve Data Descriptions from server
printf("\n\n[SampleClient] Requesting Data Descriptions...");
sDataDescriptions* pDataDefs = NULL;
iResult = theClient->GetDataDescriptions(&pDataDefs);

if (iResult != ErrorCode_OK || pDataDefs == NULL)
{
	printf("[SampleClient] Unable to retrieve Data Descriptions.");
}


b. Parsing Data Description[edit]

After an sDataDescriptions instance has been saved, data descriptions for each of the assets (marker, rigid body, skeleton, and force plate from the server) can be accessed from it.


[C++] SampleClient.cpp : Parsing Data Descriptions


// Retrieve Data Descriptions from server
printf("\n\n[SampleClient] Requesting Data Descriptions...");
sDataDescriptions* pDataDefs = NULL;
iResult = theClient->GetDataDescriptions(&pDataDefs);

if (iResult != ErrorCode_OK || pDataDefs == NULL)
{
	printf("[SampleClient] Unable to retrieve Data Descriptions.");
}
else
{
       printf("[SampleClient] Received %d Data Descriptions:\n", pDataDefs->nDataDescriptions );
       for(int i=0; i < pDataDefs->nDataDescriptions; i++)
       {
		printf("Data Description # %d (type=%d)\n", i, pDataDefs->arrDataDescriptions[i].type);
		if(pDataDefs->arrDataDescriptions[i].type == Descriptor_MarkerSet)
		{
			// MarkerSet
			sMarkerSetDescription* pMS = pDataDefs->arrDataDescriptions[i].Data.MarkerSetDescription;

           	}
           	else if(pDataDefs->arrDataDescriptions[i].type == Descriptor_RigidBody)
           	{
			// RigidBody
			sRigidBodyDescription* pRB = pDataDefs->arrDataDescriptions[i].Data.RigidBodyDescription;

           	}
           	else if(pDataDefs->arrDataDescriptions[i].type == Descriptor_Skeleton)
           	{
			// Skeleton
			sSkeletonDescription* pSK = pDataDefs->arrDataDescriptions[i].Data.SkeletonDescription;

           	}
           	else if(pDataDefs->arrDataDescriptions[i].type == Descriptor_ForcePlate)
           	{
			// Force Plate
			sForcePlateDescription* pFP = pDataDefs->arrDataDescriptions[i].Data.ForcePlateDescription;

           	}
           	else
           	{
               	printf("Unknown data type.");
			// Unknown
           	}
       }      
}


4. Get FrameOfMocapData[edit]


a. Set Callback Functions[edit]

Now that we have data descriptions, let's fetch the corresponding frame-specific data. To do this, a callback handler function needs to be set for processing the incoming frames. First, create a callback function that has the correct prototype (input arguments and return types) as declared in the NatNetClient::SetDataCallback method:
void (*CallbackFunction)(sFrameOfMocapData* pFrameOfData, void* pUserData)
The SampleClient.cpp project sets DataHandler function as the frame handler function.
void __cdecl DataHandler(sFrameOfMocapData* data, void* pUserData)


The NatNetClient::SetDataCallback method creates a new thread and assigns the frame handler function. Call this method with the created function and the NatNetClient object as its arguments. In the SampleClient application, this is called within the CreateClient function:
// set the callback handlers
// The DataHandler function will receive data from the server
theClient->SetDataCallback( DataHandler, theClient );

b. Parsing/Handling Frame Data Handling[edit]

Once you call the SetDataCallback method to link a data handler callback function, this function will receive a packet of sFrameOfMocapData each time a frame is received. The sFrameOfMocapData contains a single frame data for all of the streamed assets. This allows prompt processing of the capture frames within the handler function.

[C++] SampleClient.cpp : Frame Data Callback Handler


// set the callback handlers
// The DataHandler function will receive data from the server
theClient->SetDataCallback( DataHandler, theClient );
void __cdecl DataHandler(sFrameOfMocapData* data, void* pUserData)
{
	NatNetClient* pClient = (NatNetClient*) pUserData;

	if(fp)
		_WriteFrame(fp,data);
	
	int i=0;

	printf("FrameID : %d\n", data->iFrame);
	printf("Timestamp :  %3.2lf\n", data->fTimestamp);
	printf("Latency :  %3.2lf\n", data->fLatency);

	// FrameOfMocapData params
	bool 	bIsRecording = ((data->params & 0x01)!=0);
	bool	bTrackedModelsChanged = ((data->params & 0x02)!=0);

	if(bIsRecording)
	printf("RECORDING\n");

	if(bTrackedModelsChanged)
		printf("Models Changed.\n");

	// Printing Rigid Body Data…
	//…

	// Printing Skeleton Data…
	//…

	// Printing Rigid Body Data…
	//…

	// labeled markers…
	//…

    	// force plates…
	//…
}


5. Disconnect[edit]


When exiting the program, call Uninitialize method using the connected client object and disconnect the client application from the server.
	/*  Disconnecting from the Server  */
	theClient -> Uninitialize();
} // End. Main()