aboutsummaryrefslogtreecommitdiffstats
path: root/support/hololens/ServoApp/ImmersiveMain.cpp
blob: 26b8ea5640642a54b52e5eaa2e1d9da622eefa70 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

#include "pch.h"
#include "ImmersiveMain.h"
#include "Common/DirectXHelper.h"

#include <windows.graphics.directx.direct3d11.interop.h>

using namespace Immersive;
using namespace concurrency;
using namespace Microsoft::WRL;
using namespace std::placeholders;
using namespace winrt::Windows::Foundation::Numerics;
using namespace winrt::Windows::Gaming::Input;
using namespace winrt::Windows::Graphics::Holographic;
using namespace winrt::Windows::Graphics::DirectX::Direct3D11;
using namespace winrt::Windows::Perception::Spatial;
using namespace winrt::Windows::UI::Input::Spatial;

// Loads and initializes application assets when the application is loaded.
ImmersiveMain::ImmersiveMain(
    std::shared_ptr<DX::DeviceResources> const &deviceResources)
    : m_deviceResources(deviceResources) {
  // Register to be notified if the device is lost or recreated.
  m_deviceResources->RegisterDeviceNotify(this);

  // If connected, a game controller can also be used for input.
  m_gamepadAddedEventToken =
      Gamepad::GamepadAdded(bind(&ImmersiveMain::OnGamepadAdded, this, _1, _2));
  m_gamepadRemovedEventToken = Gamepad::GamepadRemoved(
      bind(&ImmersiveMain::OnGamepadRemoved, this, _1, _2));

  for (Gamepad const &gamepad : Gamepad::Gamepads()) {
    OnGamepadAdded(nullptr, gamepad);
  }

  m_canGetHolographicDisplayForCamera =
      winrt::Windows::Foundation::Metadata::ApiInformation::IsPropertyPresent(
          L"Windows.Graphics.Holographic.HolographicCamera", L"Display");
  m_canGetDefaultHolographicDisplay =
      winrt::Windows::Foundation::Metadata::ApiInformation::IsMethodPresent(
          L"Windows.Graphics.Holographic.HolographicDisplay", L"GetDefault");
  m_canCommitDirect3D11DepthBuffer =
      winrt::Windows::Foundation::Metadata::ApiInformation::IsMethodPresent(
          L"Windows.Graphics.Holographic.HolographicCameraRenderingParameters",
          L"CommitDirect3D11DepthBuffer");

  if (m_canGetDefaultHolographicDisplay) {
    // Subscribe for notifications about changes to the state of the default
    // HolographicDisplay and its SpatialLocator.
    m_holographicDisplayIsAvailableChangedEventToken =
        HolographicSpace::IsAvailableChanged(
            bind(&ImmersiveMain::OnHolographicDisplayIsAvailableChanged, this,
                 _1, _2));
  }

  // Acquire the current state of the default HolographicDisplay and its
  // SpatialLocator.
  OnHolographicDisplayIsAvailableChanged(nullptr, nullptr);
}

void ImmersiveMain::SetHolographicSpace(
    HolographicSpace const &holographicSpace) {
  UnregisterHolographicEventHandlers();

  m_holographicSpace = holographicSpace;

  //
  // TODO: Add code here to initialize your holographic content.
  //

#ifdef DRAW_SAMPLE_CONTENT
  // Initialize the sample hologram.
  m_spinningCubeRenderer =
      std::make_unique<SpinningCubeRenderer>(m_deviceResources);
  m_spatialInputHandler = std::make_unique<SpatialInputHandler>();
#endif

  // Respond to camera added events by creating any resources that are specific
  // to that camera, such as the back buffer render target view.
  // When we add an event handler for CameraAdded, the API layer will avoid
  // putting the new camera in new HolographicFrames until we complete the
  // deferral we created for that handler, or return from the handler without
  // creating a deferral. This allows the app to take more than one frame to
  // finish creating resources and loading assets for the new holographic
  // camera. This function should be registered before the app creates any
  // HolographicFrames.
  m_cameraAddedToken = m_holographicSpace.CameraAdded(
      std::bind(&ImmersiveMain::OnCameraAdded, this, _1, _2));

  // Respond to camera removed events by releasing resources that were created
  // for that camera. When the app receives a CameraRemoved event, it releases
  // all references to the back buffer right away. This includes render target
  // views, Direct2D target bitmaps, and so on. The app must also ensure that
  // the back buffer is not attached as a render target, as shown in
  // DeviceResources::ReleaseResourcesForBackBuffer.
  m_cameraRemovedToken = m_holographicSpace.CameraRemoved(
      std::bind(&ImmersiveMain::OnCameraRemoved, this, _1, _2));

  // Notes on spatial tracking APIs:
  // * Stationary reference frames are designed to provide a best-fit position
  // relative to the
  //   overall space. Individual positions within that reference frame are
  //   allowed to drift slightly as the device learns more about the
  //   environment.
  // * When precise placement of individual holograms is required, a
  // SpatialAnchor should be used to
  //   anchor the individual hologram to a position in the real world - for
  //   example, a point the user indicates to be of special interest. Anchor
  //   positions do not drift, but can be corrected; the anchor will use the
  //   corrected position starting in the next frame after the correction has
  //   occurred.
}

