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
qquick3dcamera.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7
8#include <QtQuick3DRuntimeRender/private/qssgrendercamera_p.h>
9
12
13#include <QtMath>
14#include <QtQuick3DUtils/private/qssgutils_p.h>
15
17
19
20/*!
21 \qmltype Camera
22 \inherits Node
23 \inqmlmodule QtQuick3D
24 \brief Defines an abstract base for Cameras.
25
26 A Camera defines how the content of the 3D scene is projected onto a 2D surface,
27 such as a View3D. A scene needs at least one Camera in order to visualize its
28 contents.
29
30 It is possible to position and rotate the Camera like any other spatial \l{QtQuick3D::Node}{Node} in
31 the scene. The \l{QtQuick3D::Node}{Node}'s location and orientation determines where the Camera is in
32 the scene, and what direction it is facing. The default orientation of the camera
33 has its forward vector pointing along the negative Z axis and its up vector along
34 the positive Y axis.
35
36 Together with the position and orientation, the frustum defines which parts of
37 a scene are visible to the Camera and how they are projected onto the 2D surface.
38 The different Camera subtypes provide multiple options to determine the shape of the
39 Camera's frustum.
40
41 \list
42 \li PerspectiveCamera provides a camera with a pyramid-shaped frustum, where objects are
43 projected so that those further away from the camera appear to be smaller. This is the
44 most commonly used camera type, and corresponds to how most real world cameras work.
45 \li OrthographicCamera provides a camera where the lines of the frustum are parallel,
46 making the perceived scale of an object unaffected by its distance to the camera. Typical
47 use cases for this type of camera are CAD (Computer-Assisted Design) applications and
48 cartography.
49 \li FrustumCamera is a perspective camera type where the frustum can be freely customized
50 by the coordinates of its intersection with the near plane. It can be useful if an
51 asymmetrical camera frustum is needed.
52 \li CustomCamera is a camera type where the projection matrix can be freely customized,
53 and can be useful for advanced users who wish to calculate their own projection matrix.
54 \endlist
55
56 To illustrate the difference, these screenshots show the same scene as projected by a
57 PerspectiveCamera and an OrthographicCamera. Notice how the red box is smaller than the
58 green box in the image rendered using the perspective projection.
59 \table
60 \header
61 \li Perspective camera
62 \li Orthographic camera
63 \row
64 \li \image perspectivecamera.png
65 {Perspective camera view of plate}
66 \li \image orthographiccamera.png
67 {Orthographic camera view of plate}
68 \endtable
69
70 \sa {Qt Quick 3D - View3D Example}
71*/
72
73/*!
74 \internal
75*/
76QQuick3DCamera::QQuick3DCamera(QQuick3DNodePrivate &dd, QQuick3DNode *parent)
77 : QQuick3DNode(dd, parent) {}
78
79/*!
80 \qmlproperty bool Camera::frustumCullingEnabled
81
82 When this property is \c true, objects outside the camera frustum will be culled, meaning they will
83 not be passed to the renderer. By default this property is set to \c false. For scenes where all or
84 most objects are inside the camera frustum, frustum culling is an unnecessary performance overhead.
85 But for complex scenes where large parts are located outside the camera's view, enabling frustum
86 culling may improve performance.
87*/
88bool QQuick3DCamera::frustumCullingEnabled() const
89{
90 return m_frustumCullingEnabled;
91}
92
93void QQuick3DCamera::setFrustumCullingEnabled(bool frustumCullingEnabled)
94{
95 if (m_frustumCullingEnabled == frustumCullingEnabled)
96 return;
97
98 m_frustumCullingEnabled = frustumCullingEnabled;
99 emit frustumCullingEnabledChanged();
100 update();
101}
102
103/*!
104 \qmlproperty Node Camera::lookAtNode
105
106 If this property is set to a \c non-null value, the rotation of this camera is automatically
107 updated so that this camera keeps looking at the specified node whenever the scene position of
108 this camera or the specified node changes.
109 By default this property is set to \c{null}.
110
111 \sa lookAt
112*/
113QQuick3DNode *QQuick3DCamera::lookAtNode() const
114{
115 return m_lookAtNode;
116}
117
118void QQuick3DCamera::setLookAtNode(QQuick3DNode *node)
119{
120 if (m_lookAtNode == node)
121 return;
122
123 if (m_lookAtNode) {
124 disconnect(m_lookAtNode, &QQuick3DNode::scenePositionChanged, this, &QQuick3DCamera::updateLookAt);
125 disconnect(this, &QQuick3DNode::scenePositionChanged, this, &QQuick3DCamera::updateLookAt);
126 }
127
128 m_lookAtNode = node;
129
130 if (m_lookAtNode) {
131 connect(m_lookAtNode, &QQuick3DNode::scenePositionChanged, this, &QQuick3DCamera::updateLookAt);
132 connect(this, &QQuick3DNode::scenePositionChanged, this, &QQuick3DCamera::updateLookAt);
133 }
134
135 emit lookAtNodeChanged();
136 updateLookAt();
137}
138
139/*!
140 \qmlmethod vector3d Camera::mapToViewport(vector3d scenePos)
141
142 Transforms \a scenePos from global scene space (3D) into viewport space (2D).
143
144 The returned position is normalized, with the top-left of the viewport
145 at [0, 0] and the bottom-right at [1, 1]. The returned z-value will contain
146 the distance from the near clip plane of the frustum (clipNear) to \a scenePos in
147 scene coordinates. If the distance is negative, the point is behind camera.
148
149 If \a scenePos cannot successfully be mapped to a position in the viewport, a
150 position of [0, 0, 0] is returned.
151
152 \sa mapFromViewport(), {View3D::mapFrom3DScene()}{View3D.mapFrom3DScene()}
153*/
154QVector3D QQuick3DCamera::mapToViewport(const QVector3D &scenePos) const
155{
156 QSSGRenderCamera *cameraNode = static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(this)->spatialNode);
157 if (!cameraNode)
158 return QVector3D(0, 0, 0);
159
160 QVector4D scenePosRightHand(scenePos, 1);
161
162 // Transform position
163 const QMatrix4x4 sceneToCamera = sceneTransform().inverted();
164 const QMatrix4x4 projectionViewMatrix = cameraNode->projection * sceneToCamera;
165 const QVector4D transformedScenePos = QSSGUtils::mat44::transform(projectionViewMatrix, scenePosRightHand);
166
167 if (qFuzzyIsNull(transformedScenePos.w()) || qIsNaN(transformedScenePos.w()))
168 return QVector3D(0, 0, 0);
169
170 // Normalize scenePosView between [-1, 1]
171 QVector3D scenePosView = transformedScenePos.toVector3D() / transformedScenePos.w();
172
173 // Set z to be the scene distance from clipNear so that the return value
174 // can be used as argument to viewportToscene() to reverse the call.
175 const QVector4D clipNearPos(scenePosView.x(), scenePosView.y(), -1, 1);
176 auto invProj = projectionViewMatrix.inverted();
177 const QVector4D clipNearPosTransformed = QSSGUtils::mat44::transform(invProj, clipNearPos);
178 if (qFuzzyIsNull(clipNearPosTransformed.w()) || qIsNaN(clipNearPosTransformed.w()))
179 return QVector3D(0, 0, 0);
180 const QVector4D clipNearPosScene = clipNearPosTransformed / clipNearPosTransformed.w();
181 QVector4D clipFarPos = clipNearPos;
182 clipFarPos.setZ(0);
183 const QVector4D clipFarPosTransformed = QSSGUtils::mat44::transform(invProj, clipFarPos);
184 if (qFuzzyIsNull(clipFarPosTransformed.w()) || qIsNaN(clipFarPosTransformed.w()))
185 return QVector3D(0, 0, 0);
186 const QVector4D clipFarPosScene = clipFarPosTransformed / clipFarPosTransformed.w();
187 const QVector3D direction = (clipFarPosScene - clipNearPosScene).toVector3D();
188 const QVector3D scenePosVec = (scenePosRightHand - clipNearPosScene).toVector3D();
189
190 const float distanceToScenePos = scenePosVec.length()
191 * (QVector3D::dotProduct(direction, scenePosVec) > 0.f ? 1 : -1);
192
193 scenePosView.setZ(distanceToScenePos);
194
195 // Convert x and y to be between [0, 1]
196 scenePosView.setX((scenePosView.x() / 2) + 0.5f);
197 scenePosView.setY((scenePosView.y() / 2) + 0.5f);
198 // And convert origin from bottom-left to top-left
199 scenePosView.setY(1 - scenePosView.y());
200 return scenePosView;
201}
202
203/*!
204 \qmlmethod vector3d Camera::mapFromViewport(vector3d viewportPos)
205
206 Transforms \a viewportPos from viewport space (2D) into global scene space (3D).
207
208 The x- and y-values of \a viewportPos must be normalized, with the top-left
209 of the viewport at [0, 0] and the bottom-right at [1, 1]. The z-value is interpreted
210 as the distance from the near clip plane of the frustum (clipNear).
211
212 If \a viewportPos cannot successfully be mapped to a position in the scene, a position
213 of [0, 0, 0] is returned.
214
215 \sa mapToViewport, {View3D::mapTo3DScene()}{View3D.mapTo3DScene()}
216*/
217QVector3D QQuick3DCamera::mapFromViewport(const QVector3D &viewportPos) const
218{
219 QSSGRenderCamera *cameraNode = static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(this)->spatialNode);
220 if (!cameraNode)
221 return QVector3D(0, 0, 0);
222
223 // Pick two positions in the frustum
224 QVector4D clipNearPos(viewportPos, 1);
225 // Convert origin from top-left to bottom-left
226 clipNearPos.setY(1 - clipNearPos.y());
227 // Convert to homogenous position between [-1, 1]
228 clipNearPos.setX((clipNearPos.x() * 2.0f) - 1.0f);
229 clipNearPos.setY((clipNearPos.y() * 2.0f) - 1.0f);
230 QVector4D clipFarPos = clipNearPos;
231 // clipNear: z = -1, clipFar: z = 1. It's recommended to use 0 as
232 // far pos instead of clipFar because of infinite projection issues.
233 clipNearPos.setZ(-1);
234 clipFarPos.setZ(0);
235
236 // Transform position to scene
237 const QMatrix4x4 sceneToCamera = sceneTransform().inverted();
238 const QMatrix4x4 projectionViewMatrixInv = (cameraNode->projection * sceneToCamera).inverted();
239 const QVector4D transformedClipNearPos = QSSGUtils::mat44::transform(projectionViewMatrixInv, clipNearPos);
240 const QVector4D transformedClipFarPos = QSSGUtils::mat44::transform(projectionViewMatrixInv, clipFarPos);
241
242 if (qFuzzyIsNull(transformedClipNearPos.w()) || qIsNaN(transformedClipNearPos.w()) ||
243 qFuzzyIsNull(transformedClipFarPos.w()) || qIsNaN(transformedClipFarPos.w()))
244 return QVector3D(0, 0, 0);
245
246 // Reverse the projection
247 const QVector3D clipNearPosScene = transformedClipNearPos.toVector3D()
248 / transformedClipNearPos.w();
249 const QVector3D clipFarPosScene = transformedClipFarPos.toVector3D()
250 / transformedClipFarPos.w();
251
252 // Calculate the position in the scene
253 const QVector3D direction = (clipFarPosScene - clipNearPosScene).normalized();
254 const float distanceFromClipNear = viewportPos.z();
255 QVector3D scenePos = clipNearPosScene + (direction * distanceFromClipNear);
256
257 return scenePos;
258}
259
260/*!
261 * \internal
262 */
263QVector3D QQuick3DCamera::mapToViewport(const QVector3D &scenePos,
264 qreal width,
265 qreal height)
266{
267 // We can be called before a spatial node is created, if that is the case, create the node now.
268 if (QSSGRenderCamera *cameraNode = static_cast<QSSGRenderCamera *>(updateSpatialNode(QQuick3DObjectPrivate::get(this)->spatialNode))) {
269 QQuick3DObjectPrivate::get(this)->spatialNode = cameraNode;
270 QSSGRenderCamera::Configuration camConfig { cameraNode->getDpr(), 1.0f /* ssaa multiplier */ };
271 QSSGRenderCamera::calculateProjectionInternal(*cameraNode, QRect(0, 0, width * camConfig.dpr, height * camConfig.dpr), camConfig);
272 }
273
274 return QQuick3DCamera::mapToViewport(scenePos);
275}
276
277/*!
278 * \internal
279 */
280QVector3D QQuick3DCamera::mapFromViewport(const QVector3D &viewportPos,
281 qreal width,
282 qreal height)
283{
284 // We can be called before a spatial node is created, if that is the case, create the node now.
285 if (QSSGRenderCamera *cameraNode = static_cast<QSSGRenderCamera *>(updateSpatialNode(QQuick3DObjectPrivate::get(this)->spatialNode))) {
286 QQuick3DObjectPrivate::get(this)->spatialNode = cameraNode;
287 QSSGRenderCamera::Configuration camConfig { cameraNode->getDpr(), 1.0f /* ssaa multiplier */ };
288 QSSGRenderCamera::calculateProjectionInternal(*cameraNode, QRect(0, 0, width * camConfig.dpr, height * camConfig.dpr));
289 }
290
291 return QQuick3DCamera::mapFromViewport(viewportPos);
292}
293
294/*!
295 \qmlmethod vector3d Camera::lookAt(vector3d scenePos)
296 \since 5.15
297
298 Sets the rotation value of the Camera so that it is pointing at \a scenePos.
299*/
300
301void QQuick3DCamera::lookAt(const QVector3D &scenePos)
302{
303 // Assumption: we never want the camera to roll.
304 // We use Euler angles here to avoid roll to sneak in through numerical instability.
305
306 const auto &targetPosition = scenePos;
307 auto sourcePosition = scenePosition();
308
309 QVector3D targetVector = sourcePosition - targetPosition;
310
311 float yaw = qRadiansToDegrees(atan2(targetVector.x(), targetVector.z()));
312
313 QVector2D p(targetVector.x(), targetVector.z()); // yaw vector projected to horizontal plane
314 float pitch = qRadiansToDegrees(atan2(p.length(), targetVector.y())) - 90;
315
316 const float previousRoll = eulerRotation().z();
317 setEulerRotation(QVector3D(pitch, yaw, previousRoll));
318}
319
320/*!
321 \qmlmethod vector3d Camera::lookAt(QtQuick3D::Node node)
322 \since 5.15
323
324 Sets the rotation value of the Camera so that it is pointing at \a node.
325*/
326
327void QQuick3DCamera::lookAt(QQuick3DNode *node)
328{
329 if (!node)
330 return;
331 lookAt(node->scenePosition());
332}
333
334void QQuick3DCamera::updateGlobalVariables(const QRectF &inViewport)
335{
336 QSSGRenderCamera *node = static_cast<QSSGRenderCamera *>(QQuick3DObjectPrivate::get(this)->spatialNode);
337 if (node)
338 QSSGRenderCamera::calculateProjectionInternal(*node, inViewport);
339}
340
341/*!
342 * \internal
343 */
344QSSGRenderGraphObject *QQuick3DCamera::updateSpatialNode(QSSGRenderGraphObject *node)
345{
346 if (!node) {
347 markAllDirty();
348 node = new QSSGRenderCamera(QQuick3DObjectPrivate::get(this)->type);
349 }
350
351 QQuick3DNode::updateSpatialNode(node);
352
353 QSSGRenderCamera *camera = static_cast<QSSGRenderCamera *>(node);
354 if (qUpdateIfNeeded(camera->enableFrustumClipping, m_frustumCullingEnabled))
355 camera->markDirty(QSSGRenderCamera::DirtyFlag::CameraDirty);
356 if (qUpdateIfNeeded(camera->levelOfDetailPixelThreshold, m_levelOfDetailBias))
357 camera->markDirty(QSSGRenderCamera::DirtyFlag::CameraDirty);
358 if (qUpdateIfNeeded(camera->layerMask, quint32(QQuick3DNodePrivate::get(this)->m_tag)))
359 camera->markDirty(QSSGRenderCamera::DirtyFlag::LayerMaskDirty);
360
361 return node;
362}
363
364void QQuick3DCamera::updateLookAt()
365{
366 if (m_lookAtNode)
367 lookAt(m_lookAtNode);
368}
369
370/*!
371 \qmlproperty real Camera::levelOfDetailBias
372 \since 6.5
373
374 This property changes the size a model needs to be when rendered before the
375 automatic level of detail meshes are used. Each generated level of detail
376 mesh contains an ideal size value that each level should be shown, which is
377 a ratio of how much of the rendered scene will be that mesh. A model that
378 represents only a few pixels on screen will not require the full geometry
379 to look correct, so a lower level of detail mesh will be used instead in
380 this case. This value is a bias to the ideal value such that a value smaller
381 than \c 1.0 will require an even smaller rendered size before switching to
382 a lesser level of detail. Values above \c 1.0 will lead to lower levels of detail
383 being used sooner. A value of \c 0.0 will disable the usage of levels of detail
384 completely.
385
386 The default value is \c 1.0
387
388 \note This property will only have an effect on Models with geomtry containing
389 levels of detail.
390
391 \sa Model::levelOfDetailBias
392*/
393
394float QQuick3DCamera::levelOfDetailBias() const
395{
396 return m_levelOfDetailBias;
397}
398
399void QQuick3DCamera::setLevelOfDetailBias(float newLevelOfDetailBias)
400{
401 if (qFuzzyCompare(m_levelOfDetailBias, newLevelOfDetailBias))
402 return;
403 m_levelOfDetailBias = newLevelOfDetailBias;
404 emit levelOfDetailBiasChanged();
405 update();
406}
407
408QT_END_NAMESPACE
Combined button and popup list for selecting options.