From 18c7320879299962d506b2521f86aa7a1c920c3a Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Fri, 1 Nov 2024 00:54:32 -0700 Subject: [PATCH] 3rd person camera clipping option --- .../dialogs/graphics/GraphicsSettings.qml | 30 ++++++++++++ interface/src/Application.cpp | 49 +++++++++++++++++-- interface/src/Application.h | 7 +++ interface/src/CameraRootTransformNode.cpp | 48 ++++++++++++++++++ interface/src/CameraRootTransformNode.h | 20 ++++++++ .../src/raypick/PickScriptingInterface.cpp | 4 ++ .../scripting/RenderScriptingInterface.cpp | 7 +++ .../src/scripting/RenderScriptingInterface.h | 18 ++++++- interface/src/ui/PreferencesDialog.cpp | 7 ++- scripts/system/create/edit.js | 5 ++ 10 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 interface/src/CameraRootTransformNode.cpp create mode 100644 interface/src/CameraRootTransformNode.h diff --git a/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml b/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml index a928b1379f..a0804957be 100644 --- a/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml +++ b/interface/resources/qml/hifi/dialogs/graphics/GraphicsSettings.qml @@ -658,6 +658,36 @@ Flickable { } } } + Item { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 35 + Layout.topMargin: 16 + + HifiStylesUit.RalewayRegular { + id: enableCameraClippingHeader + text: "3rd Person Camera Clipping" + anchors.left: parent.left + anchors.top: parent.top + width: 200 + height: parent.height + size: 16 + color: "#FFFFFF" + } + + HifiControlsUit.CheckBox { + id: enableCameraClipping + checked: Render.cameraClippingEnabled + boxSize: 16 + spacing: -1 + colorScheme: hifi.colorSchemes.dark + anchors.left: enableCameraClippingHeader.right + anchors.leftMargin: 20 + anchors.top: parent.top + onCheckedChanged: { + Render.cameraClippingEnabled = enableCameraClipping.checked; + } + } + } } ColumnLayout { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index db8a5dc0cf..c5951a2aff 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -241,6 +241,7 @@ #include #include #include +#include #include "ResourceRequestObserver.h" @@ -1004,6 +1005,7 @@ Application::Application( _previousSessionCrashed(false), //setupEssentials(parser, false)), _previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION), _fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES), + _cameraClippingEnabled("cameraClippingEnabled", false), _hmdTabletScale("hmdTabletScale", DEFAULT_HMD_TABLET_SCALE_PERCENT), _desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT), _firstRun(Settings::firstRun, true), @@ -2499,6 +2501,17 @@ void Application::initialize(const QCommandLineParser &parser) { DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); + // Setup the camera clipping ray pick + { + _prevCameraClippingEnabled = _cameraClippingEnabled.get(); + auto cameraRayPick = std::make_shared(Vectors::ZERO, -Vectors::UP, + PickFilter(PickScriptingInterface::getPickEntities() | + PickScriptingInterface::getPickLocalEntities()), + MyAvatar::ZOOM_MAX, 0.0f, _prevCameraClippingEnabled); + cameraRayPick->parentTransform = std::make_shared(); + _cameraClippingRayPickID = DependencyManager::get()->addPick(PickQuery::Ray, cameraRayPick); + } + BillboardModeHelpers::setBillboardRotationOperator([](const glm::vec3& position, const glm::quat& rotation, BillboardMode billboardMode, const glm::vec3& frustumPos, bool rotate90x) { const glm::quat ROTATE_90X = glm::angleAxis(-(float)M_PI_2, Vectors::RIGHT); @@ -3684,9 +3697,7 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { PROFILE_RANGE(render, __FUNCTION__); PerformanceTimer perfTimer("updateCamera"); - glm::vec3 boomOffset; auto myAvatar = getMyAvatar(); - boomOffset = myAvatar->getModelScale() * myAvatar->getBoomLength() * -IDENTITY_FORWARD; // The render mode is default or mirror if the camera is in mirror mode, assigned further below renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE; @@ -3725,6 +3736,16 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { _myCamera.setOrientation(glm::normalize(glmExtractRotation(worldCameraMat))); _myCamera.setPosition(extractTranslation(worldCameraMat)); } else { + float boomLength = myAvatar->getBoomLength(); + if (getCameraClippingEnabled()) { + auto result = + DependencyManager::get()->getPrevPickResultTyped(_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()); @@ -3802,7 +3823,19 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) { _myCamera.update(); } - renderArgs._cameraMode = (int8_t)_myCamera.getMode(); + 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()->enablePick(_cameraClippingRayPickID); + } else { + DependencyManager::get()->disablePick(_cameraClippingRayPickID); + } + _prevCameraClippingEnabled = shouldEnableCameraClipping; + } } void Application::runTests() { @@ -3817,6 +3850,16 @@ void Application::setFieldOfView(float fov) { } } +void Application::setCameraClippingEnabled(bool enabled) { + _cameraClippingEnabled.set(enabled); + _prevCameraClippingEnabled = enabled; + if (enabled) { + DependencyManager::get()->enablePick(_cameraClippingRayPickID); + } else { + DependencyManager::get()->disablePick(_cameraClippingRayPickID); + } +} + void Application::setHMDTabletScale(float hmdTabletScale) { _hmdTabletScale.set(hmdTabletScale); } diff --git a/interface/src/Application.h b/interface/src/Application.h index ec575b384d..2c7a97a5eb 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -246,6 +246,9 @@ class Application : public QApplication, float getFieldOfView() { return _fieldOfView.get(); } void setFieldOfView(float fov); + bool getCameraClippingEnabled() { return _cameraClippingEnabled.get(); } + void setCameraClippingEnabled(bool enabled); + float getHMDTabletScale() { return _hmdTabletScale.get(); } void setHMDTabletScale(float hmdTabletScale); float getDesktopTabletScale() { return _desktopTabletScale.get(); } @@ -719,6 +722,7 @@ private slots: Setting::Handle _previousScriptLocation; Setting::Handle _fieldOfView; + Setting::Handle _cameraClippingEnabled; Setting::Handle _hmdTabletScale; Setting::Handle _desktopTabletScale; Setting::Handle _firstRun; @@ -893,5 +897,8 @@ private slots: DiscordPresence* _discordPresence{ nullptr }; bool _profilingInitialized { false }; + + bool _prevCameraClippingEnabled { false }; + unsigned int _cameraClippingRayPickID; }; #endif // hifi_Application_h diff --git a/interface/src/CameraRootTransformNode.cpp b/interface/src/CameraRootTransformNode.cpp new file mode 100644 index 0000000000..596bdab3d3 --- /dev/null +++ b/interface/src/CameraRootTransformNode.cpp @@ -0,0 +1,48 @@ +// +// Created by HifiExperiments on 10/30/2024 +// Copyright 2024 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 +// + +#include "CameraRootTransformNode.h" + +#include "Application.h" +#include "DependencyManager.h" +#include "avatar/AvatarManager.h" +#include "avatar/MyAvatar.h" + +Transform CameraRootTransformNode::getTransform() { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + glm::vec3 pos; + glm::quat ori; + + CameraMode mode = qApp->getCamera().getMode(); + if (mode == CAMERA_MODE_FIRST_PERSON || mode == CAMERA_MODE_THIRD_PERSON) { + pos = myAvatar->getDefaultEyePosition(); + ori = myAvatar->getHeadOrientation(); + } else if (mode == CAMERA_MODE_FIRST_PERSON_LOOK_AT) { + pos = myAvatar->getCameraEyesPosition(0.0f); + ori = myAvatar->getLookAtRotation(); + } else { + ori = myAvatar->getLookAtRotation(); + pos = myAvatar->getLookAtPivotPoint(); + + if (mode == CAMERA_MODE_SELFIE) { + ori = ori * glm::angleAxis(PI, ori * Vectors::UP); + } + } + + ori = ori * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT); + + glm::vec3 scale = glm::vec3(myAvatar->scaleForChildren()); + return Transform(ori, scale, pos); +} + +QVariantMap CameraRootTransformNode::toVariantMap() const { + QVariantMap map; + map["joint"] = "CameraRoot"; + return map; +} diff --git a/interface/src/CameraRootTransformNode.h b/interface/src/CameraRootTransformNode.h new file mode 100644 index 0000000000..6a0f58f42e --- /dev/null +++ b/interface/src/CameraRootTransformNode.h @@ -0,0 +1,20 @@ +// +// Created by HifiExperiments on 10/30/2024 +// Copyright 2024 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 +// +#ifndef hifi_CameraRootTransformNode_h +#define hifi_CameraRootTransformNode_h + +#include "TransformNode.h" + +class CameraRootTransformNode : public TransformNode { +public: + CameraRootTransformNode() {} + Transform getTransform() override; + QVariantMap toVariantMap() const override; +}; + +#endif // hifi_CameraRootTransformNode_h diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 5323c52faf..221b0fcb63 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -21,6 +21,7 @@ #include "ParabolaPick.h" #include "CollisionPick.h" +#include "CameraRootTransformNode.h" #include "SpatialParentFinder.h" #include "PickTransformNode.h" #include "MouseTransformNode.h" @@ -537,6 +538,9 @@ void PickScriptingInterface::setParentTransform(std::shared_ptr pick, } else if (joint == "Avatar") { pick->parentTransform = std::make_shared(); return; + } else if (joint == "CameraRoot") { + pick->parentTransform = std::make_shared(); + return; } else { parentUuid = myAvatar->getSessionUUID(); parentJointIndex = myAvatar->getJointIndex(joint); diff --git a/interface/src/scripting/RenderScriptingInterface.cpp b/interface/src/scripting/RenderScriptingInterface.cpp index e126a5734e..db3e398547 100644 --- a/interface/src/scripting/RenderScriptingInterface.cpp +++ b/interface/src/scripting/RenderScriptingInterface.cpp @@ -342,6 +342,13 @@ void RenderScriptingInterface::setVerticalFieldOfView(float fieldOfView) { } } +void RenderScriptingInterface::setCameraClippingEnabled(bool enabled) { + if (qApp->getCameraClippingEnabled() != enabled) { + qApp->setCameraClippingEnabled(enabled); + emit settingsChanged(); + } +} + QStringList RenderScriptingInterface::getScreens() const { QStringList screens; diff --git a/interface/src/scripting/RenderScriptingInterface.h b/interface/src/scripting/RenderScriptingInterface.h index 56b474cf31..97d7868a96 100644 --- a/interface/src/scripting/RenderScriptingInterface.h +++ b/interface/src/scripting/RenderScriptingInterface.h @@ -37,6 +37,7 @@ * they're disabled. * @property {integer} antialiasingMode - The active anti-aliasing mode. * @property {number} viewportResolutionScale - The view port resolution scale, > 0.0. + * @property {boolean} cameraClippingEnabled - true if third person camera clipping is enabled, false if it's disabled. */ class RenderScriptingInterface : public QObject { Q_OBJECT @@ -49,6 +50,7 @@ class RenderScriptingInterface : public QObject { Q_PROPERTY(AntialiasingConfig::Mode antialiasingMode READ getAntialiasingMode WRITE setAntialiasingMode NOTIFY settingsChanged) Q_PROPERTY(float viewportResolutionScale READ getViewportResolutionScale WRITE setViewportResolutionScale NOTIFY settingsChanged) Q_PROPERTY(float verticalFieldOfView READ getVerticalFieldOfView WRITE setVerticalFieldOfView NOTIFY settingsChanged) + Q_PROPERTY(bool cameraClippingEnabled READ getCameraClippingEnabled WRITE setCameraClippingEnabled NOTIFY settingsChanged) public: RenderScriptingInterface(); @@ -261,7 +263,21 @@ public slots: * @function Render.setVerticalFieldOfView * @param {number} fieldOfView - The vertical field of view in degrees to set. */ - void setVerticalFieldOfView( float fieldOfView ); + void setVerticalFieldOfView(float fieldOfView); + + /*@jsdoc + * Gets whether or not third person camera clipping is enabled. + * @function Render.getCameraClippingEnabled + * @returns {boolean} true if camera clipping is enabled, false if it's disabled. + */ + bool getCameraClippingEnabled() { return qApp->getCameraClippingEnabled(); } + + /*@jsdoc + * Sets whether or not third person camera clipping is enabled. + * @function Render.setCameraClippingEnabled + * @param {boolean} enabled - true to enable third person camera clipping, false to disable. + */ + void setCameraClippingEnabled(bool enabled); signals: diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 4c87f12998..623aede2fa 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -231,7 +231,7 @@ void setupPreferences() { preferences->addPreference(new CheckPreference(UI_CATEGORY, "Show Graphics icon on tablet and toolbar", getter, setter)); } - static const QString VIEW_CATEGORY{ "View" }; + static const QString VIEW_CATEGORY { "View" }; { auto getter = [myAvatar]()->float { return myAvatar->getRealWorldFieldOfView(); }; auto setter = [myAvatar](float value) { myAvatar->setRealWorldFieldOfView(value); }; @@ -249,6 +249,11 @@ void setupPreferences() { preference->setStep(1); preferences->addPreference(preference); } + { + auto getter = []()->bool { return qApp->getCameraClippingEnabled(); }; + auto setter = [](bool value) { qApp->setCameraClippingEnabled(value); }; + preferences->addPreference(new CheckPreference(VIEW_CATEGORY, "Enable 3rd Person Camera Clipping?", getter, setter)); + } // Snapshots static const QString SNAPSHOTS { "Snapshots" }; diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index 82cab1c76c..24df1c9e0f 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -261,6 +261,8 @@ visible: false }); + var savedClippingEnabled = false; + function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { // Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original // position in the given direction. @@ -1195,6 +1197,7 @@ selectionDisplay.disableTriggerMapping(); tablet.landscape = false; Controller.disableMapping(CONTROLLER_MAPPING_NAME); + Render.cameraClippingEnabled = savedClippingEnabled; } else { if (shouldUseEditTabletApp()) { tablet.loadQMLSource(Script.resolvePath("qml/Edit.qml"), true); @@ -1212,6 +1215,8 @@ print("starting tablet in landscape mode"); tablet.landscape = true; Controller.enableMapping(CONTROLLER_MAPPING_NAME); + savedClippingEnabled = Render.cameraClippingEnabled; + Render.cameraClippingEnabled = false; // Not sure what the following was meant to accomplish, but it currently causes // everybody else to think that Interface has lost focus overall. fogbugzid:558 // Window.setFocus();