Tilt Five NDK
Getting Started : C++

Files

The following files need to be included in your project

File(s) Purpose
.hpp & .h files from the include directory C and 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 tiltfive::Error::kNoService and retry appropriately (assuming it's a transient condition).

A trivial example of handling this is shown below:

272// Convenience function to repeatedly call another function if it returns 'service unavailable'
273template <typename T>
274auto waitForService(Client& client, const std::function<tiltfive::Result<T>(Client& client)>& func)
276
277 bool waitingForService = false;
278 for (;;) {
279 auto result = func(client);
280 if (result) {
281 return result;
282 } else if (result.error() != tiltfive::Error::kNoService) {
283 return result.error();
284 }
285
286 std::cout << (waitingForService ? "." : "Waiting for service...") << std::flush;
287 waitingForService = true;
289 }
290}
@ kNoService
Service isn't connected.
ostream cout
void sleep_for(const chrono::duration< _Rep, _Period > &__rtime)
Templated return type with support for error conditions.
Definition: result.hpp:37
333 result = waitForService<void>(*client, printServiceVersion);
334 if (!result) {
335 std::cerr << "Failed to get service version : " << result << std::endl;
336 std::exit(EXIT_FAILURE);
337 }
ostream cerr
basic_ostream< _CharT, _Traits > & endl(basic_ostream< _CharT, _Traits > &__os)
248auto printServiceVersion(Client& client) -> tiltfive::Result<void> {
249 auto result = client->getServiceVersion();
250 if (!result) {
251 return result.error();
252 }
253
254 std::cout << "Service version : " << result << std::endl;
255 return tiltfive::kSuccess;
256}
static constexpr success_t kSuccess
Indicates 'success' for a Result<void> function.
Definition: result.hpp:324
Specialization of tiltfive::Result for functions with 'no return'.
Definition: result.hpp:199

Result Wrapper

To avoid using exceptions, most C++ functions return the tiltfive::Result type.

This class wraps the 'real' return type and adds additional error conditions. tiltfive::Result is truthy, so you should first check if the result is valid before using it, after that you dereference the tiltfive::Result to get the 'real' value.

An example of this is shown below:

auto result = fooCall();
if (!result) {
doSomethingWithTheError(result.error());
} else {
doSomethingWithTheResult(*result);
}

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_2.png

Create Client

311 // Create the client
312 auto client = tiltfive::obtainClient("com.tiltfive.test", "0.1.0", nullptr);
313 if (!client) {
314 std::cerr << "Failed to create client : " << client << std::endl;
315 std::exit(EXIT_FAILURE);
316 }
317 std::cout << "Obtained client : " << client << std::endl;
auto obtainClient(const std::string &applicationId, const std::string &applicationVersion, void *platformContext, const uint8_t sdkType=0) -> Result< std::shared_ptr< Client > >
Obtain an instance of the Tilt Five™ API client.

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 (tiltfive::Client::getServiceVersion()) and fixed physical parameters (tiltfive::Client::getGameboardSize()).

231auto printGameboardDimensions(Client& client) -> tiltfive::Result<void> {
232 auto result = client->getGameboardSize(kT5_GameboardType_LE);
233 if (!result) {
234 return result.error();
235 }
236
237 float width = result->viewableExtentPositiveX + result->viewableExtentNegativeX;
238 float length = result->viewableExtentPositiveY + result->viewableExtentNegativeY;
239 float height = result->viewableExtentPositiveZ;
240
241 std::cout << "LE Gameboard size : " << width << "m x " << length << "m x " << height << "m"
242 << std::endl;
243
244 return tiltfive::kSuccess;
245}
246
248auto printServiceVersion(Client& client) -> tiltfive::Result<void> {
249 auto result = client->getServiceVersion();
250 if (!result) {
251 return result.error();
252 }
253
254 std::cout << "Service version : " << result << std::endl;
255 return tiltfive::kSuccess;
256}
@ kT5_GameboardType_LE
An LE gameboard.
Definition: types.h:262

Enumerate & Select Glasses

