diff options
Diffstat (limited to 'src/runtimerender/graphobjects/Qt3DSRenderCamera.cpp')
-rw-r--r-- | src/runtimerender/graphobjects/Qt3DSRenderCamera.cpp | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/src/runtimerender/graphobjects/Qt3DSRenderCamera.cpp b/src/runtimerender/graphobjects/Qt3DSRenderCamera.cpp new file mode 100644 index 0000000..b9f9c20 --- /dev/null +++ b/src/runtimerender/graphobjects/Qt3DSRenderCamera.cpp @@ -0,0 +1,496 @@ +/**************************************************************************** +** +** Copyright (C) 2008-2012 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) any later version +** approved by the KDE Free Qt Foundation. The licenses are as published by +** the Free Software Foundation and appearing in the file LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "Qt3DSRenderCamera.h" +#include "Qt3DSRenderPresentation.h" +#include "foundation/Qt3DSVec2.h" +#include "render/Qt3DSRenderTexture2D.h" +#include "render/Qt3DSRenderContext.h" +#include "Qt3DSTextRenderer.h" + +#include <qmath.h> + +using namespace qt3ds::render; + +namespace { + +QT3DSF32 GetAspectRatio(const NVRenderRectF &inViewport) +{ + return inViewport.m_Height != 0 ? inViewport.m_Width / inViewport.m_Height : 0.0f; +} + +QT3DSF32 GetAspectRatio(const QT3DSVec2 &inDimensions) +{ + return inDimensions.y != 0 ? inDimensions.x / inDimensions.y : 0.0f; +} + +bool IsCameraVerticalAdjust(CameraScaleModes::Enum inMode, QT3DSF32 inDesignAspect, + QT3DSF32 inActualAspect) +{ + return (inMode == CameraScaleModes::Fit && inActualAspect >= inDesignAspect) + || inMode == CameraScaleModes::FitVertical; +} + +bool IsCameraHorizontalAdjust(CameraScaleModes::Enum inMode, QT3DSF32 inDesignAspect, + QT3DSF32 inActualAspect) +{ + return (inMode == CameraScaleModes::Fit && inActualAspect < inDesignAspect) + || inMode == CameraScaleModes::FitHorizontal; +} + +bool IsFitTypeScaleMode(CameraScaleModes::Enum inMode) +{ + return inMode == CameraScaleModes::Fit || inMode == CameraScaleModes::FitHorizontal + || inMode == CameraScaleModes::FitVertical; +} + +struct SPinCameraResult +{ + NVRenderRectF m_Viewport; + NVRenderRectF m_VirtualViewport; + SPinCameraResult(NVRenderRectF v, NVRenderRectF vv) + : m_Viewport(v) + , m_VirtualViewport(vv) + { + } +}; +// Scale and transform the projection matrix to respect the camera anchor attribute +// and the scale mode. +SPinCameraResult PinCamera(const NVRenderRectF &inViewport, QT3DSVec2 inDesignDims, + QT3DSMat44 &ioPerspectiveMatrix, CameraScaleModes::Enum inScaleMode, + CameraScaleAnchors::Enum inPinLocation) +{ + NVRenderRectF viewport(inViewport); + NVRenderRectF idealViewport(inViewport.m_X, inViewport.m_Y, inDesignDims.x, inDesignDims.y); + QT3DSF32 designAspect = GetAspectRatio(inDesignDims); + QT3DSF32 actualAspect = GetAspectRatio(inViewport); + if (IsFitTypeScaleMode(inScaleMode)) { + idealViewport.m_Width = viewport.m_Width; + idealViewport.m_Height = viewport.m_Height; + } + // We move the viewport such that the left, top of the presentation sits against the left top + // edge + // We only need to translate in X *if* our actual aspect > design aspect + // And then we only need to account for whatever centering would happen. + + bool pinLeft = inPinLocation == CameraScaleAnchors::SouthWest + || inPinLocation == CameraScaleAnchors::West + || inPinLocation == CameraScaleAnchors::NorthWest; + bool pinRight = inPinLocation == CameraScaleAnchors::SouthEast + || inPinLocation == CameraScaleAnchors::East + || inPinLocation == CameraScaleAnchors::NorthEast; + bool pinTop = inPinLocation == CameraScaleAnchors::NorthWest + || inPinLocation == CameraScaleAnchors::North + || inPinLocation == CameraScaleAnchors::NorthEast; + bool pinBottom = inPinLocation == CameraScaleAnchors::SouthWest + || inPinLocation == CameraScaleAnchors::South + || inPinLocation == CameraScaleAnchors::SouthEast; + + if (inScaleMode == CameraScaleModes::SameSize) { + // In this case the perspective transform does not center the view, + // it places it in the lower-left of the viewport. + QT3DSF32 idealWidth = inDesignDims.x; + QT3DSF32 idealHeight = inDesignDims.y; + if (pinRight) + idealViewport.m_X -= ((idealWidth - inViewport.m_Width)); + else if (!pinLeft) + idealViewport.m_X -= ((idealWidth - inViewport.m_Width) / 2.0f); + + if (pinTop) + idealViewport.m_Y -= ((idealHeight - inViewport.m_Height)); + else if (!pinBottom) + idealViewport.m_Y -= ((idealHeight - inViewport.m_Height) / 2.0f); + } else { + // In this case our perspective matrix will center the view and we need to decenter + // it as necessary + // if we are wider than we are high + if (IsCameraVerticalAdjust(inScaleMode, designAspect, actualAspect)) { + if (pinLeft || pinRight) { + QT3DSF32 idealWidth = inViewport.m_Height * designAspect; + QT3DSI32 halfOffset = (QT3DSI32)((idealWidth - inViewport.m_Width) / 2.0f); + halfOffset = pinLeft ? halfOffset : -1 * halfOffset; + idealViewport.m_X += halfOffset; + } + } else { + if (pinTop || pinBottom) { + QT3DSF32 idealHeight = inViewport.m_Width / designAspect; + QT3DSI32 halfOffset = (QT3DSI32)((idealHeight - inViewport.m_Height) / 2.0f); + halfOffset = pinBottom ? halfOffset : -1 * halfOffset; + idealViewport.m_Y += halfOffset; + } + } + } + + ioPerspectiveMatrix = NVRenderContext::ApplyVirtualViewportToProjectionMatrix( + ioPerspectiveMatrix, viewport, idealViewport); + return SPinCameraResult(viewport, idealViewport); +} +} + +SCamera::SCamera() + : SNode(GraphObjectTypes::Camera) + , m_ClipNear(10) + , m_ClipFar(10000) + , m_FOV(60) + , m_FOVHorizontal(false) + , m_ScaleMode(CameraScaleModes::Fit) + , m_ScaleAnchor(CameraScaleAnchors::Center) +{ + TORAD(m_FOV); + m_Projection = QT3DSMat44::createIdentity(); + m_Position = QT3DSVec3(0, 0, -600); +} + +// Code for testing +SCameraGlobalCalculationResult SCamera::CalculateGlobalVariables(const NVRenderRectF &inViewport, + const QT3DSVec2 &inDesignDimensions) +{ + bool wasDirty = SNode::CalculateGlobalVariables(); + return SCameraGlobalCalculationResult(wasDirty, + CalculateProjection(inViewport, inDesignDimensions)); +} + +bool SCamera::CalculateProjection(const NVRenderRectF &inViewport, const QT3DSVec2 &inDesignDimensions) +{ + bool retval = false; + if (m_Flags.IsOrthographic()) + retval = ComputeFrustumOrtho(inViewport, inDesignDimensions); + else + retval = ComputeFrustumPerspective(inViewport, inDesignDimensions); + if (retval) { + QT3DSF32 *writePtr(m_Projection.front()); + m_FrustumScale.x = writePtr[0]; + m_FrustumScale.y = writePtr[5]; + PinCamera(inViewport, inDesignDimensions, m_Projection, m_ScaleMode, m_ScaleAnchor); + } + return retval; +} + +//============================================================================== +/** + * Compute the projection matrix for a perspective camera + * @return true if the computed projection matrix is valid + */ +bool SCamera::ComputeFrustumPerspective(const NVRenderRectF &inViewport, + const QT3DSVec2 &inDesignDimensions) +{ + m_Projection = QT3DSMat44::createIdentity(); + QT3DSF32 theAngleInRadians = verticalFov(inViewport) / 2.0f; + QT3DSF32 theDeltaZ = m_ClipFar - m_ClipNear; + QT3DSF32 theSine = sinf(theAngleInRadians); + QT3DSF32 designAspect = GetAspectRatio(inDesignDimensions); + QT3DSF32 theAspectRatio = designAspect; + if (IsFitTypeScaleMode(m_ScaleMode)) + theAspectRatio = GetAspectRatio(inViewport); + + if ((theDeltaZ != 0) && (theSine != 0) && (theAspectRatio != 0)) { + QT3DSF32 *writePtr(m_Projection.front()); + writePtr[10] = -(m_ClipFar + m_ClipNear) / theDeltaZ; + writePtr[11] = -1; + writePtr[14] = -2 * m_ClipNear * m_ClipFar / theDeltaZ; + writePtr[15] = 0; + + if (IsCameraVerticalAdjust(m_ScaleMode, designAspect, theAspectRatio)) { + QT3DSF32 theCotangent = cosf(theAngleInRadians) / theSine; + writePtr[0] = theCotangent / theAspectRatio; + writePtr[5] = theCotangent; + } else { + QT3DSF32 theCotangent = cosf(theAngleInRadians) / theSine; + writePtr[0] = theCotangent / designAspect; + writePtr[5] = theCotangent * (theAspectRatio / designAspect); + } + return true; + } else { + QT3DS_ASSERT(false); + return false; + } +} + +//============================================================================== +/** + * Compute the projection matrix for a orthographic camera + * @return true if the computed projection matrix is valid + */ +bool SCamera::ComputeFrustumOrtho(const NVRenderRectF &inViewport, const QT3DSVec2 &inDesignDimensions) +{ + m_Projection = QT3DSMat44::createIdentity(); + + QT3DSF32 theDeltaZ = m_ClipFar - m_ClipNear; + QT3DSF32 halfWidth = inDesignDimensions.x / 2.0f; + QT3DSF32 halfHeight = inDesignDimensions.y / 2.0f; + QT3DSF32 designAspect = GetAspectRatio(inDesignDimensions); + QT3DSF32 theAspectRatio = designAspect; + if (IsFitTypeScaleMode(m_ScaleMode)) + theAspectRatio = GetAspectRatio(inViewport); + if (theDeltaZ != 0) { + QT3DSF32 *writePtr(m_Projection.front()); + writePtr[10] = -2.0f / theDeltaZ; + writePtr[11] = 0.0f; + writePtr[14] = -(m_ClipNear + m_ClipFar) / theDeltaZ; + writePtr[15] = 1.0f; + if (IsCameraVerticalAdjust(m_ScaleMode, designAspect, theAspectRatio)) { + writePtr[0] = 1.0f / (halfHeight * theAspectRatio); + writePtr[5] = 1.0f / halfHeight; + } else { + writePtr[0] = 1.0f / halfWidth; + writePtr[5] = 1.0f / (halfWidth / theAspectRatio); + } + return true; + } else { + QT3DS_ASSERT(false); + return false; + } +} + +QT3DSF32 SCamera::GetOrthographicScaleFactor(const NVRenderRectF &inViewport, + const QT3DSVec2 &inDesignDimensions) const +{ + if (m_ScaleMode == CameraScaleModes::SameSize) + return 1.0f; + QT3DSMat44 temp(QT3DSMat44::createIdentity()); + QT3DSF32 designAspect = GetAspectRatio(inDesignDimensions); + QT3DSF32 theAspectRatio = GetAspectRatio(inViewport); + if (m_ScaleMode == CameraScaleModes::Fit) { + if (theAspectRatio >= designAspect) { + return inViewport.m_Width < inDesignDimensions.x ? theAspectRatio / designAspect : 1.0f; + + } else { + return inViewport.m_Height < inDesignDimensions.y ? designAspect / theAspectRatio + : 1.0f; + } + } else if (m_ScaleMode == CameraScaleModes::FitVertical) { + return (QT3DSF32)inDesignDimensions.y / (QT3DSF32)inViewport.m_Height; + } else { + return (QT3DSF32)inDesignDimensions.x / (QT3DSF32)inViewport.m_Width; + } +} + +QT3DSF32 SCamera::GetTextScaleFactor(const NVRenderRectF &inViewport, + const QT3DSVec2 &inDesignDimensions) const +{ + return NVMax(1.0f, 1.0f / GetOrthographicScaleFactor(inViewport, inDesignDimensions)); +} + +QT3DSMat33 SCamera::GetLookAtMatrix(const QT3DSVec3 &inUpDir, const QT3DSVec3 &inDirection) const +{ + QT3DSVec3 theDirection(inDirection); + + theDirection.normalize(); + + const QT3DSVec3 &theUpDir(inUpDir); + + // gram-shmidt orthogonalization + QT3DSVec3 theCrossDir(theDirection.cross(theUpDir)); + theCrossDir.normalize(); + QT3DSVec3 theFinalDir(theCrossDir.cross(theDirection)); + theFinalDir.normalize(); + QT3DSF32 multiplier = 1.0f; + if (m_Flags.IsLeftHanded()) + multiplier = -1.0f; + + QT3DSMat33 theResultMatrix(theCrossDir, theFinalDir, multiplier * theDirection); + return theResultMatrix; +} + +void SCamera::LookAt(const QT3DSVec3 &inCameraPos, const QT3DSVec3 &inUpDir, const QT3DSVec3 &inTargetPos) +{ + QT3DSVec3 theDirection = inTargetPos - inCameraPos; + if (m_Flags.IsLeftHanded()) + theDirection.z *= -1.0f; + m_Rotation = GetRotationVectorFromRotationMatrix(GetLookAtMatrix(inUpDir, theDirection)); + m_Position = inCameraPos; + MarkDirty(qt3ds::render::NodeTransformDirtyFlag::TransformIsDirty); +} + +void SCamera::CalculateViewProjectionMatrix(QT3DSMat44 &outMatrix) const +{ + QT3DSMat44 globalInverse = m_GlobalTransform.getInverse(); + outMatrix = m_Projection * globalInverse; +} + +SCuboidRect SCamera::GetCameraBounds(const NVRenderRectF &inViewport, + const QT3DSVec2 &inDesignDimensions) const +{ + QT3DSMat44 unused(QT3DSMat44::createIdentity()); + SPinCameraResult theResult = + PinCamera(inViewport, inDesignDimensions, unused, m_ScaleMode, m_ScaleAnchor); + // find the normalized edges of the view frustum given the renormalization that happens when + // pinning the camera. + SCuboidRect normalizedCuboid(-1, 1, 1, -1); + QT3DSVec2 translation(theResult.m_Viewport.m_X - theResult.m_VirtualViewport.m_X, + theResult.m_Viewport.m_Y - theResult.m_VirtualViewport.m_Y); + if (m_ScaleMode == CameraScaleModes::SameSize) { + // the cuboid ranges are the actual divided by the ideal in this case + QT3DSF32 xRange = 2.0f * (theResult.m_Viewport.m_Width / theResult.m_VirtualViewport.m_Width); + QT3DSF32 yRange = + 2.0f * (theResult.m_Viewport.m_Height / theResult.m_VirtualViewport.m_Height); + normalizedCuboid = SCuboidRect(-1, -1 + yRange, -1 + xRange, -1); + translation.x /= (theResult.m_VirtualViewport.m_Width / 2.0f); + translation.y /= (theResult.m_VirtualViewport.m_Height / 2.0f); + normalizedCuboid.Translate(translation); + } + // fit. This means that two parameters of the normalized cuboid will be -1, 1. + else { + // In this case our perspective matrix will center the view and we need to decenter + // it as necessary + QT3DSF32 actualAspect = GetAspectRatio(inViewport); + QT3DSF32 designAspect = GetAspectRatio(inDesignDimensions); + // if we are wider than we are high + QT3DSF32 idealWidth = inViewport.m_Width; + QT3DSF32 idealHeight = inViewport.m_Height; + + if (IsCameraVerticalAdjust(m_ScaleMode, designAspect, actualAspect)) { + // then we just need to setup the left, right parameters of the cuboid because we know + // the top + // bottom are -1,1 due to how fit works. + idealWidth = (QT3DSF32)ITextRenderer::NextMultipleOf4( + (QT3DSU32)(inViewport.m_Height * designAspect + .5f)); + // halfRange should always be greater than 1.0f. + QT3DSF32 halfRange = inViewport.m_Width / idealWidth; + normalizedCuboid.m_Left = -halfRange; + normalizedCuboid.m_Right = halfRange; + translation.x = translation.x / (idealWidth / 2.0f); + } else { + idealHeight = (QT3DSF32)ITextRenderer::NextMultipleOf4( + (QT3DSU32)(inViewport.m_Width / designAspect + .5f)); + QT3DSF32 halfRange = inViewport.m_Height / idealHeight; + normalizedCuboid.m_Bottom = -halfRange; + normalizedCuboid.m_Top = halfRange; + translation.y = translation.y / (idealHeight / 2.0f); + } + normalizedCuboid.Translate(translation); + } + // Given no adjustment in the virtual rect, then this is what we would have. + + return normalizedCuboid; +} + +void SCamera::SetupOrthographicCameraForOffscreenRender(NVRenderTexture2D &inTexture, + QT3DSMat44 &outVP) +{ + STextureDetails theDetails(inTexture.GetTextureDetails()); + SCamera theTempCamera; + SetupOrthographicCameraForOffscreenRender(inTexture, outVP, theTempCamera); +} + +void SCamera::SetupOrthographicCameraForOffscreenRender(NVRenderTexture2D &inTexture, + QT3DSMat44 &outVP, SCamera &outCamera) +{ + STextureDetails theDetails(inTexture.GetTextureDetails()); + SCamera theTempCamera; + theTempCamera.m_Flags.SetOrthographic(true); + theTempCamera.MarkDirty(NodeTransformDirtyFlag::TransformIsDirty); + QT3DSVec2 theDimensions((QT3DSF32)theDetails.m_Width, (QT3DSF32)theDetails.m_Height); + theTempCamera.CalculateGlobalVariables( + NVRenderRect(0, 0, theDetails.m_Width, theDetails.m_Height), theDimensions); + theTempCamera.CalculateViewProjectionMatrix(outVP); + outCamera = theTempCamera; +} + +SRay SCamera::Unproject(const QT3DSVec2 &inViewportRelativeCoords, const NVRenderRectF &inViewport, + const QT3DSVec2 &inDesignDimensions, bool sceneCameraView) const +{ + SRay theRay; + QT3DSMat44 tempVal(QT3DSMat44::createIdentity()); + SPinCameraResult result = + PinCamera(inViewport, inDesignDimensions, tempVal, m_ScaleMode, m_ScaleAnchor); + QT3DSVec2 globalCoords = inViewport.ToAbsoluteCoords(inViewportRelativeCoords); + QT3DSVec2 normalizedCoords = + result.m_VirtualViewport.AbsoluteToNormalizedCoordinates(globalCoords); + QT3DSVec3 &outOrigin(theRay.m_Origin); + QT3DSVec3 &outDir(theRay.m_Direction); + QT3DSVec2 inverseFrustumScale(1.0f / m_FrustumScale.x, 1.0f / m_FrustumScale.y); + QT3DSVec2 scaledCoords(inverseFrustumScale.x * normalizedCoords.x, + inverseFrustumScale.y * normalizedCoords.y); + + if (m_Flags.IsOrthographic()) { + outOrigin.x = scaledCoords.x; + outOrigin.y = scaledCoords.y; + outOrigin.z = 0.0f; + + outDir.x = 0.0f; + outDir.y = 0.0f; + outDir.z = -1.0f; + } else { + outOrigin.x = 0.0f; + outOrigin.y = 0.0f; + outOrigin.z = 0.0f; + + outDir.x = scaledCoords.x; + outDir.y = scaledCoords.y; + outDir.z = -1.0f; + } + + outOrigin = m_GlobalTransform.transform(outOrigin); + + // CalculateNormalMatrix(), but 4x4 matrix to have scale() method + QT3DSMat44 theNormalMatrix = m_GlobalTransform.getInverse().getTranspose(); + if (sceneCameraView) { + // When in scene camera view mode, camera scale needs to be inverted. + // See QT3DS-3393. + const float scaleX = m_GlobalTransform[0][0] / theNormalMatrix[0][0]; + const float scaleY = m_GlobalTransform[1][1] / theNormalMatrix[1][1]; + const float scaleZ = m_GlobalTransform[2][2] / theNormalMatrix[2][2]; + QT3DSVec4 scaleVector(scaleX, scaleY, scaleZ, 1.0); + theNormalMatrix.scale(scaleVector); + } + + outDir = theNormalMatrix.transform(outDir); + outDir.normalize(); + /* + char printBuf[2000]; + sprintf_s( printBuf, "normCoords %f %f outDir %f %f %f\n" + , normalizedCoords.x, normalizedCoords.y, outDir.x, outDir.y, outDir.z ); + OutputDebugStringA( printBuf ); + */ + + return theRay; +} + +QT3DSVec3 SCamera::UnprojectToPosition(const QT3DSVec3 &inGlobalPos, const SRay &inRay) const +{ + QT3DSVec3 theCameraDir = GetDirection(); + QT3DSVec3 theObjGlobalPos = inGlobalPos; + QT3DSF32 theDistance = -1.0f * theObjGlobalPos.dot(theCameraDir); + NVPlane theCameraPlane(theCameraDir, theDistance); + return inRay.Intersect(theCameraPlane); +} + +QT3DSF32 SCamera::verticalFov(QT3DSF32 aspectRatio) const +{ + if (m_FOVHorizontal) + return 2.0f * qAtan(qTan(qreal(m_FOV) / 2.0) / qreal(aspectRatio)); + else + return m_FOV; +} + +QT3DSF32 SCamera::verticalFov(const NVRenderRectF &inViewport) const +{ + return verticalFov(GetAspectRatio(inViewport)); +} |