1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dxrspatialanchorlistmodel_p.h"
5#include "qquick3dxrspatialanchor_p.h"
6
7#if defined(Q_OS_VISIONOS)
8#include "visionos/qquick3dxranchormanager_visionos_p.h"
9#else
10#include "openxr/qquick3dxranchormanager_openxr_p.h"
11#endif
12
13QT_BEGIN_NAMESPACE
14
15/*!
16 \qmltype XrSpatialAnchorListModel
17 \inherits ListModel
18 \inqmlmodule QtQuick3D.Xr
19 \brief Provides a model containing spatial anchors.
20
21 This type provides a list of spatial anchors, which are points in
22 the physical world that can be tracked and associated with virtual content.
23
24 The list contains elements that have an \c anchor property with the type \l XrSpatialAnchor.
25
26 You can use it like this:
27
28 \qml
29 Repeater3D {
30 model: XrSpatialAnchorListModel {
31 }
32 delegate: Node {
33 required property XrSpatialAnchor anchor
34 position: anchor.position
35 rotation: anchor.rotation
36 // Further use of anchor properties...
37 }
38 }
39 \endqml
40*/
41
42static QString getClassificationString(QQuick3DXrSpatialAnchorListModel::ClassificationFlag classification)
43{
44 switch (classification) {
45 case QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Wall:
46 return QStringLiteral("wall");
47 case QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Ceiling:
48 return QStringLiteral("ceiling");
49 case QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Floor:
50 return QStringLiteral("floor");
51 case QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Table:
52 return QStringLiteral("table");
53 case QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Seat:
54 return QStringLiteral("seat");
55 case QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Window:
56 return QStringLiteral("window");
57 case QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Door:
58 return QStringLiteral("door");
59 case QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Other:
60 return QStringLiteral("other");
61 }
62
63 return {};
64}
65
66static QQuick3DXrSpatialAnchorListModel::ClassificationFlag getClassificationFlagType(QQuick3DXrSpatialAnchor::Classification classification)
67{
68 switch (classification) {
69 case QQuick3DXrSpatialAnchor::Classification::Unknown:
70 return QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Other;
71 case QQuick3DXrSpatialAnchor::Classification::Wall:
72 return QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Wall;
73 case QQuick3DXrSpatialAnchor::Classification::Ceiling:
74 return QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Ceiling;
75 case QQuick3DXrSpatialAnchor::Classification::Floor:
76 return QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Floor;
77 case QQuick3DXrSpatialAnchor::Classification::Table:
78 return QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Table;
79 case QQuick3DXrSpatialAnchor::Classification::Seat:
80 return QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Seat;
81 case QQuick3DXrSpatialAnchor::Classification::Window:
82 return QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Window;
83 case QQuick3DXrSpatialAnchor::Classification::Door:
84 return QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Door;
85 case QQuick3DXrSpatialAnchor::Classification::Other:
86 return QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Other;
87 }
88
89 Q_UNREACHABLE_RETURN(QQuick3DXrSpatialAnchorListModel::ClassificationFlag::Other);
90}
91
92QQuick3DXrSpatialAnchorListModel::QQuick3DXrSpatialAnchorListModel(QObject *parent)
93 : QAbstractListModel{parent}
94{
95 m_anchorManager = QQuick3DXrAnchorManager::instance();
96 if (m_anchorManager) {
97 connect(sender: m_anchorManager, signal: &QQuick3DXrAnchorManager::anchorAdded, context: this, slot: &QQuick3DXrSpatialAnchorListModel::handleAnchorAdded);
98 connect(sender: m_anchorManager, signal: &QQuick3DXrAnchorManager::anchorUpdated, context: this, slot: &QQuick3DXrSpatialAnchorListModel::handleAnchorUpdated);
99 connect(sender: m_anchorManager, signal: &QQuick3DXrAnchorManager::anchorRemoved, context: this, slot: &QQuick3DXrSpatialAnchorListModel::handleAnchorRemoved);
100 connect(sender: m_anchorManager, signal: &QQuick3DXrAnchorManager::sceneCaptureCompleted, context: this, slot: [this]{queryAnchors();});
101 queryAnchors();
102 } else {
103 qWarning(msg: "SpatialAnchorModel: Failed to get anchor manager instance");
104 }
105}
106
107int QQuick3DXrSpatialAnchorListModel::rowCount(const QModelIndex &parent) const
108{
109 if (parent.isValid() || m_anchorManager == nullptr)
110 return 0;
111
112 const auto &anchors = anchorsFiltered();
113
114 return anchors.size();
115}
116
117QVariant QQuick3DXrSpatialAnchorListModel::data(const QModelIndex &index, int role) const
118{
119 if (!index.isValid() || m_anchorManager == nullptr)
120 return QVariant();
121
122 const auto &anchors = anchorsFiltered();
123
124 // check bounds
125 if (index.row() < 0 || index.row() >= anchors.count())
126 return QVariant();
127
128 const auto &anchor = anchors.at(i: index.row());
129
130 if (role == Anchor)
131 return QVariant::fromValue(value: anchor);
132
133 // shouldn't be reachable under normal circumstances.
134 return QVariant();
135}
136
137QHash<int, QByteArray> QQuick3DXrSpatialAnchorListModel::roleNames() const
138{
139 QHash<int, QByteArray> roles;
140 roles[Anchor] = "anchor";
141 return roles;
142}
143
144/*!
145 \qmlmethod void XrSpatialAnchorListModel::requestSceneCapture()
146 \brief This method triggers a scan to capture or update spatial data for the current environment.
147
148 This method triggers the underlying XR system to perform a scene capture of the user's current physical environment,
149 to update or refine the spatial mesh, enabling more accurate placement and tracking of spatial anchors.
150
151 \note Some platforms do not perform this operation automatically.
152 For example, on Quest 3, if the user is in a previously uncaptured space, this method will not be called automatically,
153 resulting in no available anchors until a capture is manually triggered.
154 */
155
156void QQuick3DXrSpatialAnchorListModel::requestSceneCapture()
157{
158 if (m_anchorManager == nullptr)
159 return;
160
161 // not supported on the Simulator, this will be a no-op there
162 m_anchorManager->requestSceneCapture();
163}
164
165void QQuick3DXrSpatialAnchorListModel::queryAnchors()
166{
167 if (m_anchorManager == nullptr)
168 return;
169
170 m_anchorManager->queryAllAnchors();
171}
172
173void QQuick3DXrSpatialAnchorListModel::handleAnchorAdded(QQuick3DXrSpatialAnchor *anchor)
174{
175 Q_UNUSED(anchor)
176 if (matchesAnchorFilter(anchor)) {
177 // Brute Force :-p
178 beginResetModel();
179 endResetModel();
180 }
181}
182
183void QQuick3DXrSpatialAnchorListModel::handleAnchorRemoved(QUuid uuid)
184{
185 Q_UNUSED(uuid)
186 // Brute Force :-p
187 beginResetModel();
188 endResetModel();
189}
190
191void QQuick3DXrSpatialAnchorListModel::handleAnchorUpdated(QQuick3DXrSpatialAnchor *anchor)
192{
193 Q_UNUSED(anchor)
194 if (matchesAnchorFilter(anchor)) {
195 // Brute Force :-p
196 beginResetModel();
197 endResetModel();
198 }
199}
200
201/*!
202 \qmlproperty enumeration XrSpatialAnchorListModel::filterMode
203 \brief Specifies the filter mode for spatial anchors.
204
205 Holds the filter mode.
206 The filter mode can be one of the following:
207 \value XrSpatialAnchorListModel.All Show all spatial anchors.
208 \value XrSpatialAnchorListModel.Classification Show spatial anchors based on the provided classification filter flag.
209 \value XrSpatialAnchorListModel.Identifier Show spatial anchors based on matching the provided Identifiers.
210 */
211
212QQuick3DXrSpatialAnchorListModel::FilterMode QQuick3DXrSpatialAnchorListModel::filterMode() const
213{
214 return m_filterMode;
215}
216
217void QQuick3DXrSpatialAnchorListModel::setFilterMode(FilterMode newFilterMode)
218{
219 if (m_filterMode == newFilterMode)
220 return;
221 m_filterMode = newFilterMode;
222
223 // Make sure we reset the model when changing filter mode.
224 handleAnchorRemoved(uuid: {});
225
226 emit filterModeChanged();
227}
228
229/*!
230 \qmlproperty list<string> XrSpatialAnchorListModel::identifierFilter
231 \brief Holds the list of identifiers for filtering spatial anchors.
232 */
233
234QStringList QQuick3DXrSpatialAnchorListModel::identifierFilter() const
235{
236 return m_uuids.values();
237}
238
239void QQuick3DXrSpatialAnchorListModel::setIdentifierFilter(const QStringList &filter)
240{
241 QSet<QString> newFilter { filter.cbegin(), filter.cend() };
242
243 if (m_uuids == newFilter)
244 return;
245
246 m_uuids = newFilter;
247
248 // Make sure we reset the model.
249 handleAnchorRemoved(uuid: {});
250
251 emit identifierFilterChanged();
252}
253
254/*!
255 \qmlproperty enumeration XrSpatialAnchorListModel::classificationFilter
256 \brief Holds the classification flag used for filtering spatial anchors.
257
258 The ClassificationFlag filter is represented as a combination of flags:
259
260 \value XrSpatialAnchorListModel.Wall
261 \value XrSpatialAnchorListModel.Ceiling
262 \value XrSpatialAnchorListModel.Floor
263 \value XrSpatialAnchorListModel.Table
264 \value XrSpatialAnchorListModel.Seat
265 \value XrSpatialAnchorListModel.Window
266 \value XrSpatialAnchorListModel.Door
267 \value XrSpatialAnchorListModel.Other
268 */
269
270QQuick3DXrSpatialAnchorListModel::ClassificationFlags QQuick3DXrSpatialAnchorListModel::classificationFilter() const
271{
272 return m_classFilter;
273}
274
275void QQuick3DXrSpatialAnchorListModel::setClassificationFilter(ClassificationFlags newClassFilter)
276{
277 if (m_classFilter == newClassFilter)
278 return;
279
280 m_classFilter = newClassFilter;
281
282 m_classStringFilter.clear();
283
284 constexpr size_t classifcationCount = (sizeof(std::underlying_type_t<ClassificationFlag>) * 8) - 1;
285 for (size_t i = 0; i < classifcationCount; ++i) {
286 ClassificationFlag classification = static_cast<ClassificationFlag>(size_t(m_classFilter) & (size_t(1) << i));
287 auto name = getClassificationString(classification);
288 if (!name.isEmpty())
289 m_classStringFilter.insert(value: name);
290 }
291
292 // Make sure we reset the model.
293 handleAnchorRemoved(uuid: {});
294
295 emit classificationFilterChanged();
296}
297
298/*!
299 \qmlproperty list<string> XrSpatialAnchorListModel::classificationStringFilter
300 \brief Holds the classification strings used for filtering spatial anchors.
301
302 If the \l filterMode is set to \c Classification, this property can be used to provide a
303 list of additional classification string to filter on. These labels will then be matched against
304 the same value as reported by \l {XrSpatialAnchor::classificationString} property
305 of the spatial anchor.
306
307 \note Only \l {XrSpatialAnchor}{spatial anchors} that are classified as
308 \l {XrSpatialAnchor::Classification}{Other} will be checked against this filter.
309 */
310QStringList QQuick3DXrSpatialAnchorListModel::classificationStringFilter() const
311{
312 return m_classStringFilter.values();
313}
314
315void QQuick3DXrSpatialAnchorListModel::setClassificationStringFilter(const QStringList &newClassStringFilter)
316{
317 for (const auto &entry : newClassStringFilter) {
318 if (!entry.isEmpty())
319 m_classStringFilter.insert(value: entry.toLower());
320 }
321
322 // Make sure we reset the model.
323 handleAnchorRemoved(uuid: {});
324
325 emit classificationStringFilterChanged();
326}
327
328bool QQuick3DXrSpatialAnchorListModel::matchesAnchorFilter(QQuick3DXrSpatialAnchor *anchor) const
329{
330 if (m_filterMode == FilterMode::Classification) {
331 QString classificationString;
332 const auto classification = anchor->classification();
333 if (classification != QQuick3DXrSpatialAnchor::Classification::Other) {
334 const auto classificationFlagType = getClassificationFlagType(classification);
335 classificationString = getClassificationString(classification: classificationFlagType);
336 } else {
337 // NOTE: Other can mean there is no classification or the classification is not one that
338 // we have defined in our enums, so we will use the string as the user can specify any string.
339 classificationString = anchor->classificationString().toLower();
340 }
341 return m_classStringFilter.contains(value: classificationString.toLower());
342 } else if (m_filterMode == FilterMode::Identifier) {
343 const auto uuid = anchor->identifier();
344 return m_uuids.contains(value: uuid);
345 }
346
347 // 'All' mode
348 return true;
349}
350
351QList<QQuick3DXrSpatialAnchor *> QQuick3DXrSpatialAnchorListModel::anchorsFiltered() const
352{
353 QList<QQuick3DXrSpatialAnchor *> anchors;
354 const auto &unfilteredAnchors = m_anchorManager->anchors();
355 if (m_filterMode == FilterMode::All) {
356 anchors = unfilteredAnchors;
357 } else {
358 for (const auto &anchor : unfilteredAnchors) {
359 if (matchesAnchorFilter(anchor))
360 anchors.push_back(t: anchor);
361 }
362 }
363
364 return anchors;
365}
366
367QT_END_NAMESPACE
368

source code of qtquick3d/src/xr/quick3dxr/qquick3dxrspatialanchorlistmodel.cpp