void ImmersiveMain::UnregisterHolographicEventHandlers() {
  if (m_holographicSpace != nullptr) {
    // Clear previous event registrations.
    m_holographicSpace.CameraAdded(m_cameraAddedToken);
    m_cameraAddedToken = {};
    m_holographicSpace.CameraRemoved(m_cameraRemovedToken);
    m_cameraRemovedToken = {};
  }

  if (m_spatialLocator != nullptr) {
    m_spatialLocator.LocatabilityChanged(m_locatabilityChangedToken);
  }
}

ImmersiveMain::~ImmersiveMain() {
  // Deregister device notification.
  m_deviceResources->RegisterDeviceNotify(nullptr);

  UnregisterHolographicEventHandlers();

  Gamepad::GamepadAdded(m_gamepadAddedEventToken);
  Gamepad::GamepadRemoved(m_gamepadRemovedEventToken);
  HolographicSpace::IsAvailableChanged(
      m_holographicDisplayIsAvailableChangedEventToken);
}

// Updates the application state once per frame.
HolographicFrame ImmersiveMain::Update() {
  // Before doing the timer update, there is some work to do per-frame
  // to maintain holographic rendering. First, we will get information
  // about the current frame.

  // The HolographicFrame has information that the app needs in order
  // to update and render the current frame. The app begins each new
  // frame by calling CreateNextFrame.
  HolographicFrame holographicFrame = m_holographicSpace.CreateNextFrame();

  // Get a prediction of where holographic cameras will be when this frame
  // is presented.
  HolographicFramePrediction prediction = holographicFrame.CurrentPrediction();

  // Back buffers can change from frame to frame. Validate each buffer, and
  // recreate resource views and depth buffers as needed.
  m_deviceResources->EnsureCameraResources(holographicFrame, prediction);

#ifdef DRAW_SAMPLE_CONTENT
  if (m_stationaryReferenceFrame != nullptr) {
    // Check for new input state since the last frame.
    for (GamepadWithButtonState &gamepadWithButtonState : m_gamepads) {
      bool buttonDownThisUpdate =
          ((gamepadWithButtonState.gamepad.GetCurrentReading().Buttons &
            GamepadButtons::A) == GamepadButtons::A);
      if (buttonDownThisUpdate &&
          !gamepadWithButtonState.buttonAWasPressedLastFrame) {
        m_pointerPressed = true;
      }
      gamepadWithButtonState.buttonAWasPressedLastFrame = buttonDownThisUpdate;
    }

    SpatialInteractionSourceState pointerState =
        m_spatialInputHandler->CheckForInput();
    SpatialPointerPose pose = nullptr;
    if (pointerState != nullptr) {
      pose = pointerState.TryGetPointerPose(
          m_stationaryReferenceFrame.CoordinateSystem());
    } else if (m_pointerPressed) {
      pose = SpatialPointerPose::TryGetAtTimestamp(
          m_stationaryReferenceFrame.CoordinateSystem(),
          prediction.Timestamp());
    }
    m_pointerPressed = false;

    // When a Pressed gesture is detected, the sample hologram will be
    // repositioned two meters in front of the user.
    m_spinningCubeRenderer->PositionHologram(pose);
  }
#endif

  m_timer.Tick([this]() {
  //
  // TODO: Update scene objects.
  //
  // Put time-based updates here. By default this code will run once per frame,
  // but if you change the StepTimer to use a fixed time step this code will
  // run as many times as needed to get to the current step.
  //

#ifdef DRAW_SAMPLE_CONTENT
    m_spinningCubeRenderer->Update(m_timer);
#endif
  });

  if (!m_canCommitDirect3D11DepthBuffer) {
    // On versions of the platform that do not support the
    // CommitDirect3D11DepthBuffer API, we can control image stabilization by
    // setting a focus point with optional plane normal and velocity.
    for (HolographicCameraPose const &cameraPose : prediction.CameraPoses()) {
#ifdef DRAW_SAMPLE_CONTENT
      // The HolographicCameraRenderingParameters class provides access to set
      // the image stabilization parameters.
      HolographicCameraRenderingParameters renderingParameters =
          holographicFrame.GetRenderingParameters(cameraPose);

      // SetFocusPoint informs the system about a specific point in your scene
      // to prioritize for image stabilization. The focus point is set
      // independently for each holographic camera. When setting the focus
      // point, put it on or near content that the user is looking at. In this
      // example, we put the focus point at the center of the sample hologram.
      // You can also set the relative velocity and facing of the stabilization
      // plane using overloads of this method.
      if (m_stationaryReferenceFrame != nullptr) {
        renderingParameters.SetFocusPoint(
            m_stationaryReferenceFrame.CoordinateSystem(),
            m_spinningCubeRenderer->GetPosition());
      }
#endif
    }
  }

  // The holographic frame will be used to get up-to-date view and projection
  // matrices and to present the swap chain.
  return holographicFrame;
}

// Renders the current frame to each holographic camera, according to the
// current application and spatial positioning state. Returns true if the
// frame was rendered to at least one camera.
bool ImmersiveMain::Render(HolographicFrame const &holographicFrame) {
  // Don't try to render anything before the first Update.
  if (m_timer.GetFrameCount() == 0) {
    return false;
  }

  //
  // TODO: Add code for pre-pass rendering here.
  //
  // Take care of any tasks that are not specific to an individual holographic
  // camera. This includes anything that doesn't need the final view or
  // projection matrix, such as lighting maps.
  //

  // Lock the set of holographic camera resources, then draw to each camera
  // in this frame.
  return m_deviceResources->UseHolographicCameraResources<bool>(
      [this,
       holographicFrame](std::map<UINT32, std::unique_ptr<DX::CameraResources>>
                             &cameraResourceMap) {
        // Up-to-date frame predictions enhance the effectiveness of image
        // stablization and allow more accurate positioning of holograms.
        holographicFrame.UpdateCurrentPrediction();
        HolographicFramePrediction prediction =
            holographicFrame.CurrentPrediction();

        bool atLeastOneCameraRendered = false;
        for (HolographicCameraPose const &cameraPose :
             prediction.CameraPoses()) {
          // This represents the device-based resources for a HolographicCamera.
          DX::CameraResources *pCameraResources =
              cameraResourceMap[cameraPose.HolographicCamera().Id()].get();

          // Get the device context.
          const auto context = m_deviceResources->GetD3DDeviceContext();
          const auto depthStencilView = pCameraResources->GetDepthStencilView();

          // Set render targets to the current holographic camera.
          ID3D11RenderTargetView *const targets[1] = {
              pCameraResources->GetBackBufferRenderTargetView()};
          context->OMSetRenderTargets(1, targets, depthStencilView);

          // Clear the back buffer and depth stencil view.
          if (m_canGetHolographicDisplayForCamera &&
              cameraPose.HolographicCamera().Display().IsOpaque()) {
            context->ClearRenderTargetView(targets[0],
                                           DirectX::Colors::CornflowerBlue);
          } else {
            context->ClearRenderTargetView(targets[0],
                                           DirectX::Colors::Transparent);
          }
          context->ClearDepthStencilView(
              depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f,
              0);

          //
          // TODO: Replace the sample content with your own content.
          //
          // Notes regarding holographic content:
          //    * For drawing, remember that you have the potential to fill
          //    twice as many pixels
          //      in a stereoscopic render target as compared to a
          //      non-stereoscopic render target of the same resolution. Avoid
          //      unnecessary or repeated writes to the same pixel, and only
          //      draw holograms that the user can see.
          //    * To help occlude hologram geometry, you can create a depth map
          //    using geometry
          //      data obtained via the surface mapping APIs. You can use this
          //      depth map to avoid rendering holograms that are intended to be
          //      hidden behind tables, walls, monitors, and so on.
          //    * On HolographicDisplays that are transparent, black pixels will
          //    appear transparent
          //      to the user. On such devices, you should clear the screen to
          //      Transparent as shown above. You should still use alpha
          //      blending to draw semitransparent holograms.
          //

          // The view and projection matrices for each holographic camera will
          // change every frame. This function refreshes the data in the
          // constant buffer for the holographic camera indicated by cameraPose.
          if (m_stationaryReferenceFrame) {
            pCameraResources->UpdateViewProjectionBuffer(
                m_deviceResources, cameraPose,
                m_stationaryReferenceFrame.CoordinateSystem());
          }

          // Attach the view/projection constant buffer for this camera to the
          // graphics pipeline.
          bool cameraActive =
              pCameraResources->AttachViewProjectionBuffer(m_deviceResources);

#ifdef DRAW_SAMPLE_CONTENT
          // Only render world-locked content when positional tracking is
          // active.
          if (cameraActive) {
            // Draw the sample hologram.
            m_spinningCubeRenderer->Render();
            if (m_canCommitDirect3D11DepthBuffer) {
              // On versions of the platform that support the
              // CommitDirect3D11DepthBuffer API, we can provide the depth
              // buffer to the system, and it will use depth information to
              // stabilize the image at a per-pixel level.
              HolographicCameraRenderingParameters renderingParameters =
                  holographicFrame.GetRenderingParameters(cameraPose);

              IDirect3DSurface interopSurface =
                  DX::CreateDepthTextureInteropObject(
                      pCameraResources->GetDepthStencilTexture2D());

              // Calling CommitDirect3D11DepthBuffer causes the system to queue
              // Direct3D commands to read the depth buffer. It will then use
              // that information to stabilize the image as the HolographicFrame
              // is presented.
              renderingParameters.CommitDirect3D11DepthBuffer(interopSurface);
            }
          }
#endif
          atLeastOneCameraRendered = true;
        }

        return atLeastOneCameraRendered;
      });
}