44auto waitForGlasses(Client& client) -> tiltfive::Result<Glasses> {
45 std::cout << "Looking for glasses..." << std::flush;
46
47 // Loop until we find glasses
48 auto glassesList = client->listGlasses();
49 if (!glassesList) {
50 return glassesList.error();
51 }
52 while (glassesList->empty()) {
53 std::cout << "." << std::flush;
55
56 // Request a list of the available glasses
57 glassesList = client->listGlasses();
58 if (!glassesList) {
59 return glassesList.error();
60 }
61 }
62
63 // Print out the found glasses
64 for (auto& glassesInstance : *glassesList) {
65 std::cout << "Found : " << glassesInstance << std::endl;
66 }
67
68 // Return the first found glasses
69 return tiltfive::obtainGlasses(glassesList->front(), client);
70}
auto obtainGlasses(const std::string &identifier, const std::shared_ptr< Client > &client) -> Result< std::shared_ptr< Glasses > >
Obtain an instance of the Tilt Five™ Glasses.
basic_ostream< _CharT, _Traits > & flush(basic_ostream< _CharT, _Traits > &__os)

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 (tiltfive::Glasses::getIpd()) and user specified friendly name (tiltfive::Glasses::getFriendlyName()). Additionally, accessing the Wand Stream 'wand stream' does not require an exclusive connection.

152 // Get the friendly name for the glasses
153 // This is the name that's user set in the Tilt Five™ control panel.
154 auto friendlyName = glasses->getFriendlyName();
155 if (friendlyName) {
156 std::cout << "Obtained friendly name : " << friendlyName << std::endl;
157 } else if (friendlyName.error() == tiltfive::Error::kSettingUnknown) {
158 std::cerr << "Couldn't get friendly name : Service reports it's not set" << std::endl;
159 } else {
160 std::cerr << "Error obtaining friendly name : " << friendlyName << std::endl;
161 }
162
163 // Get the IPD for the glasses
164 // This is user set IPD in the Tilt Five™ control panel.
165 auto ipd = glasses->getIpd();
166 if (ipd) {
167 std::cout << "Obtained IPD : " << ipd << "m" << std::endl;
168 } else if (ipd.error() == tiltfive::Error::kSettingUnknown) {
169 std::cerr << "Couldn't get IPD : Service reports it's not set" << std::endl;
170 } else {
171 std::cerr << "Error obtaining IPD : " << ipd << std::endl;
172 return ipd.error();
173 }
@ kSettingUnknown
The requested param is unknown.

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 (tiltfive::Glasses::getLatestGlassesPose()) and sending frames for rendering (tiltfive::Glasses::sendFrame()) require exclusive access.

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

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 tiltfive::Glasses::ensureReady() . 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.

To automate the "reserve and ensure ready" process, use the GlassesConnectionHelper.

191 // Wait for exclusive glasses connection
192 auto connectionHelper = glasses->createConnectionHelper("Awesome game - Player 1");
193 auto connectionResult = connectionHelper->awaitConnection(10000_ms);
194 if (connectionResult) {
195 std::cout << "Glasses connected for exclusive use" << std::endl;
196 } else {
197 std::cerr << "Error connecting glasses for exclusive use : " << connectionResult
198 << std::endl;
199 return connectionResult.error();
200 }

Exclusive Operations

Once an exclusive connection has been established, you may perform exclusive operations such as tiltfive::Glasses::getLatestGlassesPose() and tiltfive::Glasses::sendFrame().

110auto readPoses(Glasses& glasses) -> tiltfive::Result<void> {
111 auto start = std::chrono::steady_clock::now();
112 do {
113 auto pose = glasses->getLatestGlassesPose(kT5_GlassesPoseUsage_GlassesPresentation);
114 if (!pose) {
115 if (pose.error() == tiltfive::Error::kTryAgain) {
116 std::cout << "\rPose unavailable - Is gameboard visible? ";
117 } else {
118 return pose.error();
119 }
120 } else {
121 std::cout << "\r" << pose;
122 }
123 } while ((std::chrono::steady_clock::now() - start) < 10000_ms);
124
125 std::cout << "\n";
126
127 start = std::chrono::steady_clock::now();
128 do {
129 auto poses = glasses->getLatestGameboardPoses();
130 if (!poses) {
131 if (poses.error() == tiltfive::Error::kTryAgain) {
132 std::cout << "\rGameboard poses unavailable - Is gameboard visible? "
133 " ";
134 } else {
135 return poses.error();
136 }
137 } else {
138 for (auto& pose : *poses) {
139 std::cout << "\r" << pose;
140 }
141 }
142 } while ((std::chrono::steady_clock::now() - start) < 10000_ms);
143
144 return tiltfive::kSuccess;
145}
@ kTryAgain
Target is temporarily unavailable.
@ kT5_GlassesPoseUsage_GlassesPresentation
The pose will be used to render images to be presented on the glasses.
Definition: types.h:335
size_t start() const

