diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bf5fcc5b20..e4bfd47ee4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5565,7 +5565,44 @@ void Application::updateSecondaryCameraViewFrustum() { } ViewFrustum secondaryViewFrustum; - if (camera->mirrorProjection && !camera->attachedEntityId.isNull()) { + if (camera->portalProjection && !camera->attachedEntityId.isNull() && !camera->portalEntranceEntityId.isNull()) { + auto entityScriptingInterface = DependencyManager::get(); + EntityItemPointer portalEntrance = qApp->getEntities()->getTree()->findEntityByID(camera->portalEntranceEntityId); + EntityItemPointer portalExit = qApp->getEntities()->getTree()->findEntityByID(camera->attachedEntityId); + + glm::vec3 portalEntrancePropertiesPosition = portalEntrance->getWorldPosition(); + glm::quat portalEntrancePropertiesRotation = portalEntrance->getWorldOrientation(); + glm::mat4 worldFromPortalEntranceRotation = glm::mat4_cast(portalEntrancePropertiesRotation); + glm::mat4 worldFromPortalEntranceTranslation = glm::translate(portalEntrancePropertiesPosition); + glm::mat4 worldFromPortalEntrance = worldFromPortalEntranceTranslation * worldFromPortalEntranceRotation; + glm::mat4 portalEntranceFromWorld = glm::inverse(worldFromPortalEntrance); + + glm::vec3 portalExitPropertiesPosition = portalExit->getWorldPosition(); + glm::quat portalExitPropertiesRotation = portalExit->getWorldOrientation(); + glm::vec3 portalExitPropertiesDimensions = portalExit->getScaledDimensions(); + glm::vec3 halfPortalExitPropertiesDimensions = 0.5f * portalExitPropertiesDimensions; + + glm::mat4 worldFromPortalExitRotation = glm::mat4_cast(portalExitPropertiesRotation); + glm::mat4 worldFromPortalExitTranslation = glm::translate(portalExitPropertiesPosition); + glm::mat4 worldFromPortalExit = worldFromPortalExitTranslation * worldFromPortalExitRotation; + + glm::vec3 mainCameraPositionWorld = getCamera().getPosition(); + glm::vec3 mainCameraPositionPortalEntrance = vec3(portalEntranceFromWorld * vec4(mainCameraPositionWorld, 1.0f)); + mainCameraPositionPortalEntrance = vec3(-mainCameraPositionPortalEntrance.x, mainCameraPositionPortalEntrance.y, + -mainCameraPositionPortalEntrance.z); + glm::vec3 portalExitCameraPositionWorld = vec3(worldFromPortalExit * vec4(mainCameraPositionPortalEntrance, 1.0f)); + + secondaryViewFrustum.setPosition(portalExitCameraPositionWorld); + secondaryViewFrustum.setOrientation(portalExitPropertiesRotation); + + float nearClip = mainCameraPositionPortalEntrance.z + portalExitPropertiesDimensions.z * 2.0f; + // `mainCameraPositionPortalEntrance` should technically be `mainCameraPositionPortalExit`, + // but the values are the same. + glm::vec3 upperRight = halfPortalExitPropertiesDimensions - mainCameraPositionPortalEntrance; + glm::vec3 bottomLeft = -halfPortalExitPropertiesDimensions - mainCameraPositionPortalEntrance; + glm::mat4 frustum = glm::frustum(bottomLeft.x, upperRight.x, bottomLeft.y, upperRight.y, nearClip, camera->farClipPlaneDistance); + secondaryViewFrustum.setProjection(frustum); + } else if (camera->mirrorProjection && !camera->attachedEntityId.isNull()) { auto entityScriptingInterface = DependencyManager::get(); auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId); glm::vec3 mirrorPropertiesPosition = entityProperties.getPosition(); diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index e71602b271..81944ec747 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -33,6 +33,7 @@ public: void configure(const Config& config) { _attachedEntityId = config.attachedEntityId; + _portalEntranceEntityId = config.portalEntranceEntityId; _position = config.position; _orientation = config.orientation; _vFoV = config.vFoV; @@ -40,7 +41,60 @@ public: _farClipPlaneDistance = config.farClipPlaneDistance; _textureWidth = config.textureWidth; _textureHeight = config.textureHeight; - _mirrorProjection = config.mirrorProjection; + _mirrorProjection = config.mirrorProjection; + _portalProjection = config.portalProjection; + } + + void setPortalProjection(ViewFrustum& srcViewFrustum) { + if (_portalEntranceEntityId.isNull() || _attachedEntityId.isNull()) { + qWarning() << "ERROR: Cannot set portal projection for SecondaryCamera without an attachedEntityId AND portalEntranceEntityId set."; + return; + } + + EntityItemPointer portalEntrance = qApp->getEntities()->getTree()->findEntityByID(_portalEntranceEntityId); + if (!portalEntrance) { + qWarning() << "ERROR: Cannot get EntityItemPointer for portalEntrance."; + return; + } + + EntityItemPointer portalExit = qApp->getEntities()->getTree()->findEntityByID(_attachedEntityId); + if (!portalExit) { + qWarning() << "ERROR: Cannot get EntityItemPointer for portalExit."; + return; + } + + glm::vec3 portalEntrancePropertiesPosition = portalEntrance->getWorldPosition(); + glm::quat portalEntrancePropertiesRotation = portalEntrance->getWorldOrientation(); + glm::mat4 worldFromPortalEntranceRotation = glm::mat4_cast(portalEntrancePropertiesRotation); + glm::mat4 worldFromPortalEntranceTranslation = glm::translate(portalEntrancePropertiesPosition); + glm::mat4 worldFromPortalEntrance = worldFromPortalEntranceTranslation * worldFromPortalEntranceRotation; + glm::mat4 portalEntranceFromWorld = glm::inverse(worldFromPortalEntrance); + + glm::vec3 portalExitPropertiesPosition = portalExit->getWorldPosition(); + glm::quat portalExitPropertiesRotation = portalExit->getWorldOrientation(); + glm::vec3 portalExitPropertiesDimensions = portalExit->getScaledDimensions(); + glm::vec3 halfPortalExitPropertiesDimensions = 0.5f * portalExitPropertiesDimensions; + + glm::mat4 worldFromPortalExitRotation = glm::mat4_cast(portalExitPropertiesRotation); + glm::mat4 worldFromPortalExitTranslation = glm::translate(portalExitPropertiesPosition); + glm::mat4 worldFromPortalExit = worldFromPortalExitTranslation * worldFromPortalExitRotation; + + glm::vec3 mainCameraPositionWorld = qApp->getCamera().getPosition(); + glm::vec3 mainCameraPositionPortalEntrance = vec3(portalEntranceFromWorld * vec4(mainCameraPositionWorld, 1.0f)); + mainCameraPositionPortalEntrance = vec3(-mainCameraPositionPortalEntrance.x, mainCameraPositionPortalEntrance.y, + -mainCameraPositionPortalEntrance.z); + glm::vec3 portalExitCameraPositionWorld = vec3(worldFromPortalExit * vec4(mainCameraPositionPortalEntrance, 1.0f)); + + srcViewFrustum.setPosition(portalExitCameraPositionWorld); + srcViewFrustum.setOrientation(portalExitPropertiesRotation); + + float nearClip = mainCameraPositionPortalEntrance.z + portalExitPropertiesDimensions.z * 2.0f; + // `mainCameraPositionPortalEntrance` should technically be `mainCameraPositionPortalExit`, + // but the values are the same. + glm::vec3 upperRight = halfPortalExitPropertiesDimensions - mainCameraPositionPortalEntrance; + glm::vec3 bottomLeft = -halfPortalExitPropertiesDimensions - mainCameraPositionPortalEntrance; + glm::mat4 frustum = glm::frustum(bottomLeft.x, upperRight.x, bottomLeft.y, upperRight.y, nearClip, _farClipPlaneDistance); + srcViewFrustum.setProjection(frustum); } void setMirrorProjection(ViewFrustum& srcViewFrustum) { @@ -109,7 +163,17 @@ public: auto srcViewFrustum = args->getViewFrustum(); if (_mirrorProjection) { - setMirrorProjection(srcViewFrustum); + if (_portalProjection) { + qWarning() << "ERROR: You can't set both _portalProjection and _mirrorProjection"; + } else { + setMirrorProjection(srcViewFrustum); + } + } else if (_portalProjection) { + if (_mirrorProjection) { + qWarning() << "ERROR: You can't set both _portalProjection and _mirrorProjection"; + } else { + setPortalProjection(srcViewFrustum); + } } else { if (!_attachedEntityId.isNull()) { EntityItemPointer attachedEntity = qApp->getEntities()->getTree()->findEntityByID(_attachedEntityId); @@ -141,6 +205,7 @@ protected: private: QUuid _attachedEntityId; + QUuid _portalEntranceEntityId; glm::vec3 _position; glm::quat _orientation; float _vFoV; @@ -149,6 +214,7 @@ private: int _textureWidth; int _textureHeight; bool _mirrorProjection; + bool _portalProjection; EntityPropertyFlags _attachedEntityPropertyFlags; }; diff --git a/interface/src/SecondaryCamera.h b/interface/src/SecondaryCamera.h index 3c8540c081..941ccc5f93 100644 --- a/interface/src/SecondaryCamera.h +++ b/interface/src/SecondaryCamera.h @@ -20,14 +20,17 @@ class SecondaryCameraJobConfig : public render::Task::Config { // Exposes secondary camera parameters to JavaScript. Q_OBJECT Q_PROPERTY(QUuid attachedEntityId MEMBER attachedEntityId NOTIFY dirty) // entity whose properties define camera position and orientation + Q_PROPERTY(QUuid portalEntranceEntityId MEMBER portalEntranceEntityId NOTIFY dirty) // entity whose properties define a portal's entrance position and orientation Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) // of viewpoint to render from Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) // of viewpoint to render from Q_PROPERTY(float vFoV MEMBER vFoV NOTIFY dirty) // Secondary camera's vertical field of view. In degrees. Q_PROPERTY(float nearClipPlaneDistance MEMBER nearClipPlaneDistance NOTIFY dirty) // Secondary camera's near clip plane distance. In meters. Q_PROPERTY(float farClipPlaneDistance MEMBER farClipPlaneDistance NOTIFY dirty) // Secondary camera's far clip plane distance. In meters. Q_PROPERTY(bool mirrorProjection MEMBER mirrorProjection NOTIFY dirty) // Flag to use attached mirror entity to build frustum for the mirror and set mirrored camera position/orientation. + Q_PROPERTY(bool portalProjection MEMBER portalProjection NOTIFY dirty) // Flag to use attached portal entity to build frustum for the portal and set portal camera position/orientation. public: QUuid attachedEntityId; + QUuid portalEntranceEntityId; glm::vec3 position; glm::quat orientation; float vFoV { DEFAULT_FIELD_OF_VIEW_DEGREES }; @@ -36,6 +39,7 @@ public: int textureWidth { TextureCache::DEFAULT_SPECTATOR_CAM_WIDTH }; int textureHeight { TextureCache::DEFAULT_SPECTATOR_CAM_HEIGHT }; bool mirrorProjection { false }; + bool portalProjection { false }; SecondaryCameraJobConfig() : render::Task::Config(false) {} signals: