-
Notifications
You must be signed in to change notification settings - Fork 96
Expand file tree
/
Copy pathScene_QRCode.cpp
More file actions
477 lines (415 loc) · 24.9 KB
/
Copy pathScene_QRCode.cpp
File metadata and controls
477 lines (415 loc) · 24.9 KB
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
#include "pch.h"
#include <unordered_set>
#include <DirectXCollision.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Security.Cryptography.h>
#include <XrUtility/XrString.h>
#include <pbr/GltfLoader.h>
#include <SampleShared/FileUtility.h>
#include <SampleShared/TextureUtility.h>
#include <XrSceneLib/PbrModelObject.h>
#include <XrSceneLib/Scene.h>
#include <XrSceneLib/SpaceObject.h>
#include <XrSceneLib/TextTexture.h>
using namespace std::chrono_literals;
using namespace std::chrono;
using namespace DirectX;
using TimePoint = engine::FrameTime::clock::time_point;
namespace {
enum class FilteringType { None = 0, Frustum, Sphere, Box, Count };
constexpr auto UpdateInterval = 5s; // Time to wait between SU requests
constexpr auto MaxMarkerAge = 600s; // Hide markers not detected for 10 minutes
// Filtering objects constants
constexpr float FrustumDistance = 2.0f;
constexpr DirectX::XMFLOAT2 FrustumSpan = {1.0f, .5f};
constexpr float SphereDistance = 1.0f;
constexpr float SphereRadius = 0.3f;
constexpr float BoxWidth = 1.0f;
constexpr float BoxHeight = .6f;
constexpr float BoxDepth = .4f;
constexpr float BoxDistance = 1.0f;
constexpr auto BoundsColor = Pbr::RGBAColor{0.0f, 1.0f, 0.0f, .3f};
struct QRCodeScene : public engine::Scene {
explicit QRCodeScene(engine::Context& context)
: Scene(context)
, m_filteringType(FilteringType::None)
, m_nextUpdate{engine::FrameTime::clock::now() + UpdateInterval}
, m_frustumBoundsTarget{engine::CreateQuad(
m_context.PbrResources, FrustumSpan, Pbr::Material::CreateFlat(m_context.PbrResources, BoundsColor))}
, m_sphereBoundsTarget{engine::CreateSphere(m_context.PbrResources, 2 * SphereRadius, 32, BoundsColor)}
, m_boxBoundsTarget{engine::CreateCube(m_context.PbrResources, XMFLOAT3{BoxWidth, BoxHeight, BoxDepth}, BoundsColor)} {
// Our filtering bounds will be based on view space
XrReferenceSpaceCreateInfo createInfo{XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
createInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
createInfo.poseInReferenceSpace = xr::math::Pose::Identity();
CHECK_XRCMD(xrCreateReferenceSpace(context.Session.Handle, &createInfo, &m_viewSpace));
m_frustumBoundsTarget->SetVisible(false);
m_sphereBoundsTarget->SetVisible(false);
m_boxBoundsTarget->SetVisible(false);
AddObject(m_frustumBoundsTarget);
AddObject(m_sphereBoundsTarget);
AddObject(m_boxBoundsTarget);
// Setup action to change filter
sample::ActionSet& actionSet = ActionContext().CreateActionSet("qrcodes_scene_actions", "QRCodes Scene Actions");
const std::vector<std::string> subactionPathBothHands = {"/user/hand/right", "/user/hand/left"};
m_switchFilterAction = actionSet.CreateAction(
"switch_filter_action", "Switch Filter Action", XR_ACTION_TYPE_BOOLEAN_INPUT, subactionPathBothHands);
ActionContext().SuggestInteractionProfileBindings("/interaction_profiles/khr/simple_controller",
{
{m_switchFilterAction, "/user/hand/right/input/select/click"},
{m_switchFilterAction, "/user/hand/left/input/select/click"},
});
if (context.Extensions.XR_MSFT_hand_interaction_enabled) {
ActionContext().SuggestInteractionProfileBindings("/interaction_profiles/microsoft/hand_interaction",
{
{m_switchFilterAction, "/user/hand/left/input/select/value"},
{m_switchFilterAction, "/user/hand/right/input/select/value"},
});
}
}
bool TryGetFrustumQuadPose(XrTime time, XrPosef* frustumInAppSpace, XrPosef* quadInAppSpace) {
XrSpaceLocation location{XR_TYPE_SPACE_LOCATION};
CHECK_XRCMD(xrLocateSpace(m_viewSpace, m_context.AppSpace, time, &location));
if (!xr::math::Pose::IsPoseValid(location.locationFlags)) {
return false;
}
// Frustum's pose is exactly View Pose at this precise moment
*frustumInAppSpace = location.pose;
// Quad should be displayed at the end of the frustum but is looking towards the view
XrPosef quadInFrustum = xr::math::Pose::Translation({0.0f, 0.0f, -FrustumDistance});
*quadInAppSpace = xr::math::Pose::Multiply(quadInFrustum, *frustumInAppSpace);
return true;
}
bool TryGetSpherePose(XrTime time, XrPosef* sphereInAppSpace) {
XrSpaceLocation location{XR_TYPE_SPACE_LOCATION};
CHECK_XRCMD(xrLocateSpace(m_viewSpace, m_context.AppSpace, time, &location));
if (!xr::math::Pose::IsPoseValid(location.locationFlags)) {
return false;
}
// Sphere is located in frot of the view
XrPosef sphereInViewSpace = xr::math::Pose::Translation(XrVector3f{0.0f, 0.0f, -SphereDistance});
*sphereInAppSpace = xr::math::Pose::Multiply(sphereInViewSpace, location.pose);
return true;
}
bool TryGetBoxPose(XrTime time, XrPosef* boxInAppSpace) {
XrSpaceLocation location{XR_TYPE_SPACE_LOCATION};
CHECK_XRCMD(xrLocateSpace(m_viewSpace, m_context.AppSpace, time, &location));
if (!xr::math::Pose::IsPoseValid(location.locationFlags)) {
return false;
}
// Sphere is located in frot of the view
XrPosef boxInViewSpace = xr::math::Pose::Translation(XrVector3f{0.0f, 0.0f, -BoxDistance});
*boxInAppSpace = xr::math::Pose::Multiply(boxInViewSpace, location.pose);
return true;
}
bool ShouldSwitchFilter() {
XrActionStateBoolean switchFilterState{XR_TYPE_ACTION_STATE_BOOLEAN};
XrActionStateGetInfo getInfo{XR_TYPE_ACTION_STATE_GET_INFO};
getInfo.action = m_switchFilterAction;
getInfo.subactionPath = m_context.Instance.RightHandPath;
CHECK_XRCMD(xrGetActionStateBoolean(m_context.Session.Handle, &getInfo, &switchFilterState));
if (switchFilterState.isActive && switchFilterState.changedSinceLastSync && switchFilterState.currentState) {
return true;
}
getInfo.subactionPath = m_context.Instance.LeftHandPath;
CHECK_XRCMD(xrGetActionStateBoolean(m_context.Session.Handle, &getInfo, &switchFilterState));
if (switchFilterState.isActive && switchFilterState.changedSinceLastSync && switchFilterState.currentState) {
return true;
}
return false;
}
void OnUpdate(const engine::FrameTime& frameTime) override {
if (!m_sceneObserver) {
Enable();
}
m_lastTimeOfUpdate = frameTime.PredictedDisplayTime;
// Check actions
bool shouldSwitchFilter = ShouldSwitchFilter();
if (shouldSwitchFilter) {
// Reset all filter data and switch filter type
m_sceneComputeInfo.bounds.boxCount = 0;
m_sceneComputeInfo.bounds.sphereCount = 0;
m_sceneComputeInfo.bounds.frustumCount = 0;
m_boundsLocated = false;
m_frustumBoundsTarget->SetVisible(false);
m_sphereBoundsTarget->SetVisible(false);
m_boxBoundsTarget->SetVisible(false);
m_filteringType =
static_cast<FilteringType>((static_cast<size_t>(m_filteringType) + 1) % static_cast<size_t>(FilteringType::Count));
}
// Update the location of all scene objects
if (m_markerIds.size() > 0) {
XrSceneComponentsLocateInfoMSFT locateInfo{XR_TYPE_SCENE_COMPONENTS_LOCATE_INFO_MSFT};
locateInfo.baseSpace = m_context.AppSpace;
locateInfo.time = frameTime.PredictedDisplayTime;
locateInfo.componentIdCount = static_cast<uint32_t>(m_markerIds.size());
locateInfo.componentIds = m_markerIds.data();
XrSceneComponentLocationsMSFT componentLocations{XR_TYPE_SCENE_COMPONENT_LOCATIONS_MSFT};
componentLocations.locationCount = static_cast<uint32_t>(m_componentLocations.size());
componentLocations.locations = m_componentLocations.data();
CHECK_XRCMD(xrLocateSceneComponentsMSFT(m_scene.Get(), &locateInfo, &componentLocations));
for (size_t i = 0; i < m_markerIds.size(); ++i) {
const XrSceneComponentLocationMSFT& location = m_componentLocations[i];
auto& visualInfo = m_markerVisuals[m_markerIds[i]];
const std::shared_ptr<engine::PbrModelObject>& object = visualInfo.second;
const auto centerToPose = visualInfo.first;
if (xr::math::Pose::IsPoseValid(location.flags)) {
object->Pose() = xr::math::Pose::Multiply(centerToPose, location.pose);
object->SetFillMode(xr::math::Pose::IsPoseTracked(location.flags) ? Pbr::FillMode::Solid
: Pbr::FillMode::Wireframe);
} else {
object->SetVisible(false);
}
}
}
if (m_scanState == ScanState::Waiting) {
// Check if the results are available
// xr_msft_scene_marker.h
// 4. Poll xrGetSceneComputeStateMSFT, waiting for compute complete
XrSceneComputeStateMSFT state{XR_SCENE_COMPUTE_STATE_NONE_MSFT};
CHECK_XRCMD(xrGetSceneComputeStateMSFT(m_sceneObserver.Get(), &state));
if (state == XR_SCENE_COMPUTE_STATE_COMPLETED_MSFT) {
UpdateQRCodeVisuals(frameTime.PredictedDisplayTime);
m_scanState = ScanState::Idle;
} else if (state == XR_SCENE_COMPUTE_STATE_COMPLETED_WITH_ERROR_MSFT) {
sample::Trace("Compute completed with error");
m_scanState = ScanState::Idle;
}
} else if (m_scanState == ScanState::Idle) {
// Try to locate filtering bounds if needed
if (!m_boundsLocated) {
switch (m_filteringType) {
case FilteringType::Frustum:
// Declare a Frustum in app_space, in the view direction, with a PI/2 angular width, PI angular height
// And starting in the View origin
{
XrPosef frustumPose{};
XrPosef quadPose{};
m_boundsLocated = TryGetFrustumQuadPose(m_lastTimeOfUpdate, &frustumPose, &quadPose);
if (m_boundsLocated) {
// View space localized, we can
// (1) give a pose to the quad helping to understand the frustum
// (2) give the frustum description in AppSpace to enable QRCode filtering
m_frustumBounds.pose = frustumPose;
// Let's target a 1m x 1m at "far distance"
m_frustumBounds.fov.angleUp = atan2f(FrustumSpan.y / 2.0f, FrustumDistance);
m_frustumBounds.fov.angleRight = atan2f(FrustumSpan.x / 2.0f, FrustumDistance);
m_frustumBounds.fov.angleDown = -m_frustumBounds.fov.angleUp;
m_frustumBounds.fov.angleLeft = -m_frustumBounds.fov.angleRight;
m_frustumBounds.farDistance = FrustumDistance;
m_sceneComputeInfo.bounds.frustumCount = 1;
m_sceneComputeInfo.bounds.frustums = &m_frustumBounds;
m_frustumBoundsTarget->Pose() = quadPose;
}
}
m_frustumBoundsTarget->SetVisible(m_boundsLocated);
break;
case FilteringType::Sphere:
// Declare a Sphare in app_space, in the view direction at known distance
{
XrPosef spherePose{};
m_boundsLocated = TryGetSpherePose(m_lastTimeOfUpdate, &spherePose);
if (m_boundsLocated) {
m_sphereBounds.center = spherePose.position;
m_sphereBounds.radius = SphereRadius;
m_sceneComputeInfo.bounds.sphereCount = 1;
m_sceneComputeInfo.bounds.spheres = &m_sphereBounds;
m_sphereBoundsTarget->Pose() = spherePose;
}
}
m_sphereBoundsTarget->SetVisible(m_boundsLocated);
break;
case FilteringType::Box:
// Declare a Sphare in app_space, in the view direction at known distance
{
XrPosef boxPose{};
m_boundsLocated = TryGetBoxPose(m_lastTimeOfUpdate, &boxPose);
if (m_boundsLocated) {
m_boxBounds.pose = boxPose;
m_boxBounds.extents = XrVector3f{BoxWidth, BoxHeight, BoxDepth};
m_sceneComputeInfo.bounds.boxCount = 1;
m_sceneComputeInfo.bounds.boxes = &m_boxBounds;
m_boxBoundsTarget->Pose() = boxPose;
}
}
m_boxBoundsTarget->SetVisible(m_boundsLocated);
break;
}
}
// no active query, start one if enough time has passed
if (frameTime.Now > m_nextUpdate) {
// Ready to compute scene
bool computeScene = (m_filteringType == FilteringType::None) || m_boundsLocated;
if (computeScene) {
// Start the async query
// 3. If supported, call xrComputeNewSceneMSFT including XR_SCENE_COMPUTE_FEATURE_MARKER_MSFT,
// XrSceneBoundsMSFT will be ignored
const XrSceneComputeFeatureMSFT requestedFeature{XR_SCENE_COMPUTE_FEATURE_MARKER_MSFT};
m_sceneComputeInfo.requestedFeatureCount = 1;
m_sceneComputeInfo.requestedFeatures = &requestedFeature;
m_sceneComputeInfo.bounds.space = m_context.AppSpace;
m_sceneComputeInfo.bounds.time = m_lastTimeOfUpdate;
// m_sceneComputeInfo.bounds's sphere, frustum and box are already set
m_sceneComputeInfo.consistency = XR_SCENE_COMPUTE_CONSISTENCY_SNAPSHOT_COMPLETE_MSFT;
CHECK_XRCMD(xrComputeNewSceneMSFT(m_sceneObserver.Get(), &m_sceneComputeInfo));
m_nextUpdate = frameTime.Now + UpdateInterval;
m_scanState = ScanState::Waiting;
}
}
}
}
void OnActiveChanged() override {
if (IsActive()) {
Enable();
} else {
Disable();
}
}
private:
enum class ScanState { Idle, Waiting, Processing };
void Enable() {
// xr_msft_scene_marker.h
// 0. Call xrEnumerateSceneComputeFeaturesMSFT to check if observer supports XR_SCENE_COMPUTE_FEATURE_MARKER_MSFT
{
uint32_t count = 0;
CHECK_XRCMD(xrEnumerateSceneComputeFeaturesMSFT(m_context.Instance.Handle, m_context.System.Id, count, &count, nullptr));
std::vector<XrSceneComputeFeatureMSFT> supportedFeatures(count);
CHECK_XRCMD(xrEnumerateSceneComputeFeaturesMSFT(
m_context.Instance.Handle, m_context.System.Id, count, &count, supportedFeatures.data()));
supportedFeatures.resize(count);
if (std::find(supportedFeatures.begin(), supportedFeatures.end(), XR_SCENE_COMPUTE_FEATURE_MARKER_MSFT) ==
supportedFeatures.end()) {
throw std::logic_error("Markers are not supported");
}
}
// xr_msft_scene_marker.h
// 1. Call xrCreateSceneObserverMSFT
XrSceneObserverCreateInfoMSFT createInfo{XR_TYPE_SCENE_OBSERVER_CREATE_INFO_MSFT};
CHECK_XRCMD(xrCreateSceneObserverMSFT(m_context.Session.Handle, &createInfo, m_sceneObserver.Put(xrDestroySceneObserverMSFT)));
m_scanState = ScanState::Idle;
}
void Disable() {
for (auto& [id, visual] : m_markerVisuals) {
RemoveObject(visual.second);
}
m_markerVisuals.clear();
m_markerIds.clear();
}
// Other results from SU might need background processing, but QR Codes should generally be fine
void UpdateQRCodeVisuals(const XrTime predictedDisplayTime) {
// xr_msft_scene_marker.h
// 5. Call xrCreateSceneMSFT to get compute result
XrSceneCreateInfoMSFT createInfo{XR_TYPE_SCENE_CREATE_INFO_MSFT};
CHECK_XRCMD(xrCreateSceneMSFT(m_sceneObserver.Get(), &createInfo, m_scene.Put(xrDestroySceneMSFT)));
XrSceneComponentsGetInfoMSFT getInfo{XR_TYPE_SCENE_COMPONENTS_GET_INFO_MSFT};
getInfo.componentType = XR_SCENE_COMPONENT_TYPE_MARKER_MSFT;
std::vector<XrSceneMarkerTypeMSFT> markerTypes{XrSceneMarkerTypeMSFT::XR_SCENE_MARKER_TYPE_QR_CODE_MSFT};
XrSceneMarkerTypeFilterMSFT typesFilter{XR_TYPE_SCENE_MARKER_TYPE_FILTER_MSFT};
typesFilter.markerTypeCount = static_cast<uint32_t>(markerTypes.size());
typesFilter.markerTypes = markerTypes.data();
xr::InsertExtensionStruct(getInfo, typesFilter);
XrSceneComponentsMSFT sceneComponents{XR_TYPE_SCENE_COMPONENTS_MSFT};
// Get the number of markers
CHECK_XRCMD(xrGetSceneComponentsMSFT(m_scene.Get(), &getInfo, &sceneComponents));
const uint32_t count = sceneComponents.componentCountOutput;
std::vector<XrSceneComponentMSFT> components(count);
sceneComponents.componentCapacityInput = count;
sceneComponents.components = components.data();
std::vector<XrSceneMarkerMSFT> markers(count);
XrSceneMarkersMSFT sceneMarkers{XR_TYPE_SCENE_MARKERS_MSFT};
sceneMarkers.sceneMarkerCapacityInput = count;
sceneMarkers.sceneMarkers = markers.data();
xr::InsertExtensionStruct(sceneComponents, sceneMarkers);
std::vector<XrSceneMarkerQRCodeMSFT> qrcodes(count);
XrSceneMarkerQRCodesMSFT sceneQRCodes{XR_TYPE_SCENE_MARKER_QR_CODES_MSFT};
sceneQRCodes.qrCodeCapacityInput = count;
sceneQRCodes.qrCodes = qrcodes.data();
xr::InsertExtensionStruct(sceneComponents, sceneQRCodes);
CHECK_XRCMD(xrGetSceneComponentsMSFT(m_scene.Get(), &getInfo, &sceneComponents));
// Get or create visuals for all QR Codes not too obsolete
decltype(m_markerVisuals) markerVisuals;
for (uint32_t i = 0; i < count; ++i) {
const XrDuration markerAgeNanos = predictedDisplayTime - markers[i].lastSeenTime;
std::chrono::nanoseconds markerAge = duration<XrDuration, std::nano>(markerAgeNanos);
std::chrono::duration<float> markerAgeSeconds = markerAge;
if (markerAge < MaxMarkerAge) {
auto& markerId = components[i].id;
// Always re-create to enable color change and dimension change
auto visual = CreateMarkerVisual(markerId,
markers[i],
qrcodes[i],
markerAgeSeconds.count());
XrQuaternionf rotation = xr::math::Quaternion::RotationAxisAngle(XrVector3f{1.0f, 0.0f, 0.0f}, XM_PI);
XrVector3f centerOffset{markers[i].center.x, markers[i].center.y, 0.0f};
auto centerToPose = xr::math::Pose::MakePose(rotation, centerOffset);
markerVisuals[markerId] = std::make_pair(centerToPose, visual);
AddObject(visual);
}
}
// Now, remove obsolete visuals
for (auto& [id, visual] : m_markerVisuals) {
RemoveObject(visual.second);
}
m_markerVisuals = std::move(markerVisuals);
// Finally, update the parallel vectors
m_markerIds.clear();
for (auto& [id, visual] : m_markerVisuals) {
m_markerIds.push_back(id);
}
m_componentLocations.resize(m_markerIds.size());
}
// Create a Quad which covers the marker and displays the marker's string as text
std::shared_ptr<engine::PbrModelObject> CreateMarkerVisual(const XrUuidMSFT& markerId,
const XrSceneMarkerMSFT& marker,
const XrSceneMarkerQRCodeMSFT& qrcode,
float ageSeconds) {
uint32_t stringLength{0};
std::vector<char> markerString;
CHECK_XRCMD(xrGetSceneMarkerDecodedStringMSFT(m_scene.Get(), &markerId, 0, &stringLength, nullptr));
markerString.resize(stringLength);
CHECK_XRCMD(xrGetSceneMarkerDecodedStringMSFT(m_scene.Get(), &markerId, stringLength, &stringLength, markerString.data()));
const DirectX::XMFLOAT2 size{marker.size.width, marker.size.height};
const bool isMicroQR =
(qrcode.symbolType == XrSceneMarkerQRCodeSymbolTypeMSFT::XR_SCENE_MARKER_QR_CODE_SYMBOL_TYPE_MICRO_QR_CODE_MSFT);
const auto& pbrResources = m_context.PbrResources;
engine::TextTextureInfo textInfo{256, 256};
textInfo.Foreground = Pbr::RGBA::White;
textInfo.Background = Pbr::FromSRGB(Colors::Blue);
textInfo.Margin = 5;
textInfo.TextAlignment = DWRITE_TEXT_ALIGNMENT_CENTER;
textInfo.ParagraphAlignment = DWRITE_PARAGRAPH_ALIGNMENT_CENTER;
textInfo.FontSize = 32.0f;
auto textTexture = std::make_unique<engine::TextTexture>(m_context, textInfo);
char label[512];
sprintf_s(label, "%s\n%.3f s", markerString.data(), ageSeconds);
textTexture->Draw(label);
const auto& material = textTexture->CreatePbrMaterial(m_context.PbrResources);
return engine::CreateQuad(m_context.PbrResources, size, material);
}
// The XrSceneObserver needs to be destroyed after the XrScene.
xr::UniqueXrHandle<XrSceneObserverMSFT> m_sceneObserver;
xr::UniqueXrHandle<XrSceneMSFT> m_scene;
std::vector<XrUuidMSFT> m_markerIds;
std::vector<XrSceneComponentLocationMSFT> m_componentLocations;
std::unordered_map<XrUuidMSFT, std::pair<XrPosef, std::shared_ptr<engine::PbrModelObject>>> m_markerVisuals;
XrTime m_lastTimeOfUpdate{};
XrNewSceneComputeInfoMSFT m_sceneComputeInfo{XR_TYPE_NEW_SCENE_COMPUTE_INFO_MSFT};
XrSceneSphereBoundMSFT m_sphereBounds{0.0f};
XrSceneFrustumBoundMSFT m_frustumBounds{0.0f};
XrSceneOrientedBoxBoundMSFT m_boxBounds{0.0f};
TimePoint m_nextUpdate{};
ScanState m_scanState{ScanState::Idle};
// Bounds filtering
FilteringType m_filteringType;
bool m_boundsLocated{false};
XrSpace m_viewSpace;
std::shared_ptr<engine::Object> m_frustumBoundsTarget;
std::shared_ptr<engine::Object> m_sphereBoundsTarget;
std::shared_ptr<engine::Object> m_boxBoundsTarget;
XrAction m_switchFilterAction{XR_NULL_HANDLE};
};
} // namespace
std::unique_ptr<engine::Scene> TryCreateQRCodeScene(engine::Context& context) {
return context.Extensions.XR_MSFT_scene_marker_enabled ? std::make_unique<QRCodeScene>(context) : nullptr;
}