406 lines · 20.4 KB · C++
RawDownload
////  Application_Camera.cpp//  interface/src////  Split from Application.cpp by HifiExperiments on 3/30/24//  Created by Andrzej Kapolka on 5/10/13.//  Copyright 2013 High Fidelity, Inc.//  Copyright 2020 Vircadia contributors.//  Copyright 2022-2023 Overte e.V.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//  SPDX-License-Identifier: Apache-2.0// #include "Application.h" #include <glm/gtx/transform.hpp> #include <controllers/UserInputMapper.h>#include <PickManager.h>#include <raypick/RayPick.h>#include <SecondaryCamera.h> #include "avatar/MyAvatar.h"#include "avatar/MyHead.h"#include "Menu.h" static const float MIRROR_FULLSCREEN_DISTANCE = 0.789f; void Application::copyViewFrustum(ViewFrustum& viewOut) const {    QMutexLocker viewLocker(&_viewMutex);    viewOut = _viewFrustum;} void Application::copyDisplayViewFrustum(ViewFrustum& viewOut) const {    QMutexLocker viewLocker(&_viewMutex);    viewOut = _displayViewFrustum;} void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {    PROFILE_RANGE(render, __FUNCTION__);    PerformanceTimer perfTimer("updateCamera");     auto myAvatar = getMyAvatar();     // The render mode is default or mirror if the camera is in mirror mode, assigned further below    renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;     // Always use the default eye position, not the actual head eye position.    // Using the latter will cause the camera to wobble with idle animations,    // or with changes from the face tracker    CameraMode mode = _myCamera.getMode();    if (mode == CAMERA_MODE_FIRST_PERSON || mode == CAMERA_MODE_FIRST_PERSON_LOOK_AT) {        _thirdPersonHMDCameraBoomValid= false;        if (isHMDMode()) {            mat4 camMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();            _myCamera.setPosition(extractTranslation(camMat));            _myCamera.setOrientation(glmExtractRotation(camMat));        } else if (mode == CAMERA_MODE_FIRST_PERSON) {            _myCamera.setPosition(myAvatar->getDefaultEyePosition());            _myCamera.setOrientation(myAvatar->getMyHead()->getHeadOrientation());        } else {            _myCamera.setPosition(myAvatar->getCameraEyesPosition(deltaTime));            _myCamera.setOrientation(myAvatar->getLookAtRotation());        }    } else if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) {        if (isHMDMode()) {            if (!_thirdPersonHMDCameraBoomValid) {                const glm::vec3 CAMERA_OFFSET = glm::vec3(0.0f, 0.0f, 0.7f);                _thirdPersonHMDCameraBoom = cancelOutRollAndPitch(myAvatar->getHMDSensorOrientation()) * CAMERA_OFFSET;                _thirdPersonHMDCameraBoomValid = true;            }             glm::mat4 thirdPersonCameraSensorToWorldMatrix = myAvatar->getSensorToWorldMatrix();             const glm::vec3 cameraPos = myAvatar->getHMDSensorPosition() + _thirdPersonHMDCameraBoom * myAvatar->getBoomLength();            glm::mat4 sensorCameraMat = createMatFromQuatAndPos(myAvatar->getHMDSensorOrientation(), cameraPos);            glm::mat4 worldCameraMat = thirdPersonCameraSensorToWorldMatrix * sensorCameraMat;             _myCamera.setOrientation(glm::normalize(glmExtractRotation(worldCameraMat)));            _myCamera.setPosition(extractTranslation(worldCameraMat));        } else {            float boomLength = myAvatar->getBoomLength();            if (getCameraClippingEnabled()) {                auto result =                    DependencyManager::get<PickManager>()->getPrevPickResultTyped<RayPickResult>(_cameraClippingRayPickID);                if (result && result->doesIntersect()) {                    const float CAMERA_CLIPPING_EPSILON = 0.1f;                    boomLength = std::min(boomLength, result->distance - CAMERA_CLIPPING_EPSILON);                }            }            glm::vec3 boomOffset = myAvatar->getModelScale() * boomLength * -IDENTITY_FORWARD;            _thirdPersonHMDCameraBoomValid = false;            if (mode == CAMERA_MODE_THIRD_PERSON) {                _myCamera.setOrientation(myAvatar->getHead()->getOrientation());                if (isOptionChecked(MenuOption::CenterPlayerInView)) {                    _myCamera.setPosition(myAvatar->getDefaultEyePosition()                        + _myCamera.getOrientation() * boomOffset);                } else {                    _myCamera.setPosition(myAvatar->getDefaultEyePosition()                        + myAvatar->getWorldOrientation() * boomOffset);                }            } else {                glm::quat lookAtRotation = myAvatar->getLookAtRotation();                if (mode == CAMERA_MODE_SELFIE) {                    lookAtRotation = lookAtRotation * glm::angleAxis(PI, myAvatar->getWorldOrientation() * Vectors::UP);                }                _myCamera.setPosition(myAvatar->getLookAtPivotPoint()                    + lookAtRotation * boomOffset);                _myCamera.lookAt(myAvatar->getLookAtPivotPoint());            }        }    } else if (mode == CAMERA_MODE_MIRROR) {        _thirdPersonHMDCameraBoomValid= false;         if (isHMDMode()) {            auto mirrorBodyOrientation = myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, PI + _mirrorYawOffset, 0.0f));             glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());            // Mirror HMD yaw and roll            glm::vec3 mirrorHmdEulers = glm::eulerAngles(hmdRotation);            mirrorHmdEulers.y = -mirrorHmdEulers.y;            mirrorHmdEulers.z = -mirrorHmdEulers.z;            glm::quat mirrorHmdRotation = glm::quat(mirrorHmdEulers);             glm::quat worldMirrorRotation = mirrorBodyOrientation * mirrorHmdRotation;             _myCamera.setOrientation(worldMirrorRotation);             glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix());            // Mirror HMD lateral offsets            hmdOffset.x = -hmdOffset.x;             _myCamera.setPosition(myAvatar->getDefaultEyePosition()                + glm::vec3(0, _raiseMirror * myAvatar->getModelScale(), 0)                + mirrorBodyOrientation * glm::vec3(0.0f, 0.0f, 1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror                + mirrorBodyOrientation * hmdOffset);        } else {            auto userInputMapper = DependencyManager::get<UserInputMapper>();            const float YAW_SPEED = TWO_PI / 5.0f;            float deltaYaw = userInputMapper->getActionState(controller::Action::YAW) * YAW_SPEED * deltaTime;            _mirrorYawOffset += deltaYaw;            _myCamera.setOrientation(myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, PI + _mirrorYawOffset, 0.0f)));            _myCamera.setPosition(myAvatar->getDefaultEyePosition()                + glm::vec3(0, _raiseMirror * myAvatar->getModelScale(), 0)                + (myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, _mirrorYawOffset, 0.0f))) *                glm::vec3(0.0f, 0.0f, -1.0f) * myAvatar->getBoomLength() * _scaleMirror);        }        renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;    } else if (mode == CAMERA_MODE_ENTITY) {        _thirdPersonHMDCameraBoomValid= false;        EntityItemPointer cameraEntity = _myCamera.getCameraEntityPointer();        if (cameraEntity != nullptr) {            if (isHMDMode()) {                glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());                _myCamera.setOrientation(cameraEntity->getWorldOrientation() * hmdRotation);                glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix());                _myCamera.setPosition(cameraEntity->getWorldPosition() + (hmdRotation * hmdOffset));            } else {                _myCamera.setOrientation(cameraEntity->getWorldOrientation());                _myCamera.setPosition(cameraEntity->getWorldPosition());            }        }    }    // Update camera position    if (!isHMDMode()) {        _myCamera.update();    }     renderArgs._cameraMode = (int8_t)mode;     const bool shouldEnableCameraClipping =        (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) && !isHMDMode() &&        getCameraClippingEnabled();    if (_prevCameraClippingEnabled != shouldEnableCameraClipping) {        if (shouldEnableCameraClipping) {            DependencyManager::get<PickManager>()->enablePick(_cameraClippingRayPickID);        } else {            DependencyManager::get<PickManager>()->disablePick(_cameraClippingRayPickID);        }        _prevCameraClippingEnabled = shouldEnableCameraClipping;    }} void Application::updateSecondaryCameraViewFrustum() {    // TODO: Fix this by modeling the way the secondary camera works on how the main camera works    // ie. Use a camera object stored in the game logic and informs the Engine on where the secondary    // camera should be.     // Code based on SecondaryCameraJob    auto renderConfig = _graphicsEngine->getRenderEngine()->getConfiguration();    assert(renderConfig);    auto camera = dynamic_cast<SecondaryCameraJobConfig*>(renderConfig->getConfig("SecondaryCamera"));     if (!camera || !camera->isEnabled()) {        return;    }     ViewFrustum secondaryViewFrustum;    if (camera->portalProjection && !camera->attachedEntityId.isNull() && !camera->portalEntranceEntityId.isNull()) {        auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();        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<EntityScriptingInterface>();        auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId);        glm::vec3 mirrorPropertiesPosition = entityProperties.getPosition();        glm::quat mirrorPropertiesRotation = entityProperties.getRotation();        glm::vec3 mirrorPropertiesDimensions = entityProperties.getDimensions();        glm::vec3 halfMirrorPropertiesDimensions = 0.5f * mirrorPropertiesDimensions;         // setup mirror from world as inverse of world from mirror transformation using inverted x and z for mirrored image        // TODO: we are assuming here that UP is world y-axis        glm::mat4 worldFromMirrorRotation = glm::mat4_cast(mirrorPropertiesRotation) * glm::scale(vec3(-1.0f, 1.0f, -1.0f));        glm::mat4 worldFromMirrorTranslation = glm::translate(mirrorPropertiesPosition);        glm::mat4 worldFromMirror = worldFromMirrorTranslation * worldFromMirrorRotation;        glm::mat4 mirrorFromWorld = glm::inverse(worldFromMirror);         // get mirror camera position by reflecting main camera position's z coordinate in mirror space        glm::vec3 mainCameraPositionWorld = getCamera().getPosition();        glm::vec3 mainCameraPositionMirror = vec3(mirrorFromWorld * vec4(mainCameraPositionWorld, 1.0f));        glm::vec3 mirrorCameraPositionMirror = vec3(mainCameraPositionMirror.x, mainCameraPositionMirror.y,                                                    -mainCameraPositionMirror.z);        glm::vec3 mirrorCameraPositionWorld = vec3(worldFromMirror * vec4(mirrorCameraPositionMirror, 1.0f));         // set frustum position to be mirrored camera and set orientation to mirror's adjusted rotation        glm::quat mirrorCameraOrientation = glm::quat_cast(worldFromMirrorRotation);        secondaryViewFrustum.setPosition(mirrorCameraPositionWorld);        secondaryViewFrustum.setOrientation(mirrorCameraOrientation);         // build frustum using mirror space translation of mirrored camera        float nearClip = mirrorCameraPositionMirror.z + mirrorPropertiesDimensions.z * 2.0f;        glm::vec3 upperRight = halfMirrorPropertiesDimensions - mirrorCameraPositionMirror;        glm::vec3 bottomLeft = -halfMirrorPropertiesDimensions - mirrorCameraPositionMirror;        glm::mat4 frustum = glm::frustum(bottomLeft.x, upperRight.x, bottomLeft.y, upperRight.y, nearClip, camera->farClipPlaneDistance);        secondaryViewFrustum.setProjection(frustum);    } else {        if (!camera->attachedEntityId.isNull()) {            auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();            auto entityProperties = entityScriptingInterface->getEntityProperties(camera->attachedEntityId);            secondaryViewFrustum.setPosition(entityProperties.getPosition());            secondaryViewFrustum.setOrientation(entityProperties.getRotation());        } else {            secondaryViewFrustum.setPosition(camera->position);            secondaryViewFrustum.setOrientation(camera->orientation);        }         float aspectRatio = (float)camera->textureWidth / (float)camera->textureHeight;        secondaryViewFrustum.setProjection(camera->vFoV,                                            aspectRatio,                                            camera->nearClipPlaneDistance,                                            camera->farClipPlaneDistance);    }    // Without calculating the bound planes, the secondary camera will use the same culling frustum as the main camera,    // which is not what we want here.    secondaryViewFrustum.calculate();     _conicalViews.push_back(secondaryViewFrustum);} void Application::setFieldOfView(float fov) {    if (fov != _fieldOfView.get()) {        _fieldOfView.set(fov);        resizeGL();    }} void Application::setCameraClippingEnabled(bool enabled) {    _cameraClippingEnabled.set(enabled);    _prevCameraClippingEnabled = enabled;    if (enabled) {        DependencyManager::get<PickManager>()->enablePick(_cameraClippingRayPickID);    } else {        DependencyManager::get<PickManager>()->disablePick(_cameraClippingRayPickID);    }} // Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent// to everyone.// The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition().// Note that it is called BEFORE we update position or joints based on sensors, etc.void Application::updateMyAvatarLookAtPosition(float deltaTime) {    PerformanceTimer perfTimer("lookAt");    bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);    PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()");     auto myAvatar = getMyAvatar();    myAvatar->updateEyesLookAtPosition(deltaTime);} void Application::cycleCamera() {    auto menu = Menu::getInstance();    if (menu->isOptionChecked(MenuOption::FirstPersonLookAt)) {         menu->setIsOptionChecked(MenuOption::FirstPersonLookAt, false);        menu->setIsOptionChecked(MenuOption::LookAtCamera, true);     } else if (menu->isOptionChecked(MenuOption::LookAtCamera)) {         menu->setIsOptionChecked(MenuOption::LookAtCamera, false);        if (menu->getActionForOption(MenuOption::SelfieCamera)->isVisible()) {            menu->setIsOptionChecked(MenuOption::SelfieCamera, true);        } else {            menu->setIsOptionChecked(MenuOption::FirstPersonLookAt, true);        }     } else if (menu->isOptionChecked(MenuOption::SelfieCamera)) {         menu->setIsOptionChecked(MenuOption::SelfieCamera, false);        menu->setIsOptionChecked(MenuOption::FirstPersonLookAt, true);     }    cameraMenuChanged(); // handle the menu change} void Application::cameraModeChanged() {    switch (_myCamera.getMode()) {        case CAMERA_MODE_FIRST_PERSON_LOOK_AT:            Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPersonLookAt, true);            break;        case CAMERA_MODE_LOOK_AT:            Menu::getInstance()->setIsOptionChecked(MenuOption::LookAtCamera, true);            break;        case CAMERA_MODE_SELFIE:            Menu::getInstance()->setIsOptionChecked(MenuOption::SelfieCamera, true);            break;        default:            // we don't have menu items for the others, so just leave it alone.            return;    }    cameraMenuChanged();} void Application::cameraMenuChanged() {    auto menu = Menu::getInstance();    if (menu->isOptionChecked(MenuOption::FirstPersonLookAt)) {        if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON_LOOK_AT) {            _myCamera.setMode(CAMERA_MODE_FIRST_PERSON_LOOK_AT);            getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN);        }    } else if (menu->isOptionChecked(MenuOption::LookAtCamera)) {        if (_myCamera.getMode() != CAMERA_MODE_LOOK_AT) {            _myCamera.setMode(CAMERA_MODE_LOOK_AT);            if (getMyAvatar()->getBoomLength() == MyAvatar::ZOOM_MIN) {                getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);            }        }    } else if (menu->isOptionChecked(MenuOption::SelfieCamera)) {        if (_myCamera.getMode() != CAMERA_MODE_SELFIE) {            _myCamera.setMode(CAMERA_MODE_SELFIE);            if (getMyAvatar()->getBoomLength() == MyAvatar::ZOOM_MIN) {                getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);            }        }    }} void Application::changeViewAsNeeded(float boomLength) {    // Switch between first and third person views as needed    // This is called when the boom length has changed    bool boomLengthGreaterThanMinimum = (boomLength > MyAvatar::ZOOM_MIN);     if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON_LOOK_AT && boomLengthGreaterThanMinimum) {        Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPersonLookAt, false);        Menu::getInstance()->setIsOptionChecked(MenuOption::LookAtCamera, true);        cameraMenuChanged();    } else if (_myCamera.getMode() == CAMERA_MODE_LOOK_AT && !boomLengthGreaterThanMinimum) {        Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPersonLookAt, true);        Menu::getInstance()->setIsOptionChecked(MenuOption::LookAtCamera, false);        cameraMenuChanged();    }}