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
6#include "qssgrendercamera_p.h"
7
8#include <QtQuick3DRuntimeRender/private/qssgrendererutil_p.h>
9
10#include <QtQuick3DUtils/private/qssgutils_p.h>
11
12#include <QtGui/qquaternion.h>
13#include <QtGui/QVector2D>
14
15#include <qmath.h>
16
17QT_BEGIN_NAMESPACE
18
19static inline float getAspectRatio(const QRectF &inViewport)
20{
21 return inViewport.height() != 0 ? inViewport.width() / inViewport.height() : 0.0f;
22}
23
24QSSGRenderCamera::QSSGRenderCamera(QSSGRenderGraphObject::Type type)
25 : QSSGRenderNode(type)
26{
27 Q_ASSERT(QSSGRenderCamera::isCamera(type));
28 markDirty(dirtyFlag: DirtyFlag::CameraDirty);
29}
30
31bool QSSGRenderCamera::calculateProjectionInternal(QSSGRenderCamera &camera,
32 const QRectF &inViewport,
33 Configuration config)
34{
35 return camera.calculateProjection(inViewport, config);
36}
37
38bool QSSGRenderCamera::calculateProjection(const QRectF &inViewport, Configuration config)
39{
40 bool retval = false;
41
42 // NOTE: We always update the dpr, under normal surcumstances it won't change, but we have at least
43 // one broken use-cases (front-end camera class) where we depend on the dpr being available from
44 // here...
45 dpr = config.dpr;
46
47 const bool argumentsChanged = inViewport != previousInViewport;
48 if (!argumentsChanged && !isDirty(dirtyFlag: DirtyFlag::CameraDirty))
49 return true;
50 previousInViewport = inViewport;
51 clearDirty(dirtyFlag: DirtyFlag::CameraDirty);
52
53 switch (type) {
54 case QSSGRenderGraphObject::Type::OrthographicCamera:
55 retval = computeFrustumOrtho(inViewport, clipPlanes, magnification, config, outProjection&: projection);
56 break;
57 case QSSGRenderGraphObject::Type::PerspectiveCamera:
58 retval = computeFrustumPerspective(inViewport, fov, clipPlanes, outProjection&: projection);
59 break;
60 case QSSGRenderGraphObject::Type::CustomCamera:
61 retval = true; // Do nothing
62 break;
63 case QSSGRenderGraphObject::Type::CustomFrustumCamera:
64 retval = computeCustomFrustum(frustum: customFrustum, clipPlanes, outProjection&: projection);
65 break;
66 default:
67 Q_UNREACHABLE();
68 }
69
70 if (retval) {
71 float *writePtr(projection.data());
72 frustumScale.setX(writePtr[0]);
73 frustumScale.setY(writePtr[5]);
74 }
75 return retval;
76}
77
78//==============================================================================
79/**
80 * Compute the projection matrix for a perspective camera
81 * @return true if the computed projection matrix is valid
82 */
83bool QSSGRenderCamera::computeFrustumPerspective(QRectF inViewport,
84 QSSGRenderCamera::FieldOfView fov,
85 QSSGRenderCamera::ClipPlanes clipPlane,
86 QMatrix4x4 &outProjection)
87{
88 outProjection.setToIdentity();
89 const float aspectRatio = getAspectRatio(inViewport);
90 const auto vfov = fov.asVerticalFov(aspectRatio);
91 outProjection.perspective(verticalAngle: vfov.degrees(), aspectRatio, nearPlane: clipPlane.clipNear(), farPlane: clipPlane.clipFar());
92 return true;
93}
94
95bool QSSGRenderCamera::computeCustomFrustum(QSSGRenderCamera::Frustum frustum,
96 QSSGRenderCamera::ClipPlanes clipPlanes,
97 QMatrix4x4 &outProjection)
98{
99 outProjection.setToIdentity();
100 outProjection.frustum(left: frustum.left, right: frustum.right, bottom: frustum.bottom, top: frustum.top, nearPlane: clipPlanes.clipNear(), farPlane: clipPlanes.clipFar());
101 return true;
102}
103
104void QSSGRenderCamera::calculateViewProjectionMatrix(const QMatrix4x4 &globalTransform,
105 const QMatrix4x4 &projection,
106 QMatrix4x4 &outMatrix)
107{
108 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
109 nonScaledGlobal.setColumn(index: 0, value: globalTransform.column(index: 0).normalized());
110 nonScaledGlobal.setColumn(index: 1, value: globalTransform.column(index: 1).normalized());
111 nonScaledGlobal.setColumn(index: 2, value: globalTransform.column(index: 2).normalized());
112 nonScaledGlobal.setColumn(index: 3, value: globalTransform.column(index: 3));
113 outMatrix = projection * nonScaledGlobal.inverted();
114}
115
116//==============================================================================
117/**
118 * Compute the outProjection matrix for a orthographic camera
119 * @return true if the computed outProjection matrix is valid
120 */
121bool QSSGRenderCamera::computeFrustumOrtho(QRectF inViewport,
122 QSSGRenderCamera::ClipPlanes clipPlanes,
123 QSSGRenderCamera::Magnification magnification,
124 Configuration config,
125 QMatrix4x4 &outProjection)
126{
127
128
129 outProjection.setToIdentity();
130 // When using ssaa we need to zoom with the ssaa multiplier since otherwise the
131 // orthographic camera will be zoomed out due to the bigger viewport. We therefore
132 // scale the magnification before calulating the projection.
133 // Since the same camera can be used in several View3Ds with or without ssaa we
134 // cannot store the magnification permanently.
135 magnification *= config.ssaaMultiplier;
136 const float halfWidth = inViewport.width() / 2.0f / magnification.horizontal() / config.dpr;
137 const float halfHeight = inViewport.height() / 2.0f / magnification.vertical() / config.dpr;
138 outProjection.ortho(left: -halfWidth, right: halfWidth, bottom: -halfHeight, top: halfHeight, nearPlane: clipPlanes.clipNear(), farPlane: clipPlanes.clipFar());
139 return true;
140}
141
142static QQuaternion rotationQuaternionForLookAt(const QVector3D &sourcePosition,
143 const QVector3D &sourceDirection,
144 const QVector3D &targetPosition,
145 const QVector3D &upDirection)
146{
147 QVector3D targetDirection = sourcePosition - targetPosition;
148 targetDirection.normalize();
149
150 QVector3D rotationAxis = QVector3D::crossProduct(v1: sourceDirection, v2: targetDirection);
151
152 const QVector3D normalizedAxis = rotationAxis.normalized();
153 if (qFuzzyIsNull(f: normalizedAxis.lengthSquared()))
154 rotationAxis = upDirection;
155
156 float dot = QVector3D::dotProduct(v1: sourceDirection, v2: targetDirection);
157 float rotationAngle = float(qRadiansToDegrees(radians: qAcos(v: qreal(dot))));
158
159 return QQuaternion::fromAxisAndAngle(axis: rotationAxis, angle: rotationAngle);
160}
161
162void QSSGRenderCamera::lookAt(const QVector3D &inCameraPos, const QVector3D &inUpDir, const QVector3D &inTargetPos, const QVector3D &pivot)
163{
164 QQuaternion rotation = rotationQuaternionForLookAt(sourcePosition: inCameraPos, sourceDirection: getScalingCorrectDirection(globalTransform: localTransform), targetPosition: inTargetPos, upDirection: inUpDir.normalized());
165 localTransform = QSSGRenderNode::calculateTransformMatrix(position: inCameraPos, scale: QSSGRenderNode::initScale, pivot, rotation);
166 QSSGRenderNode::markDirty(dirtyFlag: QSSGRenderNode::DirtyFlag::TransformDirty);
167}
168
169void QSSGRenderCamera::calculateViewProjectionMatrix(const QMatrix4x4 &globalTransform, QMatrix4x4 &outMatrix) const
170{
171 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
172 nonScaledGlobal.setColumn(index: 0, value: globalTransform.column(index: 0).normalized());
173 nonScaledGlobal.setColumn(index: 1, value: globalTransform.column(index: 1).normalized());
174 nonScaledGlobal.setColumn(index: 2, value: globalTransform.column(index: 2).normalized());
175 nonScaledGlobal.setColumn(index: 3, value: globalTransform.column(index: 3));
176 outMatrix = projection * nonScaledGlobal.inverted();
177}
178
179void QSSGRenderCamera::calculateViewProjectionWithoutTranslation(const QMatrix4x4 &globalTransform, float clipNear, float clipFar, QMatrix4x4 &outMatrix) const
180{
181 if (qFuzzyIsNull(f: clipFar - clipNear)) {
182 qWarning() << "QSSGRenderCamera::calculateViewProjection: far == near";
183 return;
184 }
185
186 QMatrix4x4 proj = projection;
187 proj(2, 2) = -(clipFar + clipNear) / (clipFar - clipNear);
188 proj(2, 3) = -2 * clipFar * clipNear / (clipFar - clipNear);
189 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
190 nonScaledGlobal.setColumn(index: 0, value: globalTransform.column(index: 0).normalized());
191 nonScaledGlobal.setColumn(index: 1, value: globalTransform.column(index: 1).normalized());
192 nonScaledGlobal.setColumn(index: 2, value: globalTransform.column(index: 2).normalized());
193 nonScaledGlobal.setColumn(index: 3, value: QVector4D(0, 0, 0, 1));
194 outMatrix = proj * nonScaledGlobal.inverted();
195}
196
197QSSGRenderRay QSSGRenderCamera::unproject(const QMatrix4x4 &globalTransform,
198 const QVector2D &inViewportRelativeCoords,
199 const QRectF &inViewport) const
200{
201 QSSGRenderRay theRay;
202 QVector2D normalizedCoords = QSSGUtils::rect::relativeToNormalizedCoordinates(r: inViewport, rectRelativeCoords: inViewportRelativeCoords);
203 QVector3D &outOrigin(theRay.origin);
204 QVector3D &outDir(theRay.direction);
205 QVector2D inverseFrustumScale(1.0f / frustumScale.x(), 1.0f / frustumScale.y());
206 QVector2D scaledCoords(inverseFrustumScale.x() * normalizedCoords.x(), inverseFrustumScale.y() * normalizedCoords.y());
207
208 if (type == QSSGRenderCamera::Type::OrthographicCamera) {
209 outOrigin.setX(scaledCoords.x());
210 outOrigin.setY(scaledCoords.y());
211 outOrigin.setZ(0.0f);
212
213 outDir.setX(0.0f);
214 outDir.setY(0.0f);
215 outDir.setZ(-1.0f);
216 } else {
217 outOrigin.setX(0.0f);
218 outOrigin.setY(0.0f);
219 outOrigin.setZ(0.0f);
220
221 outDir.setX(scaledCoords.x());
222 outDir.setY(scaledCoords.y());
223 outDir.setZ(-1.0f);
224 }
225
226 outOrigin = QSSGUtils::mat44::transform(m: globalTransform, v: outOrigin);
227 QMatrix3x3 theNormalMatrix = globalTransform.normalMatrix();
228
229 outDir = QSSGUtils::mat33::transform(m: theNormalMatrix, v: outDir);
230 outDir.normalize();
231
232 return theRay;
233}
234
235void QSSGRenderCamera::markDirty(DirtyFlag dirtyFlag)
236{
237 cameraDirtyFlags |= FlagT(dirtyFlag);
238 QSSGRenderNode::markDirty(dirtyFlag: QSSGRenderNode::DirtyFlag::SubNodeDirty);
239}
240
241void QSSGRenderCamera::clearDirty(DirtyFlag dirtyFlag)
242{
243 cameraDirtyFlags &= ~FlagT(dirtyFlag);
244 QSSGRenderNode::clearDirty(dirtyFlag: QSSGRenderNode::DirtyFlag::SubNodeDirty);
245}
246
247static float getZNear(const QMatrix4x4 &projection)
248{
249 const float *data = projection.constData();
250 QSSGPlane plane(QVector3D(data[3] + data[2], data[7] + data[6], data[11] + data[10]), -data[15] - data[14]);
251 plane.normalize();
252 return plane.d;
253}
254
255static QVector2D getViewportHalfExtents(const QMatrix4x4 &projection) {
256 const float *data = projection.constData();
257
258 QSSGPlane nearPlane(QVector3D(data[3] + data[2], data[7] + data[6], data[11] + data[10]), -data[15] - data[14]);
259 nearPlane.normalize();
260 QSSGPlane rightPlane(QVector3D(data[3] - data[0], data[7] - data[4], data[11] - data[8]), -data[15] + data[12]);
261 rightPlane.normalize();
262 QSSGPlane topPlane(QVector3D(data[3] - data[1], data[7] - data[5], data[11] - data[9]), -data[15] + data[13]);
263 topPlane.normalize();
264
265 // Get intersection the 3 planes
266 float denom = QVector3D::dotProduct(v1: QVector3D::crossProduct(v1: nearPlane.n, v2: rightPlane.n), v2: topPlane.n);
267 if (qFuzzyIsNull(f: denom))
268 return QVector2D();
269
270 QVector3D intersection = (QVector3D::crossProduct(v1: rightPlane.n, v2: topPlane.n) * nearPlane.d +
271 (QVector3D::crossProduct(v1: topPlane.n, v2: nearPlane.n) * rightPlane.d) +
272 (QVector3D::crossProduct(v1: nearPlane.n, v2: rightPlane.n) * topPlane.d)) / denom;
273
274 return QVector2D(intersection.x(), intersection.y());
275}
276
277float QSSGRenderCamera::getLevelOfDetailMultiplier() const
278{
279 if (type == QSSGRenderGraphObject::Type::OrthographicCamera)
280 return getViewportHalfExtents(projection).x();
281
282 float zn = getZNear(projection);
283 float width = getViewportHalfExtents(projection).x() * 2.0;
284 return 1.0 / (zn / width);
285
286}
287
288QT_END_NAMESPACE
289

source code of qtquick3d/src/runtimerender/graphobjects/qssgrendercamera.cpp