Release Exclusive Access

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

Destruction

tiltfive::Client objects that fall out of scope automatically clean up their own resources. Likewise, tiltfive::Glasses that fall out of scope have their resources release. No explicit release is required in either case.

Note that objects in the C++ binder maintain internal references as necessary. Therefore, even if you no longer have a reference to a tiltfive::Client for example, but do still have a reference tiltfive::Glasses the client will not be released until the glasses object goes out of scope. For further details see Object References below.

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 (tiltfive::Glasses::configureWandStream()) and then repeatedly polling the stream (tiltfive::Glasses::readWandStream()).

To simplify this process, you can use the WandStreamHelper to automate the process and de-multiplex the stream into abstract tiltfive::Wand objects.

Camera Image Stream

Access to the camera image stream requires both providing empty image buffers (tiltfive::Glasses::submitEmptyCamImageBuffer) and polling for filled image buffers (tiltfive::Glasses::getFilledCamImageBuffer).

The typical pattern should be to provide one or more buffers wrapped in a T5_CamImage wrapper (recommended minimum 3 for consistent framerate) via submitEmptyCamImageBuffer. Once provided, they will be filled automatically as images come in from the camera, and getFilledCamImageBuffer should be continually polled. After getFilledCamImageBuffer returns Success, the provided image buffer is valid to be used for image processing. Once processing is finished, the buffer can be resubmitted to the stream using submitEmptyCamImageBuffer again.

If receiving only the most recent image is desired instead, getFilledCamImageBuffer can be continually polled until an error is returned, and the most recent successful return will be the most recent image place into an empty buffer.

Continual polling of the filled image buffers and resubmission of empty buffers is expected to prevent filled buffers from becoming stale as new images come in from the headset.

When finished with the stream, all previously provided buffers should be cancelled via cancelCamImageBuffer before the buffer memory is freed.

Object References

Objects in the C++ binder maintain internal references std::shared_ptr. The relationships are shown below. The direction of the arrow denotes a held reference. For example, a tiltfive::Wand object holds a reference to a tiltfive::WandStreamHelper, which in turn holds a reference to a tiltfive::Glasses and finally to a tiltfive::Client. Dashed lines represent std::weak_ptr.

dot_inline_dotgraph_3.png

Helper Classes

All of the functionality of the glasses can be accessed using only tiltfive::Client and tiltfive::Glasses, however many of the calls are blocking and/or require polling. To simplify access to the functionality, there are several additional helper classes to automate these operations.

Helper Purpose
tiltfive::WandStreamHelper Monitor the wand event stream, and abstract access to tiltfive::Wand objects
tiltfive::GlassesConnectionHelper Automate the exclusive connection process which requires repeated calling of tiltfive::Glasses::reserve()
tiltfive::ParamChangeHelper Automate polling for changes to T5_ParamGlasses / T5_ParamSys and invoke callbacks on a tiltfive::ParamChangeListener subclasses
dot_inline_dotgraph_4.png

Using WandStreamHelper

Simplistically, accessing the wand stream is done by configuring the stream (tiltfive::Glasses::configureWandStream()) and then repeatedly polling the stream (tiltfive::Glasses::readWandStream()).

To simplify this process, you can use a tiltfive::WandStreamHelper to automate the process and de-multiplex the stream into abstract tiltfive::Wand objects.

The tiltfive::WandStreamHelper automatically configures the stream to match the lifecycle of tiltfive::Wand objects - when no references are held, the stream is automatically stopped and restarted as needed.

Using GlassesConnectionHelper

Connecting the glasses is essentially a process of repeatedly calling tiltfive::Glasses::reserve() until an exclusive connection is established. If the glasses are not fully available (for example due to rebooting), you may receive a reservation for the glasses instead. To automate this process use a tiltfive::GlassesConnectionHelper.

or

(new in 1.2.0) After the GlassesConnectionHelper is destroyed, the Glasses object will remain in its current connection state. (This is a change from NDK 1.1.0, where the Glasses would be released on GlassesConnectionHelper destruction). The Glasses will still be automatically released when the Glasses object itself is destroyed.

Using ParamChangeHelper

Some of the parameters may change during operation (for example, the user want to adjust the IPD while using the glasses). To automatically detect changes and trigger a callback, use a tiltfive::ParamChangeHelper.

At this point you'll be able to receive callbacks for system-wide changes via tiltfive::ParamChangeListener::onSysParamChanged(). To also receive parameter changes for individual glasses: