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/QVector2D>
13
14#include <qmath.h>
15
16QT_BEGIN_NAMESPACE
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(degrees: 60.0f))
32 , fovHorizontal(false)
33 , enableFrustumClipping(true)
34{
35 Q_ASSERT(QSSGRenderCamera::isCamera(type));
36 markDirty(dirtyFlag: DirtyFlag::CameraDirty);
37}
38
39// Code for testing
40QSSGCameraGlobalCalculationResult QSSGRenderCamera::calculateGlobalVariables(const QRectF &inViewport)
41{
42 bool wasDirty = QSSGRenderNode::calculateGlobalVariables();
43 return QSSGCameraGlobalCalculationResult{ .m_wasDirty: wasDirty, .m_computeFrustumSucceeded: 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: DirtyFlag::CameraDirty))
52 return true;
53 previousInViewport = inViewport;
54 clearDirty(dirtyFlag: 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(verticalAngle: qRadiansToDegrees(radians: verticalFov(inViewport)), aspectRatio: getAspectRatio(inViewport), nearPlane: clipNear, farPlane: 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, nearPlane: clipNear, farPlane: clipFar);
98 return true;
99}
100
101//==============================================================================
102/**
103 * Compute the projection matrix for a orthographic camera
104 * @return true if the computed projection matrix is valid
105 */
106bool QSSGRenderCamera::computeFrustumOrtho(const QRectF &inViewport)
107{
108 projection = QMatrix4x4();
109 float halfWidth = inViewport.width() / 2.0f / horizontalMagnification / dpr;
110 float halfHeight = inViewport.height() / 2.0f / verticalMagnification / dpr;
111 projection.ortho(left: -halfWidth, right: halfWidth, bottom: -halfHeight, top: halfHeight, nearPlane: clipNear, farPlane: clipFar);
112 return true;
113}
114
115float QSSGRenderCamera::getOrthographicScaleFactor(const QRectF &inViewport) const
116{
117 Q_UNUSED(inViewport);
118 return qMax(a: horizontalMagnification, b: verticalMagnification);
119}
120
121static QQuaternion rotationQuaternionForLookAt(const QVector3D &sourcePosition,
122 const QVector3D &sourceDirection,
123 const QVector3D &targetPosition,
124 const QVector3D &upDirection)
125{
126 QVector3D targetDirection = sourcePosition - targetPosition;
127 targetDirection.normalize();
128
129 QVector3D rotationAxis = QVector3D::crossProduct(v1: sourceDirection, v2: targetDirection);
130
131 const QVector3D normalizedAxis = rotationAxis.normalized();
132 if (qFuzzyIsNull(f: normalizedAxis.lengthSquared()))
133 rotationAxis = upDirection;
134
135 float dot = QVector3D::dotProduct(v1: sourceDirection, v2: targetDirection);
136 float rotationAngle = float(qRadiansToDegrees(radians: qAcos(v: qreal(dot))));
137
138 return QQuaternion::fromAxisAndAngle(axis: rotationAxis, angle: rotationAngle);
139}
140
141void QSSGRenderCamera::lookAt(const QVector3D &inCameraPos, const QVector3D &inUpDir, const QVector3D &inTargetPos, const QVector3D &pivot)
142{
143 QQuaternion rotation = rotationQuaternionForLookAt(sourcePosition: inCameraPos, sourceDirection: getScalingCorrectDirection(), targetPosition: inTargetPos, upDirection: inUpDir.normalized());
144 globalTransform = localTransform = QSSGRenderNode::calculateTransformMatrix(position: inCameraPos, scale: QSSGRenderNode::initScale, pivot, rotation);
145 QSSGRenderNode::markDirty(dirtyFlag: QSSGRenderNode::DirtyFlag::TransformDirty);
146}
147
148void QSSGRenderCamera::calculateViewProjectionMatrix(QMatrix4x4 &outMatrix) const
149{
150 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
151 nonScaledGlobal.setColumn(index: 0, value: globalTransform.column(index: 0).normalized());
152 nonScaledGlobal.setColumn(index: 1, value: globalTransform.column(index: 1).normalized());
153 nonScaledGlobal.setColumn(index: 2, value: globalTransform.column(index: 2).normalized());
154 nonScaledGlobal.setColumn(index: 3, value: globalTransform.column(index: 3));
155 outMatrix = projection * nonScaledGlobal.inverted();
156}
157
158void QSSGRenderCamera::calculateViewProjectionWithoutTranslation(float clipNear, float clipFar, QMatrix4x4 &outMatrix) const
159{
160 if (qFuzzyIsNull(f: clipFar - clipNear)) {
161 qWarning() << "QSSGRenderCamera::calculateViewProjection: far == near";
162 return;
163 }
164
165 QMatrix4x4 proj = projection;
166 proj(2, 2) = -(clipFar + clipNear) / (clipFar - clipNear);
167 proj(2, 3) = -2 * clipFar * clipNear / (clipFar - clipNear);
168 QMatrix4x4 nonScaledGlobal(Qt::Uninitialized);
169 nonScaledGlobal.setColumn(index: 0, value: globalTransform.column(index: 0).normalized());
170 nonScaledGlobal.setColumn(index: 1, value: globalTransform.column(index: 1).normalized());
171 nonScaledGlobal.setColumn(index: 2, value: globalTransform.column(index: 2).normalized());
172 nonScaledGlobal.setColumn(index: 3, value: QVector4D(0, 0, 0, 1));
173 outMatrix = proj * nonScaledGlobal.inverted();
174}
175
176QSSGRenderRay QSSGRenderCamera::unproject(const QVector2D &inViewportRelativeCoords,
177 const QRectF &inViewport) const
178{
179 QSSGRenderRay theRay;
180 QVector2D normalizedCoords = QSSGUtils::rect::relativeToNormalizedCoordinates(r: inViewport, rectRelativeCoords: inViewportRelativeCoords);
181 QVector3D &outOrigin(theRay.origin);
182 QVector3D &outDir(theRay.direction);
183 QVector2D inverseFrustumScale(1.0f / frustumScale.x(), 1.0f / frustumScale.y());
184 QVector2D scaledCoords(inverseFrustumScale.x() * normalizedCoords.x(), inverseFrustumScale.y() * normalizedCoords.y());
185
186 if (type == QSSGRenderCamera::Type::OrthographicCamera) {
187 outOrigin.setX(scaledCoords.x());
188 outOrigin.setY(scaledCoords.y());
189 outOrigin.setZ(0.0f);
190
191 outDir.setX(0.0f);
192 outDir.setY(0.0f);
193 outDir.setZ(-1.0f);
194 } else {
195 outOrigin.setX(0.0f);
196 outOrigin.setY(0.0f);
197 outOrigin.setZ(0.0f);
198
199 outDir.setX(scaledCoords.x());
200 outDir.setY(scaledCoords.y());
201 outDir.setZ(-1.0f);
202 }
203
204 outOrigin = QSSGUtils::mat44::transform(m: globalTransform, v: outOrigin);
205 QMatrix3x3 theNormalMatrix = calculateNormalMatrix();
206
207 outDir = QSSGUtils::mat33::transform(m: theNormalMatrix, v: outDir);
208 outDir.normalize();
209
210 return theRay;
211}
212
213QVector3D QSSGRenderCamera::unprojectToPosition(const QVector3D &inGlobalPos, const QSSGRenderRay &inRay) const
214{
215 QVector3D theCameraDir = getDirection();
216 QVector3D theObjGlobalPos = inGlobalPos;
217 float theDistance = -1.0f * QVector3D::dotProduct(v1: theObjGlobalPos, v2: theCameraDir);
218 QSSGPlane theCameraPlane(theCameraDir, theDistance);
219 return QSSGRenderRay::intersect(inPlane: theCameraPlane, ray: inRay).value_or(u: QVector3D{});
220}
221
222float QSSGRenderCamera::verticalFov(float aspectRatio) const
223{
224 return fovHorizontal ? float(2.0 * qAtan(v: qTan(v: qreal(fov) / 2.0) / qreal(aspectRatio))) : fov;
225}
226
227float QSSGRenderCamera::verticalFov(const QRectF &inViewport) const
228{
229 return verticalFov(aspectRatio: getAspectRatio(inViewport));
230}
231
232void QSSGRenderCamera::markDirty(DirtyFlag dirtyFlag)
233{
234 cameraDirtyFlags |= FlagT(dirtyFlag);
235 QSSGRenderNode::markDirty(dirtyFlag: QSSGRenderNode::DirtyFlag::SubNodeDirty);
236}
237
238void QSSGRenderCamera::clearDirty(DirtyFlag dirtyFlag)
239{
240 cameraDirtyFlags &= ~FlagT(dirtyFlag);
241 QSSGRenderNode::clearDirty(dirtyFlag: QSSGRenderNode::DirtyFlag::SubNodeDirty);
242}
243
244static float getZNear(const QMatrix4x4 &projection)
245{
246 const float *data = projection.constData();
247 QSSGPlane plane(QVector3D(data[3] + data[2], data[7] + data[6], data[11] + data[10]), -data[15] - data[14]);
248 plane.normalize();
249 return plane.d;
250}
251
252static QVector2D getViewportHalfExtents(const QMatrix4x4 &projection) {
253 const float *data = projection.constData();
254
255 QSSGPlane nearPlane(QVector3D(data[3] + data[2], data[7] + data[6], data[11] + data[10]), -data[15] - data[14]);
256 nearPlane.normalize();
257 QSSGPlane rightPlane(QVector3D(data[3] - data[0], data[7] - data[4], data[11] - data[8]), -data[15] + data[12]);
258 rightPlane.normalize();
259 QSSGPlane topPlane(QVector3D(data[3] - data[1], data[7] - data[5], data[11] - data[9]), -data[15] + data[13]);
260 topPlane.normalize();
261
262 // Get intersection the 3 planes
263 float denom = QVector3D::dotProduct(v1: QVector3D::crossProduct(v1: nearPlane.n, v2: rightPlane.n), v2: topPlane.n);
264 if (qFuzzyIsNull(f: denom))
265 return QVector2D();
266
267 QVector3D intersection = (QVector3D::crossProduct(v1: rightPlane.n, v2: topPlane.n) * nearPlane.d +
268 (QVector3D::crossProduct(v1: topPlane.n, v2: nearPlane.n) * rightPlane.d) +
269 (QVector3D::crossProduct(v1: nearPlane.n, v2: rightPlane.n) * topPlane.d)) / denom;
270
271 return QVector2D(intersection.x(), intersection.y());
272}
273
274float QSSGRenderCamera::getLevelOfDetailMultiplier() const
275{
276 if (type == QSSGRenderGraphObject::Type::OrthographicCamera)
277 return getViewportHalfExtents(projection).x();
278
279 float zn = getZNear(projection);
280 float width = getViewportHalfExtents(projection).x() * 2.0;
281 return 1.0 / (zn / width);
282
283}
284
285QT_END_NAMESPACE
286

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