Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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*/