void ImmersiveMain::SaveAppState() {
  //
  // TODO: Insert code here to save your app state.
  //       This method is called when the app is about to suspend.
  //
  //       For example, store information in the SpatialAnchorStore.
  //
}

void ImmersiveMain::LoadAppState() {
  //
  // TODO: Insert code here to load your app state.
  //       This method is called when the app resumes.
  //
  //       For example, load information from the SpatialAnchorStore.
  //
}

void ImmersiveMain::OnPointerPressed() { m_pointerPressed = true; }

// Notifies classes that use Direct3D device resources that the device resources
// need to be released before this method returns.
void ImmersiveMain::OnDeviceLost() {
#ifdef DRAW_SAMPLE_CONTENT
  m_spinningCubeRenderer->ReleaseDeviceDependentResources();
#endif
}

// Notifies classes that use Direct3D device resources that the device resources
// may now be recreated.
void ImmersiveMain::OnDeviceRestored() {
#ifdef DRAW_SAMPLE_CONTENT
  m_spinningCubeRenderer->CreateDeviceDependentResources();
#endif
}

void ImmersiveMain::OnLocatabilityChanged(
    SpatialLocator const &sender,
    winrt::Windows::Foundation::IInspectable const &) {
  switch (sender.Locatability()) {
  case SpatialLocatability::Unavailable:
    // Holograms cannot be rendered.
    {
      winrt::hstring message(L"Warning! Positional tracking is " +
                             std::to_wstring(int(sender.Locatability())) +
                             L".\n");
      OutputDebugStringW(message.data());
    }
    break;

  // In the following three cases, it is still possible to place holograms using
  // a SpatialLocatorAttachedFrameOfReference.
  case SpatialLocatability::PositionalTrackingActivating:
    // The system is preparing to use positional tracking.

  case SpatialLocatability::OrientationOnly:
    // Positional tracking has not been activated.

  case SpatialLocatability::PositionalTrackingInhibited:
    // Positional tracking is temporarily inhibited. User action may be required
    // in order to restore positional tracking.
    break;

  case SpatialLocatability::PositionalTrackingActive:
    // Positional tracking is active. World-locked content can be rendered.
    break;
  }
}

