Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
Loading...
Searching...
No Matches
qt3dxr-coordinates-and-units.qdoc
Go to the documentation of this file.
1
// Copyright (C) 2026 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
3
4
/*!
5
\page qt3dxr-coordinates-and-units.html
6
\title Coordinate Systems and Units in \qxr
7
8
\brief This page explains how the XR tracking space, the 3D scene, and
9
2D Qt Quick content relate to each other, and what a coordinate value
10
means in each of them.
11
12
A \qxr application brings together three coordinate systems: the tracking
13
space that the XR runtime reports device poses in, the 3D scene inside the
14
\l XrView, and the 2D coordinate system of any Qt Quick content the
15
application displays. They use different units and, in the 2D case, a
16
different set of axes. Knowing which one a given value belongs to is
17
necessary to place content where you intend it to appear.
18
19
\section1 The three coordinate systems
20
21
\table
22
\header
23
\li Coordinate system
24
\li Unit
25
\li Axes
26
\li Origin
27
\row
28
\li XR tracking space
29
\li Meter, converted to centimeters before any value reaches QML
30
\li \c{+x} right, \c{+y} up, \c{-z} forward
31
\li Selected with \l{XrView::referenceSpace}{XrView.referenceSpace}
32
\row
33
\li 3D scene: any \l Node inside an \l XrView
34
\li Centimeter
35
\li \c{+x} right, \c{+y} up, \c{-z} forward
36
\li The parent node
37
\row
38
\li 2D content: any \l Item
39
\li Pixel
40
\li \c{+x} right, \c{+y} \e down
41
\li The top-left corner of the parent item
42
\endtable
43
44
\image qt3dxr-coordinate-systems.webp {A Qt Quick 3D scene with red, green
45
and blue arrows for the +x, +y and +z axes, and a built-in cube one
46
grid square wide resting on the floor}
47
\caption The middle row of the table, drawn as a real scene: the built-in
48
\c{#Cube} at scale 1 is 100 units across, which is one meter and one square
49
of the one-meter grid.
50
51
That cube is the whole unit rule in one object:
52
53
\snippet qt3dxr-coordinate-systems.qml unit cube
54
55
The tracking space and the 3D scene share the same right-handed axis
56
convention, so a pose never needs to be mirrored or swizzled. Only the unit
57
differs: \qxr multiplies every position it receives from the XR runtime by
58
100 before it reaches the scene. An application therefore never handles
59
meters directly, and never needs to convert a tracked pose itself.
60
61
2D content is the exception. Qt Quick's \c y axis grows downwards, which is
62
why moving a 2D child of a \l Node down the screen means increasing its
63
\c y, while moving the \l Node itself down means decreasing its \c y. See
64
\l{Qt Quick 3D Scenes with 2D Content} for the general rules, and
65
\l{Placing 2D content on a virtual surface} below for the XR specifics.
66
67
\section1 One unit is one centimeter
68
69
In a plain Qt Quick 3D scene the unit is arbitrary: nothing outside the
70
scene constrains it, so an application is free to treat a unit as a meter,
71
an inch, or anything else, as long as it is consistent.
72
73
In \qxr the unit is fixed. Tracked content has to line up with the physical
74
world, and \qxr expresses the scene in centimeters:
75
76
\table
77
\header
78
\li Real-world length
79
\li Scene value
80
\row
81
\li 1 mm
82
\li 0.1
83
\row
84
\li 1 cm
85
\li 1
86
\row
87
\li 1 m
88
\li 100
89
\endtable
90
91
This applies to every distance in the scene, not just to positions: the
92
dimensions of models, the \l{XrItem::width}{width} and
93
\l{XrItem::height}{height} of an \l XrItem, light ranges, and the
94
\l{XrCamera::clipNear}{clip planes} of an \l XrCamera are all in
95
centimeters.
96
97
Some values to calibrate against: a desk is about 75 high, a door about 200
98
high and 90 wide, and a seated user's eyes are about 120 above the floor, a
99
standing user's about 160.
100
101
\qml
102
XrView {
103
referenceSpace: XrView.ReferenceSpaceLocalFloor
104
105
Node {
106
// One meter in front, at standing eye height.
107
position: Qt.vector3d(0, 160, -100)
108
109
Model {
110
// #Cube is 100 units across, so this is a 20 cm cube.
111
source: "#Cube"
112
scale: Qt.vector3d(0.2, 0.2, 0.2)
113
materials: PrincipledMaterial { baseColor: "red" }
114
}
115
}
116
}
117
\endqml
118
119
An asset that was authored in meters therefore arrives in the scene 100
120
times too small. Correct this when importing the asset rather than by
121
scaling nodes in the scene, so that the values you read and write in QML
122
stay in centimeters. \l{Balsam Asset Import Tool}{Balsam} has the
123
\c{--globalScale} and \c{--globalScaleValue} options for this.
124
125
\section1 Where the scene origin is
126
127
\l{XrView::referenceSpace}{XrView.referenceSpace} determines what the point
128
\c{(0, 0, 0)} corresponds to in the physical world:
129
130
\list
131
\li \c XrView.ReferenceSpaceLocal places the scene origin at the
132
user's default view position, which is above the floor. To
133
place content relative to the floor, use
134
\c ReferenceSpaceLocalFloor or \c ReferenceSpaceStage instead.
135
\li \c XrView.ReferenceSpaceLocalFloor places the scene origin at
136
floor height, directly below the user's default view position.
137
\li \c XrView.ReferenceSpaceStage places the scene origin at floor
138
height, in the center of the area the user has set up.
139
\endlist
140
141
\image qt3dxr-reference-spaces.webp {Three views of a standing figure where
142
the scene origin sits at eye height for ReferenceSpaceLocal, on the
143
floor for ReferenceSpaceLocalFloor, and at the center of a marked
144
play area for ReferenceSpaceStage}
145
\caption The same model at \c{y: 0} in each of the three reference spaces.
146
The figure is 175 cm tall, and the floor grid is 50 cm.
147
148
Pick the reference space first, then position content, because the choice
149
shifts the whole scene vertically. A model placed at \c{y: 0} rests on the
150
floor in \c ReferenceSpaceLocalFloor and \c ReferenceSpaceStage, but floats
151
at head height in \c ReferenceSpaceLocal.
152
153
Not every platform honors the request. On visionOS the reference space is
154
always \c ReferenceSpaceLocalFloor, so an application written for
155
\c ReferenceSpaceLocal has to check the property at run time and adjust.
156
157
\l XrOrigin marks the scene origin in the scene. To move the user through
158
the virtual scene, move or rotate the \l XrOrigin. This does not change the
159
user's position in the physical world.
160
161
Scaling the \l XrOrigin scales the user's size in the virtual world: the
162
tracked motion of the user's head and hands is scaled with it. This can be
163
useful in VR, for example to let the user look at a scene from a giant's
164
view. In AR, or whenever scene distances must match physical distances, do
165
not scale the \l XrOrigin.
166
167
\section1 Tracked nodes and the XrOrigin
168
169
\image qt3dxr-origin-hierarchy.svg {A node tree where XrCamera,
170
XrController, and XrHandModel are children of the XrOrigin, while
171
scene content sits in a separate branch under the XrView}
172
173
\l XrCamera, \l XrController, and \l XrHandModel are tracked nodes. The XR
174
runtime updates their position and rotation every frame, so treat those
175
properties as read-only.
176
177
The tracked nodes must be children of the \l XrOrigin. The XR runtime
178
reports their poses relative to the reference space, and the \l XrOrigin
179
represents the reference space in the scene.
180
181
The tracking data that these types expose as plain values is also relative
182
to the \l XrOrigin. This includes
183
\l{XrController::pokePosition}{pokePosition},
184
\l{XrController::jointPositions}{jointPositions}, and the positions of
185
\l{XrSpatialAnchor}{spatial anchors}. These values match scene coordinates
186
only when the \l XrOrigin is at the scene root with no transform. To compare
187
them with other positions in the scene, map them to scene coordinates first:
188
189
\qml
190
XrController {
191
controller: XrController.RightController
192
193
// pokePosition is relative to the XrOrigin, so map it before
194
// hit-testing the scene with it.
195
property vector3d scenePos:
196
view.xrOrigin.mapPositionToScene(pokePosition)
197
198
onScenePosChanged: view.processTouch(scenePos, touchId)
199
}
200
\endqml
201
202
The \l{Qt Quick 3D - XR Simple Touch Example}{Touch Example} shows this in
203
context.
204
205
\l{XrCamera}{XrCamera}'s \l{XrCamera::clipNear}{clipNear} and
206
\l{XrCamera::clipFar}{clipFar} are in centimeters like every other
207
distance, so the useful range is roughly 1 to 10000. Unlike a
208
\l PerspectiveCamera, an \l XrCamera has no field of view to set: the
209
runtime supplies the projection for each eye.
210
211
\section1 Placing 2D content on a virtual surface
212
213
As in any Qt Quick 3D scene, you can place a plain \l Item directly under a
214
\l Node. One pixel of that item is then one scene unit, so in \qxr a
215
500-pixel-wide \l Rectangle is five meters wide. \l XrItem avoids this by
216
letting you set the surface size in centimeters. It also registers the
217
surface with its \l XrView, so \l{XrView::processTouch()}{processTouch()}
218
can send touch events to the content.
219
220
\l XrItem is the bridge between the two unit systems. Its
221
\l{XrItem::width}{width} and \l{XrItem::height}{height} are in centimeters,
222
while its \l{XrItem::contentItem}{contentItem} is an ordinary \l Item sized
223
in pixels. \l{XrItem::pixelsPerUnit}{pixelsPerUnit} is the conversion
224
factor between the two, and by default \l XrItem derives it so that the
225
content fills the surface:
226
227
\qml
228
XrItem {
229
// A 50 cm by 30 cm surface.
230
width: 50
231
height: 30
232
contentItem: Rectangle {
233
width: 500
234
height: 300
235
color: "white"
236
}
237
}
238
\endqml
239
240
\image qt3dxr-xritem-units.webp {A 50 by 30 centimeter virtual surface
241
holding a 500 by 300 pixel Qt Quick content item, with the node
242
origin marked at its top-left corner}
243
\caption With \l{XrItem::pixelsPerUnit}{pixelsPerUnit} at 10, one
244
centimeter of surface holds ten pixels of content.
245
246
With \l{XrItem::pixelsPerUnit}{pixelsPerUnit} at 10, \l XrItem scales the
247
content item to a tenth of its pixel size. \l XrItem scales the rendered
248
content item and does not render it at a higher resolution, so text on a
249
surface much larger than its content item looks coarse. To keep text sharp,
250
set \l{XrItem::manualPixelsPerUnit}{manualPixelsPerUnit} to \c true, set
251
\l{XrItem::pixelsPerUnit}{pixelsPerUnit}, and size the content item to match
252
the surface.
253
254
An \l XrItem's surface extends to the right of and \e below its own origin,
255
following the 2D convention. Offset it to center the surface on the node
256
position:
257
258
\qml
259
XrItem {
260
width: 50
261
height: 30
262
x: -width / 2
263
y: height / 2
264
}
265
\endqml
266
267
\section1 Related information
268
269
\list
270
\li \l{Qt Quick 3D Scenes with 2D Content}
271
\li \l{Locomotion in \qxr}
272
\li \l{\qxr QML Types}
273
\endlist
274
*/
qtquick3d
src
quick3d
doc
src
xr
qt3dxr-coordinates-and-units.qdoc
Generated on
for Qt by
1.18.0