Tilt Five NDK
Getting Started : C

Project Setup

The following files need to be included in your project

File(s) Purpose
.h files from the include directory C header files for the API
One of the libraries from lib directory The Tilt Five NDK support library

Using the Glasses

Service Connection

Most of the operations performed with the glasses are not done directly by the client, but are performed by the Tilt Five™ service on behalf of the client.

The connection to the service is not guaranteed to always be available. Scenarios where the service is unavailable include:

  • During client startup there can be a small delay before the connection is established.
  • The service is not installed (e.g. User hasn't installed the Tilt Five™ drivers).
  • The service may have crashed or been stopped by a user. As such, all queries should anticipate a response of T5_ERROR_NO_SERVICE and retry appropriately (assuming it's a transient condition).

A trivial example of handling this is shown below:

468 bool waiting = false;
469 for (;;) {
470 char serviceVersion[T5_MAX_STRING_PARAM_LEN];
471 size_t bufferSize = T5_MAX_STRING_PARAM_LEN;
473 t5ctx, kT5_ParamSys_UTF8_Service_Version, serviceVersion, &bufferSize);
474 if (!err) {
475 printf("Service version : %s\n", serviceVersion);
476 break;
477 }
478
479 if (err == T5_ERROR_NO_SERVICE) {
480 if (!waiting) {
481 printf("Waiting for service...\n");
482 waiting = true;
483 }
484 } else {
485 printf("Error getting service version : %s\n", t5GetResultMessage(err));
486 exit(EXIT_FAILURE);
487 }
488 }
T5_EXPORT const char * t5GetResultMessage(T5_Result result)
#define T5_ERROR_NO_SERVICE
Service isn't connected.
Definition: errors.h:72
T5_EXPORT T5_Result t5GetSystemUtf8Param(T5_Context context, T5_ParamSys param, char *buffer, size_t *bufferSize)
Get a system-wide UTF-8 encoded string parameter.
#define T5_MAX_STRING_PARAM_LEN
The maximum number of characters allowed for string values.
Definition: types.h:38
@ kT5_ParamSys_UTF8_Service_Version
Version of the service software - UTF8.
Definition: types.h:685

Threading

All calls are blocking, and their thread safety is documented on each call. Generally, calls are grouped into a number of groups. From each group only one function should should be called concurrently. Calls may be made to functions in different groups concurrently. Additionally, some calls require specific threads (such as graphics related functions like t5InitGlassesGraphicsContext() and t5SendFrameToGlasses()).

Operations

Depending on the level of access required to the API, there are one or more setup steps. Details are shown below with example code. Further details of the API are here documented in the Tilt Five™ Native Interface (C).

dot_inline_dotgraph_1.png

Create Context

437 T5_ClientInfo clientInfo = {
438 .applicationId = "com.MyCompany.MyApplication",
439 .applicationVersion = "1.0.0",
440 .sdkType = 0,
441 .reserved = 0,
442 };
443
444 T5_Context t5ctx;
445 T5_Result err = t5CreateContext(&t5ctx, &clientInfo, 0);
446 if (err) {
447 printf("Failed to create context\n");
448 }
uint32_t T5_Result
Represents an error code that may be returned by the Tilt Five™ API.
Definition: errors.h:47
T5_EXPORT T5_Result t5CreateContext(T5_Context *context, const T5_ClientInfo *clientInfo, void *platformContext)
Create a context object.
struct T5_ContextImpl * T5_Context
Opaque handle used with system-wide functions.
Definition: types.h:131
Client provided information for use with t5CreateGlasses()
Definition: types.h:295
const char * applicationId
The application ID.
Definition: types.h:297

Release Context

The context should be released after it's finished with

543 t5DestroyContext(&t5ctx);
T5_EXPORT void t5DestroyContext(T5_Context *context)
Destroy a context object.

System-wide Queries

System-wide queries require nothing more than a client to perform. Most of these operations are querying the service for common parameters such as the service version (t5GetSystemUtf8Param() and fixed physical parameters (t5GetGameboardSize()).

454 T5_GameboardSize gameboardSize;
455 err = t5GetGameboardSize(t5ctx, kT5_GameboardType_LE, &gameboardSize);
456 if (err) {
457 printf("Failed to get gameboard size\n");
458 } else {
459 printf("LE Gameboard size : %fm x %fm x %fm\n",
460 gameboardSize.viewableExtentPositiveX + gameboardSize.viewableExtentNegativeX,
461 gameboardSize.viewableExtentPositiveY + gameboardSize.viewableExtentNegativeY,
462 gameboardSize.viewableExtentPositiveZ);
463 }
T5_EXPORT T5_Result t5GetGameboardSize(T5_Context context, T5_GameboardType gameboardType, T5_GameboardSize *gameboardSize)
Get the gameboard dimensions.
@ kT5_GameboardType_LE
An LE gameboard.
Definition: types.h:262
Physical dimensions of a gameboard.
Definition: types.h:272
float viewableExtentNegativeY
The distance in meters from the gameboard origin to the edge of the viewable area in the negative Y d...
Definition: types.h:287
float viewableExtentPositiveZ
The distance in meters above the gameboard origin that the viewable area extends in the positive Z di...
Definition: types.h:291
float viewableExtentPositiveY
The distance in meters from the gameboard origin to the edge of the viewable area in the positive Y d...
Definition: types.h:283
float viewableExtentNegativeX
The distance in meters from the gameboard origin to the edge of the viewable area in the negative X d...
Definition: types.h:279
float viewableExtentPositiveX
The distance in meters from the gameboard origin to the edge of the viewable area in the positive X d...
Definition: types.h:275

Enumerate Glasses

492 for (;;) {
493 size_t bufferSize = GLASSES_BUFFER_SIZE;
494 char glassesListBuffer[GLASSES_BUFFER_SIZE];
495 err = t5ListGlasses(t5ctx, glassesListBuffer, &bufferSize);
496 if (!err) {
497 size_t glassesCount = 0;
498
499 const char* buffPtr = glassesListBuffer;
500 size_t remainingSize = bufferSize;
501 for (;;) {
502 // Get the length of the string and exit if we've read the
503 // terminal string (Zero length)
504 size_t stringLength = strnlen(buffPtr, remainingSize);
505 if (stringLength == 0) {
506 break;
507 } else if (stringLength == remainingSize) {
508 // no null terminator encountered in the provided buffer!
509 printf("Warning: list may be missing null terminator");
510 break;
511 }
512
513 fprintf(stderr, "Glasses : %s\n", buffPtr);
514 glassesCount++;
515
T5_EXPORT T5_Result t5ListGlasses(T5_Context context, char *buffer, size_t *bufferSize)
Enumerate all glasses.

At this point buffPtr contains a glasses identifier suitable for Create Glasses 'creating/selecting glasses'.

519
520 // Advance through the returned values
521 buffPtr += stringLength + 1;
522 remainingSize -= stringLength + 1;
523 assert(buffPtr <= (glassesListBuffer + bufferSize));
524 }
525
526 printf("Listed glasses [%zu found]\n", glassesCount);
527 break;
528 }
529
530 if (err == T5_ERROR_NO_SERVICE) {
531 if (!waiting) {
532 printf("Waiting for service...\n");
533 waiting = true;
534 }
535 } else {
536 printf("Error listing glasses : %s\n", t5GetResultMessage(err));
537 exit(EXIT_FAILURE);
538 }
539 }

Create Glasses

Creating glasses handles requires a hardware identifier (id below) for the glasses as well as some information about the client.

312
313 // CREATE GLASSES
314 T5_Glasses glassesHandle;
315 T5_Result err = t5CreateGlasses(t5ctx, id, &glassesHandle);
316 if (err) {
317 printf("Error creating glasses '%s' : %s\n", id, t5GetResultMessage(err));
318 return;
319 } else {
320 printf("Created glasses : '%s'\n", id);
321 }
T5_EXPORT T5_Result t5CreateGlasses(T5_Context context, const char *id, T5_Glasses *glasses)
Create a glasses access object.
struct T5_GlassesImpl * T5_Glasses
Opaque handle used with glasses.
Definition: types.h:137

Non-exclusive Operations

Some glasses operations are available without acquiring exclusive access. These include operations to query stored parameters about glasses such as the IPD (t5GetGlassesFloatParam()) and user specified friendly name (t5GetGlassesUtf8Param()). Additionally, accessing the Wand Stream 'wand stream' does not require an exclusive connection.

257T5_Result endlesslyWatchSettings(T5_Glasses glassesHandle, const char* id) {
258 T5_Result err;
259
260 printf("Watching for changes to settings... (forever)\n");
261 for (;;) {
262 uint16_t count = PARAM_BUFFER_SIZE;
263 T5_ParamGlasses paramBuffer[PARAM_BUFFER_SIZE];
264 err = t5GetChangedGlassesParams(glassesHandle, paramBuffer, &count);
265 if (err) {
266 printf("Error getting changed params for '%s' : %s\n", id, t5GetResultMessage(err));
267 return err;
268 }
269
270 // Get the values of the changed params
271 for (int i = 0; i < count; i++) {
272 switch (paramBuffer[i]) {
274 double value = 0.0;
275 err = t5GetGlassesFloatParam(glassesHandle, 0, paramBuffer[i], &value);
276 if (err) {
277 printf("Error getting changed IPD for '%s' : %s\n",
278 id,
279 t5GetResultMessage(err));
280 return err;
281 }
282
283 printf("IPD changed for '%s' : %f\n", id, value);
284 } break;
285
287 char buffer[T5_MAX_STRING_PARAM_LEN];
288 size_t bufferSize = T5_MAX_STRING_PARAM_LEN;
290 glassesHandle, paramBuffer[i], 0, buffer, &bufferSize);
291 if (err) {
292 printf("Error getting changed friendly name for '%s' : %s\n",
293 id,
294 t5GetResultMessage(err));
295 return err;
296 }
297
298 printf("Friendly name changed for '%s' : '%s'\n", id, buffer);
299 } break;
300
301 default:
302 // Ignore other parameters
303 break;
304 }
305 }
306 }
307}
T5_EXPORT T5_Result t5GetGlassesFloatParam(T5_Glasses glasses, T5_WandHandle wand, T5_ParamGlasses param, double *value)
Get a glasses floating point parameter.
T5_EXPORT T5_Result t5GetGlassesUtf8Param(T5_Glasses glasses, T5_WandHandle wand, T5_ParamGlasses param, char *buffer, size_t *bufferSize)
Get a glasses UTF-8 encoded string parameter.
T5_EXPORT T5_Result t5GetChangedGlassesParams(T5_Glasses glasses, T5_ParamGlasses *buffer, uint16_t *count)
Get a glasses-specific list of changed parameters.
T5_ParamGlasses
Possible parameters that can be retrieved for glasses.
Definition: types.h:673
@ kT5_ParamGlasses_Float_IPD
Interpupillary distance - Float, millimeters
Definition: types.h:676
@ kT5_ParamGlasses_UTF8_FriendlyName
User-facing name of the glasses - UTF8.
Definition: types.h:679
constexpr iterator_traits< _InputIterator >::difference_type count(_InputIterator __first, _InputIterator __last, const _Tp &__value)