void ImmersiveMain::OnCameraAdded(
    HolographicSpace const &,
    HolographicSpaceCameraAddedEventArgs const &args) {
  winrt::Windows::Foundation::Deferral deferral = args.GetDeferral();
  HolographicCamera holographicCamera = args.Camera();
  create_task([this, deferral, holographicCamera]() {
    //
    // TODO: Allocate resources for the new camera and load any content specific
    // to
    //       that camera. Note that the render target size (in pixels) is a
    //       property of the HolographicCamera object, and can be used to create
    //       off-screen render targets that match the resolution of the
    //       HolographicCamera.
    //

    // Create device-based resources for the holographic camera and add it to
    // the list of cameras used for updates and rendering. Notes:
    //   * Since this function may be called at any time, the
    //   AddHolographicCamera function
    //     waits until it can get a lock on the set of holographic camera
    //     resources before adding the new camera. At 60 frames per second this
    //     wait should not take long.
    //   * A subsequent Update will take the back buffer from the
    //   RenderingParameters of this
    //     camera's CameraPose and use it to create the ID3D11RenderTargetView
    //     for this camera. Content can then be rendered for the
    //     HolographicCamera.
    m_deviceResources->AddHolographicCamera(holographicCamera);

    // Holographic frame predictions will not include any information about this
    // camera until the deferral is completed.
    deferral.Complete();
  });
}

void ImmersiveMain::OnCameraRemoved(
    HolographicSpace const &,
    HolographicSpaceCameraRemovedEventArgs const &args) {
  create_task([this]() {
    //
    // TODO: Asynchronously unload or deactivate content resources (not back
    // buffer
    //       resources) that are specific only to the camera that was removed.
    //
  });

  // Before letting this callback return, ensure that all references to the back
  // buffer are released. Since this function may be called at any time, the
  // RemoveHolographicCamera function waits until it can get a lock on the set
  // of holographic camera resources before deallocating resources for this
  // camera. At 60 frames per second this wait should not take long.
  m_deviceResources->RemoveHolographicCamera(args.Camera());
}

void ImmersiveMain::OnGamepadAdded(winrt::Windows::Foundation::IInspectable,
                                   Gamepad const &args) {
  for (GamepadWithButtonState const &gamepadWithButtonState : m_gamepads) {
    if (args == gamepadWithButtonState.gamepad) {
      // This gamepad is already in the list.
      return;
    }
  }

  GamepadWithButtonState newGamepad = {args, false};
  m_gamepads.push_back(newGamepad);
}

void ImmersiveMain::OnGamepadRemoved(winrt::Windows::Foundation::IInspectable,
                                     Gamepad const &args) {
  m_gamepads.erase(
      std::remove_if(m_gamepads.begin(), m_gamepads.end(),
                     [&](GamepadWithButtonState &gamepadWithState) {
                       return gamepadWithState.gamepad == args;
                     }),
      m_gamepads.end());
}

void ImmersiveMain::OnHolographicDisplayIsAvailableChanged(
    winrt::Windows::Foundation::IInspectable,
    winrt::Windows::Foundation::IInspectable) {
  // Get the spatial locator for the default HolographicDisplay, if one is
  // available.
  SpatialLocator spatialLocator = nullptr;
  if (m_canGetDefaultHolographicDisplay) {
    HolographicDisplay defaultHolographicDisplay =
        HolographicDisplay::GetDefault();
    if (defaultHolographicDisplay) {
      spatialLocator = defaultHolographicDisplay.SpatialLocator();
    }
  } else {
    spatialLocator = SpatialLocator::GetDefault();
  }

  if (m_spatialLocator != spatialLocator) {
    // If the spatial locator is disconnected or replaced, we should discard all
    // state that was based on it.
    if (m_spatialLocator != nullptr) {
      m_spatialLocator.LocatabilityChanged(m_locatabilityChangedToken);
      m_spatialLocator = nullptr;
    }

    m_stationaryReferenceFrame = nullptr;

    if (spatialLocator != nullptr) {
      // Use the SpatialLocator from the default HolographicDisplay to track the
      // motion of the device.
      m_spatialLocator = spatialLocator;

      // Respond to changes in the positional tracking state.
      m_locatabilityChangedToken = m_spatialLocator.LocatabilityChanged(
          std::bind(&ImmersiveMain::OnLocatabilityChanged, this, _1, _2));

      // The simplest way to render world-locked holograms is to create a
      // stationary reference frame based on a SpatialLocator. This is roughly
      // analogous to creating a "world" coordinate system with the origin
      // placed at the device's position as the app is launched.
      m_stationaryReferenceFrame =
          m_spatialLocator.CreateStationaryFrameOfReferenceAtCurrentLocation();
    }
  }
}