Simulated Reality SDK 7500c78d v1.30.2.51085 2024-04-26T11:23:03Z
Stable
Example C++ & OpenGL

1
10// External dependencies
11#include <GL/glew.h>
12#include <GLFW/glfw3.h>
13GLFWwindow* window;
14#include <glm/glm.hpp>
15#include <glm/gtc/matrix_transform.hpp>
16#include <thread>
17#include <chrono>
18
19#define GLFW_EXPOSE_NATIVE_WIN32
20#define GLFW_EXPOSE_NATIVE_WGL
21#include "glfw3native.h"
22
23#include <shellscalingapi.h>
24#undef near
25#undef far
26
27// Internal dependencies
28#include "pyramid.h"
29#include "shader.h"
30#include "sr/weaver/glweaver.h"
31
32// Simulated Reality includes
33#include "sr/types.h"
41
42#define WEAVING_ENABLED
43
44using namespace std::chrono_literals;
45
46// Flag to indicate that context is no longer valid
47bool contextValid = false;
48
49bool initializeSrObjects(); // forward declaration
50
51// User implementation of the SystemEventListener interface
52class SystemEventMonitor : public SR::SystemEventListener {
53public:
54 // Ensures SystemEventStream is cleaned up when Listener object is out of scope
56
57 // The accept function can process the system event data as soon as it becomes available
58 virtual void accept(const SR::SystemEvent& frame) override {
59 switch (frame.eventType) {
61 {
62 std::cout
63 << "ContextInvalid event received: "
64 << frame.time << " "
65 << frame.message << "\n";
66
67 contextValid = false;
68
69 std::thread([]() {
70 initializeSrObjects();
71 }).detach();
72
73 return;
74 }
75 break;
76 default:
77 std::cout << "Unknown event type" << std::endl;
78 break;
79 }
80 }
81
82};
83
84// My SR::EyePairListener that stores the last tracked eye positions
85class MyEyes : public SR::EyePairListener {
86private:
88public:
89 glm::vec3 left, right;
90 MyEyes(SR::EyeTracker* tracker) : left(-30, 0, 600), right(30, 0, 600) {
91 // Open a stream between tracker and this class
92 stream.set(tracker->openEyePairStream(this));
93 }
94 // Called by the tracker for each tracked eye pair
95 virtual void accept(const SR_eyePair& eyePair) override
96 {
97 // Remember the eye positions
98 left = glm::vec3(eyePair.left.x, eyePair.left.y, eyePair.left.z);
99 right = glm::vec3(eyePair.right.x, eyePair.right.y, eyePair.right.z);
100 }
101};
102
103// My SR::HandPoseListener that stores the last tracked index finger position
104class MyFinger : public SR::HandPoseListener {
105private:
107public:
108 glm::vec3 position;
109 MyFinger(SR::HandTracker* tracker) : position(0, 0, 0) {
110 // Open a stream between tracker and this class
111 stream.set(tracker->openHandPoseStream(this));
112 }
113 // Called by the tracker for each tracked hand pose
114 virtual void accept(const SR_handPose& handPose) override
115 {
116 // Remember the fingertip position
117 position = glm::vec3(handPose.index.tip.x, handPose.index.tip.y, handPose.index.tip.z);
118 }
119};
120
121// Create SRContext
122SR::SRContext* context = nullptr;
123SR::PredictingGLWeaver* weaver = nullptr;
124std::mutex constructNewContextMutex;
125MyEyes* eyes = nullptr;
126SystemEventMonitor* listener = nullptr;
127
128bool createSrContext() {
129 if (context != nullptr) {
130 delete context;
131 context = nullptr;
132 }
133 while (context == nullptr && contextValid == false) {
134 try {
135 context = new SR::SRContext();
136 return true;
137 }
139 std::cout << "Server not available, trying again in 0.5 second" << std::endl;
140 std::this_thread::sleep_for(500ms);
141 }
142 }
143 return false;
144}
145
146bool initializeSrObjects() {
147 std::lock_guard<std::mutex> lock(constructNewContextMutex);
148 // weaver needs to be deleted before deleting SRContext as weaver is using the context, but this function does not reconstruct weaver. Construct weaver wherever is needed
149
150 if (eyes != nullptr) {
151 delete eyes;
152 eyes = nullptr;
153 }
154 if (listener != nullptr) {
155 delete listener;
156 listener = nullptr;
157 }
158
159 while (weaver != nullptr) {
160 //wait for it
161 std::this_thread::sleep_for(10ms);
162 }
163 // constructing context
164 if (createSrContext() == false) {
165 return false;
166 }
167 // constructing EyePairListener
168 eyes = new MyEyes(SR::EyeTracker::create(*context));
169
170 SR::SystemSense* systemSense = SR::SystemSense::create(*context);
171 listener = new SystemEventMonitor();
172 // set systemEvent listener to the newly constructed systemsense
173 listener->stream.set(systemSense->openSystemEventStream(listener));
174
175 context->initialize();
176 contextValid = true;
177 return true;
178}
179
180glm::mat4 CalculateModel()
181{
182 const float size = 20.0; // Size of the object in mm
183 using namespace std::chrono;
184 auto now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
185 const int ms_per_revolution = 5000;
186 float angle = float((2 * 3.1415 / ms_per_revolution) * (now % ms_per_revolution));
187
188 glm::mat4 Model = glm::mat4();
189 Model = scale(Model, glm::vec3(size, size, size)); //scale by size
190 Model = translate(Model, glm::vec3(0, 1, 0)); //translate bottom face to origin
191 Model = rotate(Model, angle, glm::vec3(0, 1, 0)); //rotate by angle
192
193 return Model;
194}
195
196float screenWidth_mm = 697.0;
197float screenHeight_mm = 394.0;
198
199glm::mat4 CalculateProjection(glm::vec3 &eye)
200{
201 // Implementation of: Kooima, Robert. "Generalized perspective projection." J. Sch. Electron. Eng. Comput. Sci (2009).
202
203 const float near = 1.0;
204 const float far = 10000.0;
205 const glm::vec3 pa(-screenWidth_mm / 2, screenHeight_mm / 2, 0);
206 const glm::vec3 pb(screenWidth_mm / 2, screenHeight_mm / 2, 0);
207 const glm::vec3 pc(-screenWidth_mm / 2, -screenHeight_mm / 2, 0);
208
209 const glm::vec3 vr(1, 0, 0);
210 const glm::vec3 vu(0, 1, 0);
211 const glm::vec3 vn(0, 0, 1);
212
213 // Compute the screen corner vectors.
214 glm::vec3 va = pa - eye;
215 glm::vec3 vb = pb - eye;
216 glm::vec3 vc = pc - eye;
217
218 // Find the distance from the eye to screen plane.
219 float distance = -dot(va, vn);
220
221 // Find the extent of the perpendicular projection.
222 float l = dot(vr, va) * near / distance;
223 float r = dot(vr, vb) * near / distance;
224 float b = dot(vu, vc) * near / distance;
225 float t = dot(vu, va) * near / distance;
226
227 // Load the perpendicular projection
228 glm::mat4 _frustum = glm::frustum(l, r, b, t, near, far);
229
230 // Rotate the projection to be non-perpendicular
231 glm::mat4 M = {
232 vr[0], vu[0], vn[0], 0,
233 vr[1], vu[1], vn[1], 0,
234 vr[2], vu[2], vn[2], 0,
235 0, 0, 0, 1.0,
236 }; //(formatting is transposed from the paper!)
237
238 // Move the apex of the frustum to the origin.
239 glm::mat4 _translate = translate(glm::mat4(), -eye);
240
241 // Combine
242 return _frustum * M * _translate;
243}
244
245void RenderScene(GLuint MatrixID, const size_t renderWidth, const size_t renderHeight, glm::vec3 leftEye, glm::vec3 rightEye, Pyramid& pyramid) {
247 // Update Model to put object on the finger
248 glm::mat4 View = glm::mat4();
249 glm::mat4 Model = CalculateModel();
250
251 // Set projection for left rendering
252 glm::mat4 Projection = CalculateProjection(leftEye);
253 glm::mat4 MVP = Projection * View * Model;
254 glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
255
256 // Set viewport to the left half
257 glViewport(0, 0, renderWidth, renderHeight);
258
259 // Render the scene for the left eye
260 pyramid.draw();
261
262 // Set projection for right rendering
263 Projection = CalculateProjection(rightEye);
264 MVP = Projection * View * Model;
265 glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
266
267 // Set viewport to the right half
268 glViewport(renderWidth, 0, renderWidth, renderHeight);
269
270 // Render the scene for the right eye
271 pyramid.draw();
273}
274
275// Returns true if function completed succesfully, returns false otherwise
276bool ensureWindowFitsToMonitor() {
277 // Get monitor that the window is currently located on.
278 HMONITOR windowMonitor = MonitorFromWindow(glfwGetWin32Window(window), MONITOR_DEFAULTTONEAREST);
279
280 // If no monitor handle was returned (no monitor may be attached), don't change window and return false
281 if (!windowMonitor) {
282 return false;
283 }
284
285 // Get monitor rectangle in the virtual screen
286 MONITORINFO monitorInfo;
287 ZeroMemory(&monitorInfo, sizeof(monitorInfo));
288 monitorInfo.cbSize = sizeof(monitorInfo);
289 GetMonitorInfoA(windowMonitor, &monitorInfo);
290
291 // Get current window rectangle
292 RECT windowRect;
293 GetWindowRect(glfwGetWin32Window(window), &windowRect);
294
295 // If the monitor rectangle is different from the current window rectangle...
296 if (windowRect.left != monitorInfo.rcMonitor.left ||
297 windowRect.right != monitorInfo.rcMonitor.right ||
298 windowRect.top != monitorInfo.rcMonitor.top ||
299 windowRect.bottom != monitorInfo.rcMonitor.bottom) {
300 //...set the window rectangle to fit the monitor rectangle
301 glfwSetWindowPos(window,
302 monitorInfo.rcMonitor.left,
303 monitorInfo.rcMonitor.top
304 );
305 glfwSetWindowSize(window,
306 monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left,
307 monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top
308 );
309 }
310
311 // Return true, as function has completed succesfully
312 return true;
313}
314
315bool windowFitsToMonitor = false;
316
317void windowMoveCallback(GLFWwindow* window, int xPos, int yPos) {
318 windowFitsToMonitor = false;
319}
320
321void monitorConfigurationCallback(GLFWmonitor* monitor, int event) {
322 windowFitsToMonitor = false;
323}
324
325int main(void)
326{
327 // Ensure the application receives unscaled display metrics
328 SetProcessDpiAwareness(PROCESS_DPI_AWARENESS::PROCESS_PER_MONITOR_DPI_AWARE);
329
330 // Initialise GLFW
331 if (!glfwInit())
332 {
333 fprintf(stderr, "Failed to initialize GLFW\n");
334 return -1;
335 }
336
337 glfwWindowHint(GLFW_SAMPLES, 4);
338 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
339 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
340 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
341 glfwWindowHint(GLFW_AUTO_ICONIFY, GL_FALSE);
342 glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
343
344 // Open a window and create its OpenGL context
345 GLFWmonitor* monitor = glfwGetPrimaryMonitor();
346 const GLFWvidmode* mode = glfwGetVideoMode(monitor);
347 window = glfwCreateWindow(mode->width, mode->height, "Simulated Reality - Cube demo", NULL, NULL);
348 if (window == NULL) {
349 fprintf(stderr, "Failed to open GLFW window.\n");
350 glfwTerminate();
351 return -1;
352 }
353 windowFitsToMonitor = true;
354 glfwMakeContextCurrent(window);
355 glfwSwapInterval(1);
356
357 // Initialize GLEW
358 glewExperimental = true; // Needed for core profile
359 if (glewInit() != GLEW_OK) {
360 fprintf(stderr, "Failed to initialize GLEW\n");
361 glfwTerminate();
362 return -1;
363 }
364
365 // Ensure we can capture the escape key being pressed below
366 glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);
367 glfwSetWindowPosCallback(window, windowMoveCallback);
368 glfwSetMonitorCallback(monitorConfigurationCallback);
369
370 glClearColor(0.5f, 0.5f, 0.5f, 0.0);
371 glEnable(GL_DEPTH_TEST);
372 glDepthFunc(GL_LESS);
373 GLuint programID = loadBasicShaders();
374 GLuint MatrixID = glGetUniformLocation(programID, "MVP");
375
377 initializeSrObjects();
378 MyFinger finger(SR::HandTracker::create(*context));
379 SR::Screen* screen = SR::Screen::create(*context);
381
382 glUseProgram(programID);
383
384 // Get the screen width and height in millimeters
385 int screenWidth_mm_i, screenHeight_mm_i;
386 glfwGetMonitorPhysicalSize(monitor, &screenWidth_mm_i, &screenHeight_mm_i);
387 screenWidth_mm = screenWidth_mm_i;
388 screenHeight_mm = screenHeight_mm_i;
389
390 //Allocate VAO for pyramid object
391 Pyramid pyramid;
392
393#ifdef WEAVING_ENABLED
394 // Render to lower resolution than native display resolution because three-dimensional image won't convey any more detail and we can save processing power.
395 // The weaved image will be native display resolution.
396 // Full-HD resolution for each view is ideal for 4K displays.
397 const size_t renderWidth = screen->getPhysicalResolutionWidth() / 2;
398 const size_t renderHeight = screen->getPhysicalResolutionHeight() / 2;
399#else
400 // Render to half of the resolution that the display is presenting itself as.
401 // Devices like the SR Development Kit present themselves as a display with 7680 x 2160 resolution to allow for rendering at 3840 x 2160 for each view.
402 // This is ideal for 8K displays like it.
403 const size_t renderWidth = mode->width * 0.5f;
404 const size_t renderHeight = mode->height;
405#endif
406
407#ifdef WEAVING_ENABLED
409 weaver = new SR::PredictingGLWeaver(*context, renderWidth * 2, renderHeight, glfwGetWin32Window(window));
410 // set latency for the weaver prediction
411 weaver->setLatencyInFrames(2);
412
413 context->initialize();
415#endif
416
417 do {
418#ifdef WEAVING_ENABLED
420 // Draw to weaver with application specific shaders
421 if (contextValid && weaver != nullptr) {
422 glBindFramebuffer(GL_FRAMEBUFFER, weaver->getFrameBuffer());
423 }
424 else {
425 glBindFramebuffer(GL_FRAMEBUFFER, 0); //Start rendering to the display
426 }
428#endif
429 glUseProgram(programID);
430 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
431 if (contextValid) {
432 RenderScene(MatrixID, renderWidth, renderHeight, eyes->left, eyes->right, pyramid);
433 }
434 else {
435 RenderScene(MatrixID, renderWidth, renderHeight, { -30, 0, 600 }, { 30, 0, 600 }, pyramid);
436 }
437
438#ifdef WEAVING_ENABLED
439 int windowWidth, windowHeight;
440 glfwGetFramebufferSize(window, &windowWidth, &windowHeight);
442 glBindFramebuffer(GL_FRAMEBUFFER, 0); //Start rendering to the display
443 // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); does not need to be called because weaving overwrites each pixel on the backbuffer
444 glViewport(0, 0, windowWidth, windowHeight);
445
446 // For weaving, the center-point between both eyes is used.
447 // It should be converted from millimeters to centimeters
448 if (contextValid && weaver != nullptr) {
449 weaver->weave((unsigned int)windowWidth, (unsigned int)windowHeight, 0, 0);
450 }
452#endif
453
454 // Swap buffers
455 glfwSwapBuffers(window);
456 glfwPollEvents();
457
458 if (!windowFitsToMonitor) {
459 windowFitsToMonitor = ensureWindowFitsToMonitor();
460 }
461
462#ifdef WEAVING_ENABLED
463 // initialize weaver if it was deleted before
464 if (contextValid && weaver == nullptr) {
466 weaver = new SR::PredictingGLWeaver(*context, renderWidth * 2, renderHeight, glfwGetWin32Window(window));
467 // set latency for the weaver prediction
468 weaver->setLatencyInFrames(2);
469
470 context->initialize();
472 }
473
474 // delete weaver if the SRContext is not valid
475 if (contextValid == false && weaver != nullptr) {
476 delete weaver;
477 weaver = nullptr;
478 }
479#endif
480
481 } while (
482 glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS
483 && glfwWindowShouldClose(window) == 0
484 );
485
486#ifdef WEAVING_ENABLED
487 // Delete weaver GL resources before OpenGL context is deleted
488 if (weaver != nullptr) {
489 delete weaver;
490 weaver = nullptr;
491 }
492#endif
493
494 glDeleteProgram(programID);
495
496 glfwTerminate();
497
498 if (contextValid == false) {
499 contextValid = true;
500 std::this_thread::sleep_for(3000ms);
501 }
502}
void * SRContext
Definition: admin_c.h:15
Interface for listening to SR_eyePair updates.
Definition: eyepairlistener.h:20
virtual void accept(const SR_eyePair &frame)=0
Accept an SR_eyePair frame.
Sense class which provides face tracking functionality to the SR system.
Definition: eyetracker.h:40
static EyeTracker * create(SRContext &context)
Creates a functional EyeTracker instance.
virtual std::shared_ptr< EyePairStream > openEyePairStream(EyePairListener *listener)=0
Creates a EyePairStream for listener to be connected to.
GLuint getFrameBuffer()
Returns the buffer that will be used to create a weaved imaged. This buffer expects a side-by-side im...
void weave(unsigned int width, unsigned int height)
Can be called to render a weaved image of a certain size to the currently bound framebuffer.
Interface for listening to SR_handPose updates.
Definition: handposelistener.h:39
virtual void accept(const SR_handPose &handPose)=0
Accept an SR_handPose frame.
Sense class which provides hand tracking functionality to the SR system.
Definition: handtracker.h:46
virtual std::shared_ptr< HandPoseStream > openHandPoseStream(HandPoseListener *listener)=0
Creates a HandPoseStream for listener to be connected to.
static HandTracker * create(SRContext &context)
Creates a functional HandTracker instance.
Template class to wrap data stream to a listener object.
Definition: inputstream.h:25
void set(std::shared_ptr< StreamType > input)
Must be set to allow proper cleanup of internal stream.
Definition: inputstream.h:53
Definition: glweaver.h:152
void setLatencyInFrames(uint64_t latencyInFrames)
Set the latency to match the expected duration of the full rendering pipeline in number of frames....
Maintains WorldObject and Sense objects during the application lifetime.
Definition: srcontext.h:80
void initialize()
Initialize all senses.
Class of WorldObject representing the screen in real space.
Definition: screen.h:39
const int getPhysicalResolutionWidth()
Get the horizontal native resolution of the physical screen itself.
static Screen * create(SRContext &context)
Creates an instance of a Screen class.
const int getPhysicalResolutionHeight()
Get the vertical native resolution of the physical screen itself.
Class of Exception which indicates that a SR Service was not available to connect with.
Definition: exception.h:101
Interface for listening to SR_systemEvent updates.
Definition: systemeventlistener.h:30
virtual void accept(const SystemEvent &frame)=0
Accept an SR_systemEvent frame.
Class containing dynamic-length event messages.
Definition: systemevent.h:65
uint64_t time
Time since epoch in microseconds.
Definition: systemevent.h:67
SR_eventType eventType
Type of the event.
Definition: systemevent.h:68
std::string message
Full description of the event.
Definition: systemevent.h:69
Sense class which shares information about the SR system throughout the SR system and to applications...
Definition: systemsense.h:41
static SystemSense * create(SRContext &context)
Creates a functional SystemSense instance.
virtual std::shared_ptr< SystemEventStream > openSystemEventStream(SystemEventListener *listener)=0
Creates a SystemEventStream for listener to be connected to.
unsigned int GLuint
Definition: glweaver.h:28
C-compatible struct containing the position of two eyes.
Definition: eyepair.h:28
SR_point3d left
Absolute position of left eye in millimeter.
Definition: eyepair.h:34
SR_point3d right
Absolute position of right eye in millimeter.
Definition: eyepair.h:35
C-compatible struct containing the pose of a hand.
Definition: handpose.h:124
SR_finger index
Index finger.
Definition: handpose.h:136
@ ContextInvalid
Context needs to be re-initialized for the application to recover.
Definition: systemevent.h:19
SR_point3d tip
distal alias
Definition: handpose.h:64
double y
Second value in the 3d vector.
Definition: types.h:85
double z
Third value in the 3d vector.
Definition: types.h:86
double x
First value in the 3d vector.
Definition: types.h:84