Reserve for Exclusive

Other operations require exclusive access before a client can perform them. Only one client can have exclusive access to glasses at a time. Principally, obtaining the current pose (t5GetGlassesPose()) and sending frames for rendering (t5SendFrameToGlasses()) require exclusive access.

Essentially, this is a process of repeatedly calling t5ReserveGlasses() until the function returns success. This can succeed even if glasses are not fully available (for example due to rebooting). Once the glasses are marked exclusive, then it is time to ensure the glasses are ready for exclusive operations.

330 err = t5ReserveGlasses(glassesHandle, "My Awesome Application");
331 if (err) {
332 printf("Error acquiring glasses '%s': %s\n", id, t5GetResultMessage(err));
333 return;
334 }
T5_EXPORT T5_Result t5ReserveGlasses(T5_Glasses glasses, const char *displayName)
Reserve glasses for exclusive operations by the client.

Ensure Ready for Exclusive

After a pair of glasses has been reserved, they need to be made fully ready for exclusive operations (e.g., getting the current glasses pose and sending frames) using t5EnsureGlassesReady(). This may return an error that retry is needed if the glasses are not yet ready. Upon success, the glasses are ready for exclusive operations.

345 // ENSURE GLASSES ARE READY
346 for (;;) {
347 err = t5EnsureGlassesReady(glassesHandle);
348 if (err) {
349 if (err == T5_ERROR_TRY_AGAIN) {
350 // A non-sample program would probably sleep here or retry on another frame pass.
351 continue;
352 }
353 printf("Error ensure glasses ready '%s': %s\n", id, t5GetResultMessage(err));
354 return;
355 }
356
357 printf("Glasses ready : '%s'\n", id);
358 break;
359 }
#define T5_ERROR_TRY_AGAIN
Target is not currently available.
Definition: errors.h:120
T5_EXPORT T5_Result t5EnsureGlassesReady(T5_Glasses glasses)
Ensure that reserved glasses are ready for exclusive operations.

Exclusive Operations

Once an exclusive connection has been established, you may perform exclusive operations such as t5GetGlassesPose() and t5SendFrameToGlasses().

173void getPoses(T5_Glasses glassesHandle) {
174 T5_Result err;
175 T5_GlassesPose pose;
176 T5_GameboardPose gameboardPoses[3];
177
178 printf("Getting some poses");
179
180 bool waiting = false;
181 for (int i = 0; i < 1000; i++) {
183 if (err) {
184 if (err == T5_ERROR_TRY_AGAIN) {
185 if (!waiting) {
186 printf("\nWaiting...");
187 fflush(stdout);
188 } else {
189 printf(".");
190 fflush(stdout);
191 }
192 SLEEP(100);
193 i--;
194 waiting = true;
195 continue;
196 } else {
197 printf("Failed to get pose : %s\n", t5GetResultMessage(err));
198 return;
199 }
200 }
201
202 uint16_t count = 3;
203 err = t5GetGameboardPoses(glassesHandle, gameboardPoses, &count);
204 if (err) {
205 if (err == T5_ERROR_TRY_AGAIN) {
206 if (!waiting) {
207 printf("\nWaiting for gameboard poses...");
208 fflush(stdout);
209 } else {
210 printf(".");
211 fflush(stdout);
212 }
213 SLEEP(100);
214 i--;
215 waiting = true;
216 continue;
217 } else {
218 printf("Failed to get gameboard poses : %s\n", t5GetResultMessage(err));
219 return;
220 }
221 }
222
223 waiting = false;
224
225 printf("\nGLASSES POSE : [%lu] (Board:%s) "
226 "[%6.4f, %6.4f, %6.4f] [%6.4f, %6.4f, %6.4f, %6.4f]",
227 pose.timestampNanos,
228 gameboardTypeString(pose.gameboardType),
229 pose.posGLS_STAGE.x,
230 pose.posGLS_STAGE.y,
231 pose.posGLS_STAGE.z,
232 pose.rotToGLS_STAGE.w,
233 pose.rotToGLS_STAGE.x,
234 pose.rotToGLS_STAGE.y,
235 pose.rotToGLS_STAGE.z);
236
237 printf("\nPRIMARY GAMEBOARD POSE : [%lu] (Board:%s) "
238 "[%6.4f, %6.4f, %6.4f] [%6.4f, %6.4f, %6.4f, %6.4f]",
239 gameboardPoses[0].timestampNanos,
240 gameboardTypeString(gameboardPoses[0].gameboardType),
241 gameboardPoses[0].posBOARD_STAGE.x,
242 gameboardPoses[0].posBOARD_STAGE.y,
243 gameboardPoses[0].posBOARD_STAGE.z,
244 gameboardPoses[0].rotToBOARD_STAGE.w,
245 gameboardPoses[0].rotToBOARD_STAGE.x,
246 gameboardPoses[0].rotToBOARD_STAGE.y,
247 gameboardPoses[0].rotToBOARD_STAGE.z);
248
249 SLEEP(16);
250 }
251
252 printf("\n");
253}
T5_EXPORT T5_Result t5GetGameboardPoses(T5_Glasses glasses, T5_GameboardPose *buffer, uint16_t *count)
Get the latest set of gameboard poses.
T5_EXPORT T5_Result t5GetGlassesPose(T5_Glasses glasses, T5_GlassesPoseUsage usage, T5_GlassesPose *pose)
Get the latest pose of the glasses.
@ kT5_GlassesPoseUsage_GlassesPresentation
The pose will be used to render images to be presented on the glasses.
Definition: types.h:335
Glasses pose information to be retrieved with t5GetGlassesPose()
Definition: types.h:358
uint64_t timestampNanos
The timestamp of the pose.
Definition: types.h:360
T5_GameboardType gameboardType
The type of gameboard visible for this pose.
Definition: types.h:371
T5_Quat rotToGLS_STAGE
The rotation that transforms points in the STAGE frame orientation to the GLS (glasses) frame orienta...
Definition: types.h:368
T5_Vec3 posGLS_STAGE
The position of the origin of the GLS (glasses) frame relative to the STAGE frame.
Definition: types.h:364
Gameboard pose information to be retrieved by enumerating the output of t5GetGameboardPoses()
Definition: types.h:392

