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
qssgrendercamera.cpp
Go to the documentation of this file.
1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5
7
8#include <QtQuick3DRuntimeRender/private/qssgrendererutil_p.h>
9
10#include <QtQuick3DUtils/private/qssgutils_p.h>
11
12#include <QtGui/QVector2D>
13
14#include <qmath.h>
15
17
18namespace {
19
20float getAspectRatio(const QRectF &inViewport)
21{
22 return inViewport.height() != 0 ? inViewport.width() / inViewport.height() : 0.0f;
23}
24
25}
26
27QSSGRenderCamera::QSSGRenderCamera(QSSGRenderGraphObject::Type type)
28 : QSSGRenderNode(type)
29 , clipNear(10)
30 , clipFar(10000)
31 , fov(qDegreesToRadians(60.0f))
32 , fovHorizontal(false)
33 , enableFrustumClipping(true)
34{
35 Q_ASSERT(QSSGRenderCamera::isCamera(type));
36 markDirty(DirtyFlag::CameraDirty);
37}
38
39// Code for testing
40QSSGCameraGlobalCalculationResult QSSGRenderCamera::calculateGlobalVariables(const QRectF &inViewport)
41{
42 bool wasDirty = QSSGRenderNode::calculateGlobalVariables();
43 return QSSGCameraGlobalCalculationResult{ wasDirty, calculateProjection(inViewport) };
44}
45
46bool QSSGRenderCamera::calculateProjection(const QRectF &inViewport)
47{
48 bool retval = false;
49
50 const bool argumentsChanged = inViewport != previousInViewport;
51 if (!argumentsChanged && !isDirty(DirtyFlag::CameraDirty))
52 return true;
53 previousInViewport = inViewport;
54 clearDirty(DirtyFlag::CameraDirty);
55
56 switch (type) {
57 case QSSGRenderGraphObject::Type::OrthographicCamera:
58 retval = computeFrustumOrtho(inViewport);
59 break;
60 case QSSGRenderGraphObject::Type::PerspectiveCamera:
61 retval = computeFrustumPerspective(inViewport);
62 break;
63 case QSSGRenderGraphObject::Type::CustomCamera:
64 retval = true; // Do nothing
65 break;
66 case QSSGRenderGraphObject::Type::CustomFrustumCamera:
67 retval = computeCustomFrustum(inViewport);
68 break;
69 default:
70 Q_UNREACHABLE();
71 }
72
73 if (retval) {
74 float *writePtr(projection.data());
75 frustumScale.setX(writePtr[0]);
76 frustumScale.setY(writePtr[5]);
77 }
78 return retval;
79}
80
81//==============================================================================
82/**
83 * Compute the projection matrix for a perspective camera
84 * @return true if the computed projection matrix is valid
85 */
86bool QSSGRenderCamera::computeFrustumPerspective(const QRectF &inViewport)
87{
88 projection = QMatrix4x4();
89 projection.perspective(qRadiansToDegrees(verticalFov(inViewport)), getAspectRatio(inViewport), clipNear, clipFar);
90 return true;
91}
92
93bool QSSGRenderCamera::computeCustomFrustum(const QRectF &inViewport)
94{
95 Q_UNUSED(inViewport);
96 projection.setToIdentity();
97 projection.frustum(left, right, bottom, top, clipNear, clipFar);
98 return true;
99}
100
101void QSSGRenderCamera::calculateViewProjectionMatrix(const QMatrix4x4 &globalTransform,
102 const QMatrix4x4 &projection,
103 QMatrix4x4 &outMatrix)
104{
105 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
106 nonScaledGlobal.setColumn(0, globalTransform.column(0).normalized());
107 nonScaledGlobal.setColumn(1, globalTransform.column(1).normalized());
108 nonScaledGlobal.setColumn(2, globalTransform.column(2).normalized());
109 nonScaledGlobal.setColumn(3, globalTransform.column(3));
110 outMatrix = projection * nonScaledGlobal.inverted();
111}
112
113void QSSGRenderCamera::calculateViewProjectionMatrix(QMatrix4x4 &outMatrix, float clipNear, float clipFar) const
114{
115 if (qFuzzyIsNull(clipFar - clipNear)) {
116 qWarning() << "QSSGRenderCamera::calculateViewProjection: far == near";
117 return;
118 }
119
120 QMatrix4x4 proj = projection;
121 proj(2, 2) = -(clipFar + clipNear) / (clipFar - clipNear);
122 proj(2, 3) = -2 * clipFar * clipNear / (clipFar - clipNear);
123 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
124 nonScaledGlobal.setColumn(0, 0.5f * globalTransform.column(0).normalized());
125 nonScaledGlobal.setColumn(1, 0.5f * globalTransform.column(1).normalized());
126 nonScaledGlobal.setColumn(2, 0.5f * globalTransform.column(2).normalized());
127 nonScaledGlobal.setColumn(3, 0.5f * globalTransform.column(3));
128 outMatrix = proj * nonScaledGlobal.inverted();
129}
130
131//==============================================================================
132/**
133 * Compute the projection matrix for a orthographic camera
134 * @return true if the computed projection matrix is valid
135 */
136bool QSSGRenderCamera::computeFrustumOrtho(const QRectF &inViewport)
137{
138 projection = QMatrix4x4();
139 float halfWidth = inViewport.width() / 2.0f / horizontalMagnification / dpr;
140 float halfHeight = inViewport.height() / 2.0f / verticalMagnification / dpr;
141 projection.ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, clipNear, clipFar);
142 return true;
143}
144
145float QSSGRenderCamera::getOrthographicScaleFactor(const QRectF &inViewport) const
146{
147 Q_UNUSED(inViewport);
148 return qMax(horizontalMagnification, verticalMagnification);
149}
150
151static QQuaternion rotationQuaternionForLookAt(const QVector3D &sourcePosition,
152 const QVector3D &sourceDirection,
153 const QVector3D &targetPosition,
154 const QVector3D &upDirection)
155{
156 QVector3D targetDirection = sourcePosition - targetPosition;
157 targetDirection.normalize();
158
159 QVector3D rotationAxis = QVector3D::crossProduct(sourceDirection, targetDirection);
160
161 const QVector3D normalizedAxis = rotationAxis.normalized();
162 if (qFuzzyIsNull(normalizedAxis.lengthSquared()))
163 rotationAxis = upDirection;
164
165 float dot = QVector3D::dotProduct(sourceDirection, targetDirection);
166 float rotationAngle = float(qRadiansToDegrees(qAcos(qreal(dot))));
167
168 return QQuaternion::fromAxisAndAngle(rotationAxis, rotationAngle);
169}
170
171void QSSGRenderCamera::lookAt(const QVector3D &inCameraPos, const QVector3D &inUpDir, const QVector3D &inTargetPos, const QVector3D &pivot)
172{
173 QQuaternion rotation = rotationQuaternionForLookAt(inCameraPos, getScalingCorrectDirection(), inTargetPos, inUpDir.normalized());
174 globalTransform = localTransform = QSSGRenderNode::calculateTransformMatrix(inCameraPos, QSSGRenderNode::initScale, pivot, rotation);
175 QSSGRenderNode::markDirty(QSSGRenderNode::DirtyFlag::TransformDirty);
176}
177
178void QSSGRenderCamera::calculateViewProjectionMatrix(QMatrix4x4 &outMatrix) const
179{
180 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
181 nonScaledGlobal.setColumn(0, globalTransform.column(0).normalized());
182 nonScaledGlobal.setColumn(1, globalTransform.column(1).normalized());
183 nonScaledGlobal.setColumn(2, globalTransform.column(2).normalized());
184 nonScaledGlobal.setColumn(3, globalTransform.column(3));
185 outMatrix = projection * nonScaledGlobal.inverted();
186}
187
188void QSSGRenderCamera::calculateViewProjectionWithoutTranslation(float clipNear, float clipFar, QMatrix4x4 &outMatrix) const
189{
190 if (qFuzzyIsNull(clipFar - clipNear)) {
191 qWarning() << "QSSGRenderCamera::calculateViewProjection: far == near";
192 return;
193 }
194
195 QMatrix4x4 proj = projection;
196 proj(2, 2) = -(clipFar + clipNear) / (clipFar - clipNear);
197 proj(2, 3) = -2 * clipFar * clipNear / (clipFar - clipNear);
198 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
199 nonScaledGlobal.setColumn(0, globalTransform.column(0).normalized());
200 nonScaledGlobal.setColumn(1, globalTransform.column(1).normalized());
201 nonScaledGlobal.setColumn(2, globalTransform.column(2).normalized());
202 nonScaledGlobal.setColumn(3, QVector4D(0, 0, 0, 1));
203 outMatrix = proj * nonScaledGlobal.inverted();
204}
205
206QSSGRenderRay QSSGRenderCamera::unproject(const QVector2D &inViewportRelativeCoords,
207 const QRectF &inViewport) const
208{
209 QSSGRenderRay theRay;
210 QVector2D normalizedCoords = QSSGUtils::rect::relativeToNormalizedCoordinates(inViewport, inViewportRelativeCoords);
211 QVector3D &outOrigin(theRay.origin);
212 QVector3D &outDir(theRay.direction);
213 QVector2D inverseFrustumScale(1.0f / frustumScale.x(), 1.0f / frustumScale.y());
214 QVector2D scaledCoords(inverseFrustumScale.x() * normalizedCoords.x(), inverseFrustumScale.y() * normalizedCoords.y());
215
216 if (type == QSSGRenderCamera::Type::OrthographicCamera) {
217 outOrigin.setX(scaledCoords.x());
218 outOrigin.setY(scaledCoords.y());
219 outOrigin.setZ(0.0f);
220
221 outDir.setX(0.0f);
222 outDir.setY(0.0f);
223 outDir.setZ(-1.0f);
224 } else {
225 outOrigin.setX(0.0f);
226 outOrigin.setY(0.0f);
227 outOrigin.setZ(0.0f);
228
229 outDir.setX(scaledCoords.x());
230 outDir.setY(scaledCoords.y());
231 outDir.setZ(-1.0f);
232 }
233
234 outOrigin = QSSGUtils::mat44::transform(globalTransform, outOrigin);
235 QMatrix3x3 theNormalMatrix = calculateNormalMatrix();
236
237 outDir = QSSGUtils::mat33::transform(theNormalMatrix, outDir);
238 outDir.normalize();
239
240 return theRay;
241}
242
243QVector3D QSSGRenderCamera::unprojectToPosition(const QVector3D &inGlobalPos, const QSSGRenderRay &inRay) const
244{
245 QVector3D theCameraDir = getDirection();
246 QVector3D theObjGlobalPos = inGlobalPos;
247 float theDistance = -1.0f * QVector3D::dotProduct(theObjGlobalPos, theCameraDir);
248 QSSGPlane theCameraPlane(theCameraDir, theDistance);
249 return QSSGRenderRay::intersect(theCameraPlane, inRay).value_or(QVector3D{});
250}
251
252float QSSGRenderCamera::verticalFov(float aspectRatio) const
253{
254 return fovHorizontal ? float(2.0 * qAtan(qTan(qreal(fov) / 2.0) / qreal(aspectRatio))) : fov;
255}
256
257float QSSGRenderCamera::verticalFov(const QRectF &inViewport) const
258{
259 return verticalFov(getAspectRatio(inViewport));
260}
261
262void QSSGRenderCamera::markDirty(DirtyFlag dirtyFlag)
263{
264 cameraDirtyFlags |= FlagT(dirtyFlag);
265 QSSGRenderNode::markDirty(QSSGRenderNode::DirtyFlag::SubNodeDirty);
266}
267
268void QSSGRenderCamera::clearDirty(DirtyFlag dirtyFlag)
269{
270 cameraDirtyFlags &= ~FlagT(dirtyFlag);
271 QSSGRenderNode::clearDirty(QSSGRenderNode::DirtyFlag::SubNodeDirty);
272}
273
274static float getZNear(const QMatrix4x4 &projection)
275{
276 const float *data = projection.constData();
277 QSSGPlane plane(QVector3D(data[3] + data[2], data[7] + data[6], data[11] + data[10]), -data[15] - data[14]);
278 plane.normalize();
279 return plane.d;
280}
281
282static QVector2D getViewportHalfExtents(const QMatrix4x4 &projection) {
283 const float *data = projection.constData();
284
285 QSSGPlane nearPlane(QVector3D(data[3] + data[2], data[7] + data[6], data[11] + data[10]), -data[15] - data[14]);
286 nearPlane.normalize();
287 QSSGPlane rightPlane(QVector3D(data[3] - data[0], data[7] - data[4], data[11] - data[8]), -data[15] + data[12]);
288 rightPlane.normalize();
289 QSSGPlane topPlane(QVector3D(data[3] - data[1], data[7] - data[5], data[11] - data[9]), -data[15] + data[13]);
290 topPlane.normalize();
291
292 // Get intersection the 3 planes
293 float denom = QVector3D::dotProduct(QVector3D::crossProduct(nearPlane.n, rightPlane.n), topPlane.n);
294 if (qFuzzyIsNull(denom))
295 return QVector2D();
296
297 QVector3D intersection = (QVector3D::crossProduct(rightPlane.n, topPlane.n) * nearPlane.d +
298 (QVector3D::crossProduct(topPlane.n, nearPlane.n) * rightPlane.d) +
299 (QVector3D::crossProduct(nearPlane.n, rightPlane.n) * topPlane.d)) / denom;
300
301 return QVector2D(intersection.x(), intersection.y());
302}
303
304float QSSGRenderCamera::getLevelOfDetailMultiplier() const
305{
306 if (type == QSSGRenderGraphObject::Type::OrthographicCamera)
307 return getViewportHalfExtents(projection).x();
308
309 float zn = getZNear(projection);
310 float width = getViewportHalfExtents(projection).x() * 2.0;
311 return 1.0 / (zn / width);
312
313}
314
315QT_END_NAMESPACE
Combined button and popup list for selecting options.
float getAspectRatio(const QRectF &inViewport)
static QVector2D getViewportHalfExtents(const QMatrix4x4 &projection)
static float getZNear(const QMatrix4x4 &projection)
static QQuaternion rotationQuaternionForLookAt(const QVector3D &sourcePosition, const QVector3D &sourceDirection, const QVector3D &targetPosition, const QVector3D &upDirection)