Release Exclusive Access

At any point after glasses have been reserved by a client, they can be released so that another client can reserve them instead without needing to destroy the whole glasses object or context. Use t5ReleaseGlasses() to restore availability to other clients.

407 // RELEASE THE GLASSES
408 t5ReleaseGlasses(glassesHandle);
T5_EXPORT T5_Result t5ReleaseGlasses(T5_Glasses glasses)

Wand Stream

Information about wand events is not delivered separately for each connected wand, but is instead multiplexed into a glasses global stream. Stream events are one of the following (T5_WandStreamEventType):

Event Meaning
Connect Glasses have detected a wand has connected.
Disconnect Glasses have detected a wand disconnection.
Desync The wand stream has desynchronized, and the client may have missed one or more connection, disconnection or report packets.
Report A T5_WandReport is available detailing button, analog and positional data for a wand.

Simplistically, accessing the wand stream is done by configuring the stream (t5ConfigureWandStreamForGlasses()) and then repeatedly polling the stream (t5ReadWandStreamForGlasses()).

102 if (count > 0) {
103 // Enable wand stream
104 T5_WandStreamConfig config;
105 config.enabled = true;
106 err = t5ConfigureWandStreamForGlasses(glassesHandle, &config);
107 if (err) {
108 printf("Failed to enable stream : %s\n", t5GetResultMessage(err));
109 return;
110 }
111
112 T5_WandStreamEvent event;
113 for (int i = 0; i < 100; i++) {
114 err = t5ReadWandStreamForGlasses(glassesHandle, &event, 100);
115 if (err) {
116 if (err == T5_TIMEOUT) {
117 continue;
118 }
119
120 printf("Failed to read stream : %s\n", t5GetResultMessage(err));
121 return;
122 }
123
124 switch (event.type) {
126 printf("WAND EVENT : CONNECT [%u]\n", event.wandId);
127 break;
128
130 printf("WAND EVENT : DISCONNECT [%u]\n", event.wandId);
131 break;
132
134 printf("WAND EVENT : DESYNC [%u]\n", event.wandId);
135 break;
136
138 printf("WAND EVENT : REPORT [%u] %fx%f\n",
139 event.wandId,
140 event.report.stick.x,
141 event.report.stick.y);
142 break;
143 }
144 }
145
146 // Disable wand stream
147 config.enabled = false;
148 err = t5ConfigureWandStreamForGlasses(glassesHandle, &config);
149 if (err) {
150 printf("Failed to disable stream : %s\n", t5GetResultMessage(err));
151 return;
152 }
153 }
#define T5_TIMEOUT
Timeout.
Definition: errors.h:57
T5_EXPORT T5_Result t5ReadWandStreamForGlasses(T5_Glasses glasses, T5_WandStreamEvent *event, uint32_t timeoutMs)
Read from the wands event stream.
T5_EXPORT T5_Result t5ConfigureWandStreamForGlasses(T5_Glasses glasses, const T5_WandStreamConfig *config)
Configure the wand event stream.
@ kT5_WandStreamEventType_Connect
Wand connected.
Definition: types.h:575
@ kT5_WandStreamEventType_Disconnect
Wand disconnected.
Definition: types.h:578
@ kT5_WandStreamEventType_Report
Wand report (Pose, Buttons, Trigger, Stick, Battery)
Definition: types.h:584
@ kT5_WandStreamEventType_Desync
Stream has desynchronized.
Definition: types.h:581
Wand stream configuration.
Definition: types.h:566
bool enabled
Enable or disable the entire stream. True = enabled.
Definition: types.h:568
T5_Vec2 stick
Stick (X/Y) - Analog, Range [-1.0 - 1.0], 0 = Centered, 1.0 = Top/Right.
Definition: types.h:620
Represents an event from the wand stream.
Definition: types.h:658
T5_WandReport report
Report (Valid if type = kT5_WandStreamEventType_Report)
Definition: types.h:669
T5_WandStreamEventType type
Type of event.
Definition: types.h:663
T5_WandHandle wandId
Opaque identifier for the wand.
Definition: types.h:660