diff --git a/interface/resources/shaders/hmd_ui.frag b/interface/resources/shaders/hmd_ui.frag new file mode 100644 index 0000000000..5341ab575d --- /dev/null +++ b/interface/resources/shaders/hmd_ui.frag @@ -0,0 +1,30 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +uniform sampler2D sampler; + +struct OverlayData { + mat4 mvp; + float alpha; +}; + +layout(std140) uniform overlayBuffer { + OverlayData overlay; +}; + +in vec2 vTexCoord; + +out vec4 FragColor; + +void main() { + FragColor = texture(sampler, vTexCoord); + FragColor.a *= overlay.alpha; + if (FragColor.a <= 0.0) { + discard; + } +} \ No newline at end of file diff --git a/interface/resources/shaders/hmd_ui_glow.vert b/interface/resources/shaders/hmd_ui.vert similarity index 76% rename from interface/resources/shaders/hmd_ui_glow.vert rename to interface/resources/shaders/hmd_ui.vert index 71089d8608..41b9b3666f 100644 --- a/interface/resources/shaders/hmd_ui_glow.vert +++ b/interface/resources/shaders/hmd_ui.vert @@ -8,12 +8,7 @@ struct OverlayData { mat4 mvp; - vec4 glowPoints; - vec4 glowColors[2]; - vec4 resolutionRadiusAlpha; - - vec4 extraGlowColor; - vec2 extraGlowPoint; + float alpha; }; layout(std140) uniform overlayBuffer { @@ -25,11 +20,9 @@ mat4 mvp = overlay.mvp; layout(location = 0) in vec3 Position; layout(location = 3) in vec2 TexCoord; -out vec3 vPosition; out vec2 vTexCoord; void main() { gl_Position = mvp * vec4(Position, 1); vTexCoord = TexCoord; - vPosition = Position; } diff --git a/interface/resources/shaders/hmd_ui_glow.frag b/interface/resources/shaders/hmd_ui_glow.frag deleted file mode 100644 index 5dda76e89d..0000000000 --- a/interface/resources/shaders/hmd_ui_glow.frag +++ /dev/null @@ -1,86 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/07/11 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -uniform sampler2D sampler; - -struct OverlayData { - mat4 mvp; - vec4 glowPoints; - vec4 glowColors[2]; - vec4 resolutionRadiusAlpha; - - vec4 extraGlowColor; - vec2 extraGlowPoint; -}; - -layout(std140) uniform overlayBuffer { - OverlayData overlay; -}; - -vec2 resolution = overlay.resolutionRadiusAlpha.xy; -float radius = overlay.resolutionRadiusAlpha.z; -float alpha = overlay.resolutionRadiusAlpha.w; -vec4 glowPoints = overlay.glowPoints; -vec4 glowColors[2] = overlay.glowColors; - -vec2 extraGlowPoint = overlay.extraGlowPoint; -vec4 extraGlowColor = overlay.extraGlowColor; - -in vec3 vPosition; -in vec2 vTexCoord; - -out vec4 FragColor; - -float easeInOutCubic(float f) { - const float d = 1.0; - const float b = 0.0; - const float c = 1.0; - float t = f; - if ((t /= d / 2.0) < 1.0) return c / 2.0 * t * t * t + b; - return c / 2.0 * ((t -= 2.0) * t * t + 2.0) + b; -} - -void main() { - FragColor = texture(sampler, vTexCoord); - - vec2 aspect = resolution; - aspect /= resolution.x; - - float glowIntensity = 0.0; - float dist1 = distance(vTexCoord * aspect, glowPoints.xy * aspect); - float dist2 = distance(vTexCoord * aspect, glowPoints.zw * aspect); - float dist3 = distance(vTexCoord * aspect, extraGlowPoint * aspect); - float distX = min(dist1, dist2); - float dist = min(distX, dist3); - vec3 glowColor = glowColors[0].rgb; - if (dist2 < dist1) { - glowColor = glowColors[1].rgb; - } - if (dist3 < dist2) { - glowColor = extraGlowColor.rgb; - } - - if (dist <= radius) { - glowIntensity = 1.0 - (dist / radius); - glowColor.rgb = pow(glowColor, vec3(1.0 - glowIntensity)); - glowIntensity = easeInOutCubic(glowIntensity); - glowIntensity = pow(glowIntensity, 0.5); - } - - if (alpha <= 0.0) { - if (glowIntensity <= 0.0) { - discard; - } - - FragColor = vec4(glowColor, glowIntensity); - return; - } - - FragColor.rgb = mix(FragColor.rgb, glowColor.rgb, glowIntensity); - FragColor.a *= alpha; -} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cc9bace9aa..2837754858 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -193,6 +193,9 @@ #include #include #include +#include +#include + #include "commerce/Ledger.h" #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" @@ -604,6 +607,9 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + return previousSessionCrashed; } @@ -2417,6 +2423,7 @@ void Application::paintGL() { finalFramebuffer = framebufferCache->getFramebuffer(); } + mat4 eyeProjections[2]; { PROFILE_RANGE(render, "/mainRender"); PerformanceTimer perfTimer("mainRender"); @@ -2438,7 +2445,6 @@ void Application::paintGL() { _myCamera.setProjection(displayPlugin->getCullingProjection(_myCamera.getProjection())); renderArgs._context->enableStereo(true); mat4 eyeOffsets[2]; - mat4 eyeProjections[2]; auto baseProjection = renderArgs.getViewFrustum().getProjection(); auto hmdInterface = DependencyManager::get(); float IPDScale = hmdInterface->getIPDScale(); @@ -2469,6 +2475,19 @@ void Application::paintGL() { displaySide(&renderArgs, _myCamera); } + gpu::Batch postCompositeBatch; + { + PROFILE_RANGE(render, "/postComposite"); + PerformanceTimer perfTimer("postComposite"); + renderArgs._batch = &postCompositeBatch; + renderArgs._batch->setViewportTransform(ivec4(0, 0, finalFramebufferSize.width(), finalFramebufferSize.height())); + renderArgs._batch->setViewTransform(renderArgs.getViewFrustum().getView()); + for_each_eye([&](Eye eye) { + renderArgs._batch->setProjectionTransform(eyeProjections[eye]); + _overlays.render3DHUDOverlays(&renderArgs); + }); + } + auto frame = _gpuContext->endFrame(); frame->frameIndex = _frameCount; frame->framebuffer = finalFramebuffer; @@ -2476,6 +2495,7 @@ void Application::paintGL() { DependencyManager::get()->releaseFramebuffer(framebuffer); }; frame->overlay = _applicationOverlay.getOverlayTexture(); + frame->postCompositeBatch = postCompositeBatch; // deliver final scene rendering commands to the display plugin { PROFILE_RANGE(render, "/pluginOutput"); @@ -5028,6 +5048,16 @@ void Application::update(float deltaTime) { _overlays.update(deltaTime); } + { + PROFILE_RANGE(app, "RayPick"); + DependencyManager::get()->update(); + } + + { + PROFILE_RANGE(app, "LaserPointerManager"); + _laserPointerManager.update(); + } + // Update _viewFrustum with latest camera and view frustum data... // NOTE: we get this from the view frustum, to make it simpler, since the // loadViewFrumstum() method will get the correct details from the camera @@ -5888,6 +5918,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("AudioScope", DependencyManager::get().data()); scriptEngine->registerGlobalObject("AvatarBookmarks", DependencyManager::get().data()); scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("LaserPointers", DependencyManager::get().data()); // Caches scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); @@ -5941,6 +5972,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance()); scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("RayPick", DependencyManager::get().data()); + qScriptRegisterMetaType(scriptEngine, OverlayIDtoScriptValue, OverlayIDfromScriptValue); // connect this script engines printedMessage signal to the global ScriptEngines these various messages diff --git a/interface/src/Application.h b/interface/src/Application.h index f8eb393f9e..83ae0b036a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -70,6 +70,7 @@ #include "ui/OverlayConductor.h" #include "ui/overlays/Overlays.h" #include "UndoStackScriptingInterface.h" +#include "raypick/LaserPointerManager.h" #include #include @@ -300,6 +301,8 @@ public: QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; } bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; } + LaserPointerManager& getLaserPointerManager() { return _laserPointerManager; } + signals: void svoImportRequested(const QString& url); @@ -697,5 +700,7 @@ private: QUrl _avatarOverrideUrl; bool _saveAvatarOverrideUrl { false }; + LaserPointerManager _laserPointerManager; + }; #endif // hifi_Application_h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index bd545c64e0..35039f168c 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -476,19 +476,25 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude, const QScriptValue& avatarIdsToDiscard) { + QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); + QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); + + return findRayIntersection(ray, avatarsToInclude, avatarsToDiscard); +} + +RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard) { RayToAvatarIntersectionResult result; if (QThread::currentThread() != thread()) { BLOCKING_INVOKE_METHOD(const_cast(this), "findRayIntersection", Q_RETURN_ARG(RayToAvatarIntersectionResult, result), Q_ARG(const PickRay&, ray), - Q_ARG(const QScriptValue&, avatarIdsToInclude), - Q_ARG(const QScriptValue&, avatarIdsToDiscard)); + Q_ARG(const QVector&, avatarsToInclude), + Q_ARG(const QVector&, avatarsToDiscard)); return result; } - QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); - QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); - glm::vec3 normDirection = glm::normalize(ray.direction); for (auto avatarData : _avatarHash) { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 30801807d6..c21214484b 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -73,6 +73,9 @@ public: Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude = QScriptValue(), const QScriptValue& avatarIdsToDiscard = QScriptValue()); + Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard); // TODO: remove this HACK once we settle on optimal default sort coefficients Q_INVOKABLE float getAvatarSortCoefficient(const QString& name); diff --git a/interface/src/raypick/JointRayPick.cpp b/interface/src/raypick/JointRayPick.cpp new file mode 100644 index 0000000000..6803700e4b --- /dev/null +++ b/interface/src/raypick/JointRayPick.cpp @@ -0,0 +1,48 @@ +// +// JointRayPick.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "JointRayPick.h" + +#include "DependencyManager.h" +#include "avatar/AvatarManager.h" + +JointRayPick::JointRayPick(const QString& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const uint16_t filter, const float maxDistance, const bool enabled) : + RayPick(filter, maxDistance, enabled), + _jointName(jointName), + _posOffset(posOffset), + _dirOffset(dirOffset) +{ +} + +const PickRay JointRayPick::getPickRay(bool& valid) const { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + int jointIndex = myAvatar->getJointIndex(_jointName); + bool useAvatarHead = _jointName == "Avatar"; + const int INVALID_JOINT = -1; + if (jointIndex != INVALID_JOINT || useAvatarHead) { + glm::vec3 jointPos = useAvatarHead ? myAvatar->getHeadPosition() : myAvatar->getAbsoluteJointTranslationInObjectFrame(jointIndex); + glm::quat jointRot = useAvatarHead ? myAvatar->getHeadOrientation() : myAvatar->getAbsoluteJointRotationInObjectFrame(jointIndex); + glm::vec3 avatarPos = myAvatar->getPosition(); + glm::quat avatarRot = myAvatar->getOrientation(); + + glm::vec3 pos = useAvatarHead ? jointPos : avatarPos + (avatarRot * jointPos); + glm::quat rot = useAvatarHead ? jointRot * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT) : avatarRot * jointRot; + + // Apply offset + pos = pos + (rot * _posOffset); + glm::vec3 dir = rot * glm::normalize(_dirOffset); + + valid = true; + return PickRay(pos, dir); + } + + valid = false; + return PickRay(); +} diff --git a/interface/src/raypick/JointRayPick.h b/interface/src/raypick/JointRayPick.h new file mode 100644 index 0000000000..0631372bdb --- /dev/null +++ b/interface/src/raypick/JointRayPick.h @@ -0,0 +1,32 @@ +// +// JointRayPick.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// 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_JointRayPick_h +#define hifi_JointRayPick_h + +#include "RayPick.h" + +#include + +class JointRayPick : public RayPick { + +public: + JointRayPick(const QString& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const uint16_t filter, const float maxDistance = 0.0f, const bool enabled = false); + + const PickRay getPickRay(bool& valid) const override; + +private: + QString _jointName; + glm::vec3 _posOffset; + glm::vec3 _dirOffset; + +}; + +#endif // hifi_JointRayPick_h diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp new file mode 100644 index 0000000000..9dc1bdd72e --- /dev/null +++ b/interface/src/raypick/LaserPointer.cpp @@ -0,0 +1,220 @@ +// +// LaserPointer.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "LaserPointer.h" + +#include "Application.h" +#include "avatar/AvatarManager.h" + +LaserPointer::LaserPointer(const QVariantMap& rayProps, const QHash& renderStates, QHash>& defaultRenderStates, + const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled) : + _renderingEnabled(enabled), + _renderStates(renderStates), + _defaultRenderStates(defaultRenderStates), + _faceAvatar(faceAvatar), + _centerEndY(centerEndY), + _lockEnd(lockEnd) +{ + _rayPickUID = DependencyManager::get()->createRayPick(rayProps); + + for (QString& state : _renderStates.keys()) { + if (!enabled || state != _currentRenderState) { + disableRenderState(_renderStates[state]); + } + } + for (QString& state : _defaultRenderStates.keys()) { + if (!enabled || state != _currentRenderState) { + disableRenderState(_defaultRenderStates[state].second); + } + } +} + +LaserPointer::~LaserPointer() { + DependencyManager::get()->removeRayPick(_rayPickUID); + + for (RenderState& renderState : _renderStates) { + renderState.deleteOverlays(); + } + for (QPair& renderState : _defaultRenderStates) { + renderState.second.deleteOverlays(); + } +} + +void LaserPointer::enable() { + DependencyManager::get()->enableRayPick(_rayPickUID); + _renderingEnabled = true; +} + +void LaserPointer::disable() { + DependencyManager::get()->disableRayPick(_rayPickUID); + _renderingEnabled = false; + if (!_currentRenderState.isEmpty()) { + if (_renderStates.contains(_currentRenderState)) { + disableRenderState(_renderStates[_currentRenderState]); + } + if (_defaultRenderStates.contains(_currentRenderState)) { + disableRenderState(_defaultRenderStates[_currentRenderState].second); + } + } +} + +void LaserPointer::setRenderState(const QString& state) { + if (!_currentRenderState.isEmpty() && state != _currentRenderState) { + if (_renderStates.contains(_currentRenderState)) { + disableRenderState(_renderStates[_currentRenderState]); + } + if (_defaultRenderStates.contains(_currentRenderState)) { + disableRenderState(_defaultRenderStates[_currentRenderState].second); + } + } + _currentRenderState = state; +} + +void LaserPointer::editRenderState(const QString& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { + updateRenderStateOverlay(_renderStates[state].getStartID(), startProps); + updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps); + updateRenderStateOverlay(_renderStates[state].getEndID(), endProps); +} + +void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { + if (!id.isNull() && props.isValid()) { + qApp->getOverlays().editOverlay(id, props); + } +} + +void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const bool defaultState) { + PickRay pickRay = DependencyManager::get()->getPickRay(_rayPickUID); + if (!renderState.getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("position", vec3toVariant(pickRay.origin)); + startProps.insert("visible", true); + startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays()); + qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); + } + glm::vec3 endVec; + if (((defaultState || !_lockEnd) && _objectLockEnd.first.isNull()) || type == IntersectionType::HUD) { + endVec = pickRay.origin + pickRay.direction * distance; + } else { + if (!_objectLockEnd.first.isNull()) { + glm::vec3 pos; + glm::quat rot; + glm::vec3 dim; + glm::vec3 registrationPoint; + if (_objectLockEnd.second) { + pos = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "position").value); + rot = quatFromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "rotation").value); + dim = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "dimensions").value); + registrationPoint = glm::vec3(0.5f); + } else { + EntityItemProperties props = DependencyManager::get()->getEntityProperties(_objectLockEnd.first); + pos = props.getPosition(); + rot = props.getRotation(); + dim = props.getDimensions(); + registrationPoint = props.getRegistrationPoint(); + } + const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); + endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); + } else { + if (type == IntersectionType::ENTITY) { + endVec = DependencyManager::get()->getEntityTransform(objectID)[3]; + } else if (type == IntersectionType::OVERLAY) { + endVec = vec3FromVariant(qApp->getOverlays().getProperty(objectID, "position").value); + } else if (type == IntersectionType::AVATAR) { + endVec = DependencyManager::get()->getAvatar(objectID)->getPosition(); + } + } + } + QVariant end = vec3toVariant(endVec); + if (!renderState.getPathID().isNull()) { + QVariantMap pathProps; + pathProps.insert("start", vec3toVariant(pickRay.origin)); + pathProps.insert("end", end); + pathProps.insert("visible", true); + pathProps.insert("ignoreRayIntersection", renderState.doesPathIgnoreRays()); + qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); + } + if (!renderState.getEndID().isNull()) { + QVariantMap endProps; + if (_centerEndY) { + endProps.insert("position", end); + } else { + glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value); + endProps.insert("position", vec3toVariant(endVec + glm::vec3(0, 0.5f * dim.y, 0))); + } + if (_faceAvatar) { + glm::quat rotation = glm::inverse(glm::quat_cast(glm::lookAt(endVec, DependencyManager::get()->getMyAvatar()->getPosition(), Vectors::UP))); + endProps.insert("rotation", quatToVariant(glm::quat(glm::radians(glm::vec3(0, glm::degrees(safeEulerAngles(rotation)).y, 0))))); + } + endProps.insert("visible", true); + endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays()); + qApp->getOverlays().editOverlay(renderState.getEndID(), endProps); + } +} + +void LaserPointer::disableRenderState(const RenderState& renderState) { + if (!renderState.getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("visible", false); + startProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); + } + if (!renderState.getPathID().isNull()) { + QVariantMap pathProps; + pathProps.insert("visible", false); + pathProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); + } + if (!renderState.getEndID().isNull()) { + QVariantMap endProps; + endProps.insert("visible", false); + endProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(renderState.getEndID(), endProps); + } +} + +void LaserPointer::update() { + RayPickResult prevRayPickResult = DependencyManager::get()->getPrevRayPickResult(_rayPickUID); + if (_renderingEnabled && !_currentRenderState.isEmpty() && _renderStates.contains(_currentRenderState) && prevRayPickResult.type != IntersectionType::NONE) { + updateRenderState(_renderStates[_currentRenderState], prevRayPickResult.type, prevRayPickResult.distance, prevRayPickResult.objectID, false); + disableRenderState(_defaultRenderStates[_currentRenderState].second); + } else if (_renderingEnabled && !_currentRenderState.isEmpty() && _defaultRenderStates.contains(_currentRenderState)) { + disableRenderState(_renderStates[_currentRenderState]); + updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), true); + } else if (!_currentRenderState.isEmpty()) { + disableRenderState(_renderStates[_currentRenderState]); + disableRenderState(_defaultRenderStates[_currentRenderState].second); + } +} + +RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : + _startID(startID), _pathID(pathID), _endID(endID) +{ + if (!_startID.isNull()) { + _startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool(); + } + if (!_pathID.isNull()) { + _pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool(); + } + if (!_endID.isNull()) { + _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool(); + } +} + +void RenderState::deleteOverlays() { + if (!_startID.isNull()) { + qApp->getOverlays().deleteOverlay(_startID); + } + if (!_pathID.isNull()) { + qApp->getOverlays().deleteOverlay(_pathID); + } + if (!_endID.isNull()) { + qApp->getOverlays().deleteOverlay(_endID); + } +} \ No newline at end of file diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h new file mode 100644 index 0000000000..946b01d3ba --- /dev/null +++ b/interface/src/raypick/LaserPointer.h @@ -0,0 +1,92 @@ +// +// LaserPointer.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// 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_LaserPointer_h +#define hifi_LaserPointer_h + +#include +#include "glm/glm.hpp" +#include "ui/overlays/Overlay.h" + +#include +#include "RayPickManager.h" + +class RayPickResult; + +class RenderState { + +public: + RenderState() {} + RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID); + + const OverlayID& getStartID() const { return _startID; } + const OverlayID& getPathID() const { return _pathID; } + const OverlayID& getEndID() const { return _endID; } + const bool& doesStartIgnoreRays() const { return _startIgnoreRays; } + const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; } + const bool& doesEndIgnoreRays() const { return _endIgnoreRays; } + + void deleteOverlays(); + +private: + OverlayID _startID; + OverlayID _pathID; + OverlayID _endID; + bool _startIgnoreRays; + bool _pathIgnoreRays; + bool _endIgnoreRays; +}; + + +class LaserPointer { + +public: + LaserPointer(const QVariantMap& rayProps, const QHash& renderStates, QHash>& defaultRenderStates, + const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled); + ~LaserPointer(); + + QUuid getRayUID() { return _rayPickUID; } + void enable(); + void disable(); + const RayPickResult getPrevRayPickResult() { return DependencyManager::get()->getPrevRayPickResult(_rayPickUID); } + + void setRenderState(const QString& state); + // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. + void editRenderState(const QString& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps); + + void setIgnoreEntities(const QScriptValue& ignoreEntities) { DependencyManager::get()->setIgnoreEntities(_rayPickUID, ignoreEntities); } + void setIncludeEntities(const QScriptValue& includeEntities) { DependencyManager::get()->setIncludeEntities(_rayPickUID, includeEntities); } + void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { DependencyManager::get()->setIgnoreOverlays(_rayPickUID, ignoreOverlays); } + void setIncludeOverlays(const QScriptValue& includeOverlays) { DependencyManager::get()->setIncludeOverlays(_rayPickUID, includeOverlays); } + void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { DependencyManager::get()->setIgnoreAvatars(_rayPickUID, ignoreAvatars); } + void setIncludeAvatars(const QScriptValue& includeAvatars) { DependencyManager::get()->setIncludeAvatars(_rayPickUID, includeAvatars); } + + void setLockEndUUID(QUuid objectID, const bool isOverlay) { _objectLockEnd = QPair(objectID, isOverlay); } + + void update(); + +private: + bool _renderingEnabled; + QString _currentRenderState { "" }; + QHash _renderStates; + QHash> _defaultRenderStates; + bool _faceAvatar; + bool _centerEndY; + bool _lockEnd; + QPair _objectLockEnd { QPair(QUuid(), false)}; + + QUuid _rayPickUID; + + void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); + void updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const bool defaultState); + void disableRenderState(const RenderState& renderState); +}; + +#endif // hifi_LaserPointer_h diff --git a/interface/src/raypick/LaserPointerManager.cpp b/interface/src/raypick/LaserPointerManager.cpp new file mode 100644 index 0000000000..33f292b255 --- /dev/null +++ b/interface/src/raypick/LaserPointerManager.cpp @@ -0,0 +1,153 @@ +// +// LaserPointerManager.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "LaserPointerManager.h" +#include "RayPick.h" + +QUuid LaserPointerManager::createLaserPointer(const QVariantMap& rayProps, const QHash& renderStates, QHash>& defaultRenderStates, + const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled) { + std::shared_ptr laserPointer = std::make_shared(rayProps, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, enabled); + if (!laserPointer->getRayUID().isNull()) { + QWriteLocker lock(&_addLock); + QUuid id = QUuid::createUuid(); + _laserPointersToAdd.enqueue(QPair>(id, laserPointer)); + return id; + } + return QUuid(); +} + +void LaserPointerManager::removeLaserPointer(const QUuid uid) { + QWriteLocker lock(&_removeLock); + _laserPointersToRemove.enqueue(uid); +} + +void LaserPointerManager::enableLaserPointer(const QUuid uid) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->enable(); + } +} + +void LaserPointerManager::disableLaserPointer(const QUuid uid) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->disable(); + } +} + +void LaserPointerManager::setRenderState(QUuid uid, const QString & renderState) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setRenderState(renderState); + } +} + +void LaserPointerManager::editRenderState(QUuid uid, const QString& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->editRenderState(state, startProps, pathProps, endProps); + } +} + +const RayPickResult LaserPointerManager::getPrevRayPickResult(const QUuid uid) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QReadLocker laserLock(_laserPointerLocks[uid].get()); + return _laserPointers[uid]->getPrevRayPickResult(); + } + return RayPickResult(); +} + +void LaserPointerManager::update() { + for (QUuid uid : _laserPointers.keys()) { + // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts + QReadLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->update(); + } + + QWriteLocker containsLock(&_containsLock); + { + QWriteLocker lock(&_addLock); + while (!_laserPointersToAdd.isEmpty()) { + QPair> laserPointerToAdd = _laserPointersToAdd.dequeue(); + _laserPointers[laserPointerToAdd.first] = laserPointerToAdd.second; + _laserPointerLocks[laserPointerToAdd.first] = std::make_shared(); + } + } + + { + QWriteLocker lock(&_removeLock); + while (!_laserPointersToRemove.isEmpty()) { + QUuid uid = _laserPointersToRemove.dequeue(); + _laserPointers.remove(uid); + _laserPointerLocks.remove(uid); + } + } +} + +void LaserPointerManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIgnoreEntities(ignoreEntities); + } +} + +void LaserPointerManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIncludeEntities(includeEntities); + } +} + +void LaserPointerManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIgnoreOverlays(ignoreOverlays); + } +} + +void LaserPointerManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIncludeOverlays(includeOverlays); + } +} + +void LaserPointerManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIgnoreAvatars(ignoreAvatars); + } +} + +void LaserPointerManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIncludeAvatars(includeAvatars); + } +} + +void LaserPointerManager::setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setLockEndUUID(objectID, isOverlay); + } +} diff --git a/interface/src/raypick/LaserPointerManager.h b/interface/src/raypick/LaserPointerManager.h new file mode 100644 index 0000000000..002b8ce38e --- /dev/null +++ b/interface/src/raypick/LaserPointerManager.h @@ -0,0 +1,57 @@ +// +// LaserPointerManager.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// 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_LaserPointerManager_h +#define hifi_LaserPointerManager_h + +#include +#include +#include +#include + +#include "LaserPointer.h" + +class RayPickResult; + +class LaserPointerManager { + +public: + QUuid createLaserPointer(const QVariantMap& rayProps, const QHash& renderStates, QHash>& defaultRenderStates, + const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled); + void removeLaserPointer(const QUuid uid); + void enableLaserPointer(const QUuid uid); + void disableLaserPointer(const QUuid uid); + void setRenderState(QUuid uid, const QString& renderState); + void editRenderState(QUuid uid, const QString& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps); + const RayPickResult getPrevRayPickResult(const QUuid uid); + + void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities); + void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities); + void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays); + void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays); + void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars); + void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars); + + void setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay); + + void update(); + +private: + QHash> _laserPointers; + QHash> _laserPointerLocks; + QReadWriteLock _addLock; + QQueue>> _laserPointersToAdd; + QReadWriteLock _removeLock; + QQueue _laserPointersToRemove; + QReadWriteLock _containsLock; + +}; + +#endif // hifi_LaserPointerManager_h diff --git a/interface/src/raypick/LaserPointerScriptingInterface.cpp b/interface/src/raypick/LaserPointerScriptingInterface.cpp new file mode 100644 index 0000000000..78a62d0ed8 --- /dev/null +++ b/interface/src/raypick/LaserPointerScriptingInterface.cpp @@ -0,0 +1,123 @@ +// +// LaserPointerScriptingInterface.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "LaserPointerScriptingInterface.h" + +#include +#include "RegisteredMetaTypes.h" +#include "GLMHelpers.h" + +#include "Application.h" + +QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& properties) { + QVariantMap propertyMap = properties.toMap(); + + bool faceAvatar = false; + if (propertyMap["faceAvatar"].isValid()) { + faceAvatar = propertyMap["faceAvatar"].toBool(); + } + + bool centerEndY = true; + if (propertyMap["centerEndY"].isValid()) { + centerEndY = propertyMap["centerEndY"].toBool(); + } + + bool lockEnd = false; + if (propertyMap["lockEnd"].isValid()) { + lockEnd = propertyMap["lockEnd"].toBool(); + } + + bool enabled = false; + if (propertyMap["enabled"].isValid()) { + enabled = propertyMap["enabled"].toBool(); + } + + QHash renderStates; + if (propertyMap["renderStates"].isValid()) { + QList renderStateVariants = propertyMap["renderStates"].toList(); + for (QVariant& renderStateVariant : renderStateVariants) { + if (renderStateVariant.isValid()) { + QVariantMap renderStateMap = renderStateVariant.toMap(); + if (renderStateMap["name"].isValid()) { + QString name = renderStateMap["name"].toString(); + renderStates[name] = buildRenderState(renderStateMap); + } + } + } + } + + QHash> defaultRenderStates; + if (propertyMap["defaultRenderStates"].isValid()) { + QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); + for (QVariant& renderStateVariant : renderStateVariants) { + if (renderStateVariant.isValid()) { + QVariantMap renderStateMap = renderStateVariant.toMap(); + if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { + QString name = renderStateMap["name"].toString(); + float distance = renderStateMap["distance"].toFloat(); + defaultRenderStates[name] = QPair(distance, buildRenderState(renderStateMap)); + } + } + } + } + + return qApp->getLaserPointerManager().createLaserPointer(propertyMap, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, enabled); +} + +void LaserPointerScriptingInterface::editRenderState(QUuid uid, const QString& renderState, const QVariant& properties) { + QVariantMap propMap = properties.toMap(); + + QVariant startProps; + if (propMap["start"].isValid()) { + startProps = propMap["start"]; + } + + QVariant pathProps; + if (propMap["path"].isValid()) { + pathProps = propMap["path"]; + } + + QVariant endProps; + if (propMap["end"].isValid()) { + endProps = propMap["end"]; + } + + qApp->getLaserPointerManager().editRenderState(uid, renderState, startProps, pathProps, endProps); +} + +const RenderState LaserPointerScriptingInterface::buildRenderState(const QVariantMap& propMap) { + QUuid startID; + if (propMap["start"].isValid()) { + QVariantMap startMap = propMap["start"].toMap(); + if (startMap["type"].isValid()) { + startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap); + } + } + + QUuid pathID; + if (propMap["path"].isValid()) { + QVariantMap pathMap = propMap["path"].toMap(); + // right now paths must be line3ds + if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") { + pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap); + } + } + + QUuid endID; + if (propMap["end"].isValid()) { + QVariantMap endMap = propMap["end"].toMap(); + if (endMap["type"].isValid()) { + endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap); + } + } + + return RenderState(startID, pathID, endID); +} \ No newline at end of file diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h new file mode 100644 index 0000000000..70d2e9a1fd --- /dev/null +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -0,0 +1,48 @@ +// +// LaserPointerScriptingInterface.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// 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_LaserPointerScriptingInterface_h +#define hifi_LaserPointerScriptingInterface_h + +#include + +#include "LaserPointerManager.h" +#include "RegisteredMetaTypes.h" +#include "DependencyManager.h" +#include "Application.h" + +class LaserPointerScriptingInterface : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public slots: + Q_INVOKABLE QUuid createLaserPointer(const QVariant& properties); + Q_INVOKABLE void enableLaserPointer(QUuid uid) { qApp->getLaserPointerManager().enableLaserPointer(uid); } + Q_INVOKABLE void disableLaserPointer(QUuid uid) { qApp->getLaserPointerManager().disableLaserPointer(uid); } + Q_INVOKABLE void removeLaserPointer(QUuid uid) { qApp->getLaserPointerManager().removeLaserPointer(uid); } + Q_INVOKABLE void editRenderState(QUuid uid, const QString& renderState, const QVariant& properties); + Q_INVOKABLE void setRenderState(QUuid uid, const QString& renderState) { qApp->getLaserPointerManager().setRenderState(uid, renderState); } + Q_INVOKABLE RayPickResult getPrevRayPickResult(QUuid uid) { return qApp->getLaserPointerManager().getPrevRayPickResult(uid); } + + Q_INVOKABLE void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { qApp->getLaserPointerManager().setIgnoreEntities(uid, ignoreEntities); } + Q_INVOKABLE void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { qApp->getLaserPointerManager().setIncludeEntities(uid, includeEntities); } + Q_INVOKABLE void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { qApp->getLaserPointerManager().setIgnoreOverlays(uid, ignoreOverlays); } + Q_INVOKABLE void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { qApp->getLaserPointerManager().setIncludeOverlays(uid, includeOverlays); } + Q_INVOKABLE void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { qApp->getLaserPointerManager().setIgnoreAvatars(uid, ignoreAvatars); } + Q_INVOKABLE void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { qApp->getLaserPointerManager().setIncludeAvatars(uid, includeAvatars); } + + Q_INVOKABLE void setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay) { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay); } + +private: + const RenderState buildRenderState(const QVariantMap & propMap); + +}; + +#endif // hifi_LaserPointerScriptingInterface_h diff --git a/interface/src/raypick/MouseRayPick.cpp b/interface/src/raypick/MouseRayPick.cpp new file mode 100644 index 0000000000..39dcb74090 --- /dev/null +++ b/interface/src/raypick/MouseRayPick.cpp @@ -0,0 +1,32 @@ +// +// MouseRayPick.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/19/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "MouseRayPick.h" + +#include "DependencyManager.h" +#include "Application.h" +#include "display-plugins/CompositorHelper.h" + +MouseRayPick::MouseRayPick(const uint16_t filter, const float maxDistance, const bool enabled) : + RayPick(filter, maxDistance, enabled) +{ +} + +const PickRay MouseRayPick::getPickRay(bool& valid) const { + QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition(); + if (position.isValid()) { + QVariantMap posMap = position.toMap(); + valid = true; + return qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat()); + } + + valid = false; + return PickRay(); +} diff --git a/interface/src/raypick/MouseRayPick.h b/interface/src/raypick/MouseRayPick.h new file mode 100644 index 0000000000..84d1c86e24 --- /dev/null +++ b/interface/src/raypick/MouseRayPick.h @@ -0,0 +1,24 @@ +// +// MouseRayPick.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/19/2017 +// Copyright 2017 High Fidelity, Inc. +// +// 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_MouseRayPick_h +#define hifi_MouseRayPick_h + +#include "RayPick.h" + +class MouseRayPick : public RayPick { + +public: + MouseRayPick(const uint16_t filter, const float maxDistance = 0.0f, const bool enabled = false); + + const PickRay getPickRay(bool& valid) const override; +}; + +#endif // hifi_MouseRayPick_h diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp new file mode 100644 index 0000000000..e807ef23ff --- /dev/null +++ b/interface/src/raypick/RayPick.cpp @@ -0,0 +1,18 @@ +// +// RayPick.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "RayPick.h" + +RayPick::RayPick(const uint16_t filter, const float maxDistance, const bool enabled) : + _filter(filter), + _maxDistance(maxDistance), + _enabled(enabled) +{ +} diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h new file mode 100644 index 0000000000..48841b9518 --- /dev/null +++ b/interface/src/raypick/RayPick.h @@ -0,0 +1,64 @@ +// +// RayPick.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// 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_RayPick_h +#define hifi_RayPick_h + +#include +#include "RegisteredMetaTypes.h" + +#include "EntityItemID.h" +#include "ui/overlays/Overlay.h" + +class RayPick { + +public: + RayPick(const uint16_t filter, const float maxDistance, const bool enabled); + + virtual const PickRay getPickRay(bool& valid) const = 0; + + void enable() { _enabled = true; } + void disable() { _enabled = false; } + + const uint16_t& getFilter() { return _filter; } + const float& getMaxDistance() { return _maxDistance; } + const bool& isEnabled() { return _enabled; } + const RayPickResult& getPrevRayPickResult() { return _prevResult; } + + void setRayPickResult(const RayPickResult& rayPickResult) { _prevResult = rayPickResult; } + + const QVector& getIgnoreEntites() { return _ignoreEntities; } + const QVector& getIncludeEntites() { return _includeEntities; } + const QVector& getIgnoreOverlays() { return _ignoreOverlays; } + const QVector& getIncludeOverlays() { return _includeOverlays; } + const QVector& getIgnoreAvatars() { return _ignoreAvatars; } + const QVector& getIncludeAvatars() { return _includeAvatars; } + void setIgnoreEntities(const QScriptValue& ignoreEntities) { _ignoreEntities = qVectorEntityItemIDFromScriptValue(ignoreEntities); } + void setIncludeEntities(const QScriptValue& includeEntities) { _includeEntities = qVectorEntityItemIDFromScriptValue(includeEntities); } + void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { _ignoreOverlays = qVectorOverlayIDFromScriptValue(ignoreOverlays); } + void setIncludeOverlays(const QScriptValue& includeOverlays) { _includeOverlays = qVectorOverlayIDFromScriptValue(includeOverlays); } + void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { _ignoreAvatars = qVectorEntityItemIDFromScriptValue(ignoreAvatars); } + void setIncludeAvatars(const QScriptValue& includeAvatars) { _includeAvatars = qVectorEntityItemIDFromScriptValue(includeAvatars); } + +private: + uint16_t _filter; + float _maxDistance; + bool _enabled; + RayPickResult _prevResult; + + QVector _ignoreEntities; + QVector _includeEntities; + QVector _ignoreOverlays; + QVector _includeOverlays; + QVector _ignoreAvatars; + QVector _includeAvatars; +}; + +#endif // hifi_RayPick_h diff --git a/interface/src/raypick/RayPickManager.cpp b/interface/src/raypick/RayPickManager.cpp new file mode 100644 index 0000000000..f7f22a66bb --- /dev/null +++ b/interface/src/raypick/RayPickManager.cpp @@ -0,0 +1,288 @@ +// +// RayPickManager.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "RayPickManager.h" + +#include "RayPick.h" + +#include "Application.h" +#include "EntityScriptingInterface.h" +#include "ui/overlays/Overlays.h" +#include "avatar/AvatarManager.h" +#include "scripting/HMDScriptingInterface.h" +#include "DependencyManager.h" + +#include "JointRayPick.h" +#include "StaticRayPick.h" +#include "MouseRayPick.h" + +bool RayPickManager::checkAndCompareCachedResults(QPair& ray, RayPickCache& cache, RayPickResult& res, unsigned int mask) { + if (cache.contains(ray) && cache[ray].contains(mask)) { + if (cache[ray][mask].distance < res.distance) { + res = cache[ray][mask]; + } + return true; + } + return false; +} + +void RayPickManager::cacheResult(const bool intersects, const RayPickResult& resTemp, unsigned int mask, RayPickResult& res, QPair& ray, RayPickCache& cache) { + if (intersects) { + cache[ray][mask] = resTemp; + if (resTemp.distance < res.distance) { + res = resTemp; + } + } else { + cache[ray][mask] = RayPickResult(); + } +} + +void RayPickManager::update() { + RayPickCache results; + for (auto& uid : _rayPicks.keys()) { + std::shared_ptr rayPick = _rayPicks[uid]; + if (!rayPick->isEnabled() || rayPick->getFilter() == RayPickMask::PICK_NOTHING || rayPick->getMaxDistance() < 0.0f) { + continue; + } + + bool valid; + PickRay ray = rayPick->getPickRay(valid); + + if (!valid) { + continue; + } + + QPair rayKey = QPair(ray.origin, ray.direction); + RayPickResult res; + + if (rayPick->getFilter() & RayPickMask::PICK_ENTITIES) { + RayToEntityIntersectionResult entityRes; + bool fromCache = true; + unsigned int invisible = rayPick->getFilter() & RayPickMask::PICK_INCLUDE_INVISIBLE; + unsigned int noncollidable = rayPick->getFilter() & RayPickMask::PICK_INCLUDE_NONCOLLIDABLE; + unsigned int entityMask = RayPickMask::PICK_ENTITIES | invisible | noncollidable; + if (!checkAndCompareCachedResults(rayKey, results, res, entityMask)) { + entityRes = DependencyManager::get()->findRayIntersection(ray, true, rayPick->getIncludeEntites(), rayPick->getIgnoreEntites(), !invisible, !noncollidable); + fromCache = false; + } + + if (!fromCache) { + cacheResult(entityRes.intersects, RayPickResult(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, entityRes.surfaceNormal), + entityMask, res, rayKey, results); + } + } + + if (rayPick->getFilter() & RayPickMask::PICK_OVERLAYS) { + RayToOverlayIntersectionResult overlayRes; + bool fromCache = true; + unsigned int invisible = rayPick->getFilter() & RayPickMask::PICK_INCLUDE_INVISIBLE; + unsigned int noncollidable = rayPick->getFilter() & RayPickMask::PICK_INCLUDE_NONCOLLIDABLE; + unsigned int overlayMask = RayPickMask::PICK_OVERLAYS | invisible | noncollidable; + if (!checkAndCompareCachedResults(rayKey, results, res, overlayMask)) { + overlayRes = qApp->getOverlays().findRayIntersection(ray, true, rayPick->getIncludeOverlays(), rayPick->getIgnoreOverlays(), !invisible, !noncollidable); + fromCache = false; + } + + if (!fromCache) { + cacheResult(overlayRes.intersects, RayPickResult(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, overlayRes.surfaceNormal), + overlayMask, res, rayKey, results); + } + } + + if (rayPick->getFilter() & RayPickMask::PICK_AVATARS) { + if (!checkAndCompareCachedResults(rayKey, results, res, RayPickMask::PICK_AVATARS)) { + RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersection(ray, rayPick->getIncludeAvatars(), rayPick->getIgnoreAvatars()); + cacheResult(avatarRes.intersects, RayPickResult(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection), RayPickMask::PICK_AVATARS, res, rayKey, results); + } + } + + // Can't intersect with HUD in desktop mode + if (rayPick->getFilter() & RayPickMask::PICK_HUD && DependencyManager::get()->isHMDMode()) { + if (!checkAndCompareCachedResults(rayKey, results, res, RayPickMask::PICK_HUD)) { + glm::vec3 hudRes = DependencyManager::get()->calculateRayUICollisionPoint(ray.origin, ray.direction); + cacheResult(true, RayPickResult(IntersectionType::HUD, 0, glm::distance(ray.origin, hudRes), hudRes), RayPickMask::PICK_HUD, res, rayKey, results); + } + } + + QWriteLocker lock(_rayPickLocks[uid].get()); + if (rayPick->getMaxDistance() == 0.0f || (rayPick->getMaxDistance() > 0.0f && res.distance < rayPick->getMaxDistance())) { + rayPick->setRayPickResult(res); + } else { + rayPick->setRayPickResult(RayPickResult()); + } + } + + QWriteLocker containsLock(&_containsLock); + { + QWriteLocker lock(&_addLock); + while (!_rayPicksToAdd.isEmpty()) { + QPair> rayPickToAdd = _rayPicksToAdd.dequeue(); + _rayPicks[rayPickToAdd.first] = rayPickToAdd.second; + _rayPickLocks[rayPickToAdd.first] = std::make_shared(); + } + } + + { + QWriteLocker lock(&_removeLock); + while (!_rayPicksToRemove.isEmpty()) { + QUuid uid = _rayPicksToRemove.dequeue(); + _rayPicks.remove(uid); + _rayPickLocks.remove(uid); + } + } +} + +QUuid RayPickManager::createRayPick(const QVariantMap& rayProps) { + bool enabled = false; + if (rayProps["enabled"].isValid()) { + enabled = rayProps["enabled"].toBool(); + } + + uint16_t filter = 0; + if (rayProps["filter"].isValid()) { + filter = rayProps["filter"].toUInt(); + } + + float maxDistance = 0.0f; + if (rayProps["maxDistance"].isValid()) { + maxDistance = rayProps["maxDistance"].toFloat(); + } + + if (rayProps["joint"].isValid()) { + QString jointName = rayProps["joint"].toString(); + + if (jointName != "Mouse") { + // x = upward, y = forward, z = lateral + glm::vec3 posOffset = Vectors::ZERO; + if (rayProps["posOffset"].isValid()) { + posOffset = vec3FromVariant(rayProps["posOffset"]); + } + + glm::vec3 dirOffset = Vectors::UP; + if (rayProps["dirOffset"].isValid()) { + dirOffset = vec3FromVariant(rayProps["dirOffset"]); + } + + QWriteLocker lock(&_addLock); + QUuid id = QUuid::createUuid(); + _rayPicksToAdd.enqueue(QPair>(id, std::make_shared(jointName, posOffset, dirOffset, filter, maxDistance, enabled))); + return id; + } else { + QWriteLocker lock(&_addLock); + QUuid id = QUuid::createUuid(); + _rayPicksToAdd.enqueue(QPair>(id, std::make_shared(filter, maxDistance, enabled))); + return id; + } + } else if (rayProps["position"].isValid()) { + glm::vec3 position = vec3FromVariant(rayProps["position"]); + + glm::vec3 direction = -Vectors::UP; + if (rayProps["direction"].isValid()) { + direction = vec3FromVariant(rayProps["direction"]); + } + + QWriteLocker lock(&_addLock); + QUuid id = QUuid::createUuid(); + _rayPicksToAdd.enqueue(QPair>(id, std::make_shared(position, direction, filter, maxDistance, enabled))); + return id; + } + + return QUuid(); +} + +void RayPickManager::removeRayPick(const QUuid uid) { + QWriteLocker lock(&_removeLock); + _rayPicksToRemove.enqueue(uid); +} + +void RayPickManager::enableRayPick(const QUuid uid) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker rayPickLock(_rayPickLocks[uid].get()); + _rayPicks[uid]->enable(); + } +} + +void RayPickManager::disableRayPick(const QUuid uid) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker rayPickLock(_rayPickLocks[uid].get()); + _rayPicks[uid]->disable(); + } +} + +const PickRay RayPickManager::getPickRay(const QUuid uid) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + bool valid; + PickRay pickRay = _rayPicks[uid]->getPickRay(valid); + if (valid) { + return pickRay; + } + } + return PickRay(); +} + +const RayPickResult RayPickManager::getPrevRayPickResult(const QUuid uid) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QReadLocker lock(_rayPickLocks[uid].get()); + return _rayPicks[uid]->getPrevRayPickResult(); + } + return RayPickResult(); +} + +void RayPickManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIgnoreEntities(ignoreEntities); + } +} + +void RayPickManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIncludeEntities(includeEntities); + } +} + +void RayPickManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIgnoreOverlays(ignoreOverlays); + } +} + +void RayPickManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIncludeOverlays(includeOverlays); + } +} + +void RayPickManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIgnoreAvatars(ignoreAvatars); + } +} + +void RayPickManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIncludeAvatars(includeAvatars); + } +} \ No newline at end of file diff --git a/interface/src/raypick/RayPickManager.h b/interface/src/raypick/RayPickManager.h new file mode 100644 index 0000000000..592541e7cf --- /dev/null +++ b/interface/src/raypick/RayPickManager.h @@ -0,0 +1,111 @@ +// +// RayPickManager.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// 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_RayPickManager_h +#define hifi_RayPickManager_h + +#include +#include +#include +#include +#include + +#include "RegisteredMetaTypes.h" +#include "DependencyManager.h" + +class RayPick; +class RayPickResult; + +enum RayPickMask { + PICK_NOTHING = 0, + PICK_ENTITIES = 1 << 0, + PICK_OVERLAYS = 1 << 1, + PICK_AVATARS = 1 << 2, + PICK_HUD = 1 << 3, + + // NOT YET IMPLEMENTED + PICK_BOUNDING_BOX = 1 << 4, // if not set, picks again physics mesh (can't pick against graphics mesh, yet) + + PICK_INCLUDE_INVISIBLE = 1 << 5, // if not set, will not intersect invisible elements, otherwise, intersects both visible and invisible elements + PICK_INCLUDE_NONCOLLIDABLE = 1 << 6, // if not set, will not intersect noncollidable elements, otherwise, intersects both collidable and noncollidable elements + + // NOT YET IMPLEMENTED + PICK_ALL_INTERSECTIONS = 1 << 7 // if not set, returns closest intersection, otherwise, returns list of all intersections +}; + +class RayPickManager : public QObject, public Dependency { + Q_OBJECT + Q_PROPERTY(unsigned int PICK_NOTHING READ PICK_NOTHING CONSTANT) + Q_PROPERTY(unsigned int PICK_ENTITIES READ PICK_ENTITIES CONSTANT) + Q_PROPERTY(unsigned int PICK_OVERLAYS READ PICK_OVERLAYS CONSTANT) + Q_PROPERTY(unsigned int PICK_AVATARS READ PICK_AVATARS CONSTANT) + Q_PROPERTY(unsigned int PICK_HUD READ PICK_HUD CONSTANT) + Q_PROPERTY(unsigned int PICK_BOUNDING_BOX READ PICK_BOUNDING_BOX CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ PICK_INCLUDE_INVISIBLE CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ PICK_INCLUDE_NONCOLLIDABLE CONSTANT) + Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ PICK_ALL_INTERSECTIONS CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ INTERSECTED_OVERLAY CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ INTERSECTED_AVATAR CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT) + SINGLETON_DEPENDENCY + +public: + void update(); + const PickRay getPickRay(const QUuid uid); + +public slots: + Q_INVOKABLE QUuid createRayPick(const QVariantMap& rayProps); + Q_INVOKABLE void removeRayPick(const QUuid uid); + Q_INVOKABLE void enableRayPick(const QUuid uid); + Q_INVOKABLE void disableRayPick(const QUuid uid); + Q_INVOKABLE const RayPickResult getPrevRayPickResult(const QUuid uid); + + Q_INVOKABLE void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities); + Q_INVOKABLE void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities); + Q_INVOKABLE void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays); + Q_INVOKABLE void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays); + Q_INVOKABLE void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars); + Q_INVOKABLE void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars); + +private: + QHash> _rayPicks; + QHash> _rayPickLocks; + QReadWriteLock _addLock; + QQueue>> _rayPicksToAdd; + QReadWriteLock _removeLock; + QQueue _rayPicksToRemove; + QReadWriteLock _containsLock; + + typedef QHash, QHash> RayPickCache; + + // Returns true if this ray exists in the cache, and if it does, update res if the cached result is closer + bool checkAndCompareCachedResults(QPair& ray, RayPickCache& cache, RayPickResult& res, unsigned int mask); + void cacheResult(const bool intersects, const RayPickResult& resTemp, unsigned int mask, RayPickResult& res, QPair& ray, RayPickCache& cache); + + unsigned int PICK_NOTHING() { return RayPickMask::PICK_NOTHING; } + unsigned int PICK_ENTITIES() { return RayPickMask::PICK_ENTITIES; } + unsigned int PICK_OVERLAYS() { return RayPickMask::PICK_OVERLAYS; } + unsigned int PICK_AVATARS() { return RayPickMask::PICK_AVATARS; } + unsigned int PICK_HUD() { return RayPickMask::PICK_HUD; } + unsigned int PICK_BOUNDING_BOX() { return RayPickMask::PICK_BOUNDING_BOX; } + unsigned int PICK_INCLUDE_INVISIBLE() { return RayPickMask::PICK_INCLUDE_INVISIBLE; } + unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return RayPickMask::PICK_INCLUDE_NONCOLLIDABLE; } + unsigned int PICK_ALL_INTERSECTIONS() { return RayPickMask::PICK_ALL_INTERSECTIONS; } + unsigned int INTERSECTED_NONE() { return IntersectionType::NONE; } + unsigned int INTERSECTED_ENTITY() { return IntersectionType::ENTITY; } + unsigned int INTERSECTED_OVERLAY() { return IntersectionType::OVERLAY; } + unsigned int INTERSECTED_AVATAR() { return IntersectionType::AVATAR; } + unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; } + +}; + +#endif // hifi_RayPickManager_h \ No newline at end of file diff --git a/interface/src/raypick/StaticRayPick.cpp b/interface/src/raypick/StaticRayPick.cpp new file mode 100644 index 0000000000..43088150ad --- /dev/null +++ b/interface/src/raypick/StaticRayPick.cpp @@ -0,0 +1,22 @@ +// +// StaticRayPick.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "StaticRayPick.h" + +StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const uint16_t filter, const float maxDistance, const bool enabled) : + RayPick(filter, maxDistance, enabled), + _pickRay(position, direction) +{ +} + +const PickRay StaticRayPick::getPickRay(bool& valid) const { + valid = true; + return _pickRay; +} \ No newline at end of file diff --git a/interface/src/raypick/StaticRayPick.h b/interface/src/raypick/StaticRayPick.h new file mode 100644 index 0000000000..05ff4ef397 --- /dev/null +++ b/interface/src/raypick/StaticRayPick.h @@ -0,0 +1,28 @@ +// +// StaticRayPick.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// Copyright 2017 High Fidelity, Inc. +// +// 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_StaticRayPick_h +#define hifi_StaticRayPick_h + +#include "RayPick.h" + +class StaticRayPick : public RayPick { + +public: + StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const uint16_t filter, const float maxDistance = 0.0f, const bool enabled = false); + + const PickRay getPickRay(bool& valid) const override; + +private: + PickRay _pickRay; + +}; + +#endif // hifi_StaticRayPick_h diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 35f2e2aa86..93c3a7652e 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -82,6 +82,22 @@ bool HMDScriptingInterface::shouldShowHandControllers() const { return _showHandControllersCount > 0; } +void HMDScriptingInterface::activateHMDHandMouse() { + QWriteLocker lock(&_hmdHandMouseLock); + auto offscreenUi = DependencyManager::get(); + offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", true); + _hmdHandMouseCount++; +} + +void HMDScriptingInterface::deactivateHMDHandMouse() { + QWriteLocker lock(&_hmdHandMouseLock); + _hmdHandMouseCount = std::max(_hmdHandMouseCount - 1, 0); + if (_hmdHandMouseCount == 0) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", false); + } +} + void HMDScriptingInterface::closeTablet() { _showTablet = false; } @@ -153,50 +169,6 @@ QString HMDScriptingInterface::preferredAudioOutput() const { return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); } -bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) { - if (QThread::currentThread() != thread()) { - bool result; - BLOCKING_INVOKE_METHOD(this, "setHandLasers", Q_RETURN_ARG(bool, result), - Q_ARG(int, hands), Q_ARG(bool, enabled), Q_ARG(glm::vec4, color), Q_ARG(glm::vec3, direction)); - return result; - } - - auto offscreenUi = DependencyManager::get(); - offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled); - return qApp->getActiveDisplayPlugin()->setHandLaser(hands, - enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None, - color, direction); -} - -bool HMDScriptingInterface::setExtraLaser(const glm::vec3& worldStart, bool enabled, const glm::vec4& color, const glm::vec3& direction) { - if (QThread::currentThread() != thread()) { - bool result; - BLOCKING_INVOKE_METHOD(this, "setExtraLaser", Q_RETURN_ARG(bool, result), - Q_ARG(glm::vec3, worldStart), Q_ARG(bool, enabled), Q_ARG(glm::vec4, color), Q_ARG(glm::vec3, direction)); - return result; - } - - auto offscreenUi = DependencyManager::get(); - offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled); - - auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto sensorToWorld = myAvatar->getSensorToWorldMatrix(); - auto worldToSensor = glm::inverse(sensorToWorld); - auto sensorStart = ::transformPoint(worldToSensor, worldStart); - auto sensorDirection = ::transformVectorFast(worldToSensor, direction); - - return qApp->getActiveDisplayPlugin()->setExtraLaser(enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None, - color, sensorStart, sensorDirection); -} - -void HMDScriptingInterface::disableExtraLaser() { - setExtraLaser(vec3(0), false, vec4(0), vec3(0)); -} - -void HMDScriptingInterface::disableHandLasers(int hands) { - setHandLasers(hands, false, vec4(0), vec3(0)); -} - bool HMDScriptingInterface::suppressKeyboard() { return qApp->getActiveDisplayPlugin()->suppressKeyboard(); } diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 3ed7db0232..2eefe6ea22 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -22,6 +22,7 @@ class QScriptEngine; #include #include +#include class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency { Q_OBJECT @@ -51,12 +52,8 @@ public: Q_INVOKABLE void requestHideHandControllers(); Q_INVOKABLE bool shouldShowHandControllers() const; - Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction); - Q_INVOKABLE void disableHandLasers(int hands); - - Q_INVOKABLE bool setExtraLaser(const glm::vec3& worldStart, bool enabled, const glm::vec4& color, const glm::vec3& direction); - Q_INVOKABLE void disableExtraLaser(); - + Q_INVOKABLE void activateHMDHandMouse(); + Q_INVOKABLE void deactivateHMDHandMouse(); /// Suppress the activation of any on-screen keyboard so that a script operation will /// not be interrupted by a keyboard popup @@ -119,6 +116,9 @@ private: bool getHUDLookAtPosition3D(glm::vec3& result) const; glm::mat4 getWorldHMDMatrix() const; std::atomic _showHandControllersCount { 0 }; + + QReadWriteLock _hmdHandMouseLock; + int _hmdHandMouseCount; }; #endif // hifi_HMDScriptingInterface_h diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 827417a912..52a3d7a929 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -267,7 +267,7 @@ const render::ShapeKey Circle3DOverlay::getShapeKey() { if (getAlpha() != 1.0f) { builder.withTranslucent(); } - if (!getIsSolid()) { + if (!getIsSolid() || shouldDrawHUDLayer()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index a353545245..31cbe5e822 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -120,7 +120,7 @@ const render::ShapeKey Cube3DOverlay::getShapeKey() { if (getAlpha() != 1.0f) { builder.withTranslucent(); } - if (!getIsSolid()) { + if (!getIsSolid() || shouldDrawHUDLayer()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 7dfee2c491..82417db83a 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -123,7 +123,7 @@ void Image3DOverlay::render(RenderArgs* args) { const render::ShapeKey Image3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias(); - if (_emissive) { + if (_emissive || shouldDrawHUDLayer()) { builder.withUnlit(); } if (getAlpha() != 1.0f) { diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 675dff7e93..8b88d1e963 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -13,6 +13,8 @@ #include #include +#include "Application.h" + static const xColor DEFAULT_OVERLAY_COLOR = { 255, 255, 255 }; static const float DEFAULT_ALPHA = 0.7f; @@ -30,6 +32,7 @@ Overlay::Overlay() : _colorPulse(0.0f), _color(DEFAULT_OVERLAY_COLOR), _visible(true), + _drawHUDLayer(false), _anchor(NO_ANCHOR) { } @@ -48,6 +51,7 @@ Overlay::Overlay(const Overlay* overlay) : _colorPulse(overlay->_colorPulse), _color(overlay->_color), _visible(overlay->_visible), + _drawHUDLayer(overlay->_drawHUDLayer), _anchor(overlay->_anchor) { } @@ -86,6 +90,11 @@ void Overlay::setProperties(const QVariantMap& properties) { setColorPulse(properties["colorPulse"].toFloat()); } + if (properties["drawHUDLayer"].isValid()) { + bool drawHUDLayer = properties["drawHUDLayer"].toBool(); + setDrawHUDLayer(drawHUDLayer); + } + if (properties["visible"].isValid()) { bool visible = properties["visible"].toBool(); setVisible(visible); @@ -161,6 +170,12 @@ float Overlay::getAlpha() { return (_alphaPulse >= 0.0f) ? _alpha * pulseLevel : _alpha * (1.0f - pulseLevel); } +void Overlay::setDrawHUDLayer(bool drawHUDLayer) { + if (drawHUDLayer != _drawHUDLayer) { + qApp->getOverlays().setOverlayDrawHUDLayer(getOverlayID(), drawHUDLayer); + _drawHUDLayer = drawHUDLayer; + } +} // pulse travels from min to max, then max to min in one period. float Overlay::updatePulse() { diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 494c287676..a9774eea06 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -58,6 +58,7 @@ public: virtual bool is3D() const = 0; bool isLoaded() { return _isLoaded; } bool getVisible() const { return _visible; } + bool shouldDrawHUDLayer() const { return _drawHUDLayer; } xColor getColor(); float getAlpha(); Anchor getAnchor() const { return _anchor; } @@ -72,6 +73,7 @@ public: // setters void setVisible(bool visible) { _visible = visible; } + void setDrawHUDLayer(bool drawHUDLayer); void setColor(const xColor& color) { _color = color; } void setAlpha(float alpha) { _alpha = alpha; } void setAnchor(Anchor anchor) { _anchor = anchor; } @@ -114,6 +116,7 @@ protected: xColor _color; bool _visible; // should the overlay be drawn at all + bool _drawHUDLayer; // should the overlay be drawn on the HUD layer Anchor _anchor; unsigned int _stackOrder { 0 }; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index b6c7dcbd46..04ee205ba6 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -37,20 +37,29 @@ #include "Web3DOverlay.h" #include +#include "render/ShapePipeline.h" + Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") +extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); + void Overlays::cleanupAllOverlays() { QMap overlaysHUD; + QMap overlays3DHUD; QMap overlaysWorld; { QMutexLocker locker(&_mutex); overlaysHUD.swap(_overlaysHUD); + overlays3DHUD.swap(_overlays3DHUD); overlaysWorld.swap(_overlaysWorld); } foreach(Overlay::Pointer overlay, overlaysHUD) { _overlaysToDelete.push_back(overlay); } + foreach(Overlay::Pointer overlay, overlays3DHUD) { + _overlaysToDelete.push_back(overlay); + } foreach(Overlay::Pointer overlay, overlaysWorld) { _overlaysToDelete.push_back(overlay); } @@ -64,6 +73,8 @@ void Overlays::init() { #if OVERLAY_PANELS _scriptEngine = new QScriptEngine(); #endif + _shapePlumber = std::make_shared(); + initOverlay3DPipelines(*_shapePlumber, true); } void Overlays::update(float deltatime) { @@ -72,6 +83,9 @@ void Overlays::update(float deltatime) { foreach(const auto& thisOverlay, _overlaysHUD) { thisOverlay->update(deltatime); } + foreach(const auto& thisOverlay, _overlays3DHUD) { + thisOverlay->update(deltatime); + } foreach(const auto& thisOverlay, _overlaysWorld) { thisOverlay->update(deltatime); } @@ -128,6 +142,23 @@ void Overlays::renderHUD(RenderArgs* renderArgs) { } } +void Overlays::render3DHUDOverlays(RenderArgs* renderArgs) { + PROFILE_RANGE(render_overlays, __FUNCTION__); + gpu::Batch& batch = *renderArgs->_batch; + + auto textureCache = DependencyManager::get(); + + QMutexLocker lock(&_mutex); + foreach(Overlay::Pointer thisOverlay, _overlays3DHUD) { + // Reset necessary batch pipeline settings between overlays + batch.setResourceTexture(0, textureCache->getWhiteTexture()); // FIXME - do we really need to do this?? + batch.setModelTransform(Transform()); + + renderArgs->_shapePipeline = _shapePlumber->pickPipeline(renderArgs, thisOverlay->getShapeKey()); + thisOverlay->render(renderArgs); + } +} + void Overlays::disable() { _enabled = false; } @@ -142,8 +173,9 @@ Overlay::Pointer Overlays::getOverlay(OverlayID id) const { QMutexLocker locker(&_mutex); if (_overlaysHUD.contains(id)) { return _overlaysHUD[id]; - } - if (_overlaysWorld.contains(id)) { + } else if (_overlays3DHUD.contains(id)) { + return _overlays3DHUD[id]; + } else if (_overlaysWorld.contains(id)) { return _overlaysWorld[id]; } return nullptr; @@ -200,7 +232,7 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { OverlayID thisID = OverlayID(QUuid::createUuid()); overlay->setOverlayID(thisID); overlay->setStackOrder(_stackOrder++); - if (overlay->is3D()) { + if (overlay->is3D() && !overlay->shouldDrawHUDLayer()) { { QMutexLocker locker(&_mutex); _overlaysWorld[thisID] = overlay; @@ -210,6 +242,9 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { render::Transaction transaction; overlay->addToScene(overlay, scene, transaction); scene->enqueueTransaction(transaction); + } else if (overlay->is3D() && overlay->shouldDrawHUDLayer()) { + QMutexLocker locker(&_mutex); + _overlays3DHUD[thisID] = overlay; } else { QMutexLocker locker(&_mutex); _overlaysHUD[thisID] = overlay; @@ -218,6 +253,28 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { return thisID; } +void Overlays::setOverlayDrawHUDLayer(const OverlayID& id, const bool drawHUDLayer) { + QMutexLocker locker(&_mutex); + if (drawHUDLayer && _overlaysWorld.contains(id)) { + std::shared_ptr overlay = _overlaysWorld.take(id); + render::ScenePointer scene = qApp->getMain3DScene(); + render::Transaction transaction; + auto itemID = overlay->getRenderItemID(); + if (render::Item::isValidID(itemID)) { + overlay->removeFromScene(overlay, scene, transaction); + scene->enqueueTransaction(transaction); + } + _overlays3DHUD[id] = overlay; + } else if (!drawHUDLayer && _overlays3DHUD.contains(id)) { + std::shared_ptr overlay = _overlays3DHUD.take(id); + render::ScenePointer scene = qApp->getMain3DScene(); + render::Transaction transaction; + overlay->addToScene(overlay, scene, transaction); + scene->enqueueTransaction(transaction); + _overlaysWorld[id] = overlay; + } +} + OverlayID Overlays::cloneOverlay(OverlayID id) { if (QThread::currentThread() != thread()) { OverlayID result; @@ -294,6 +351,8 @@ void Overlays::deleteOverlay(OverlayID id) { QMutexLocker locker(&_mutex); if (_overlaysHUD.contains(id)) { overlayToDelete = _overlaysHUD.take(id); + } else if (_overlays3DHUD.contains(id)) { + overlayToDelete = _overlays3DHUD.take(id); } else if (_overlaysWorld.contains(id)) { overlayToDelete = _overlaysWorld.take(id); } else { @@ -475,15 +534,15 @@ RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray, const QVector overlaysToInclude = qVectorOverlayIDFromScriptValue(overlayIDsToInclude); const QVector overlaysToDiscard = qVectorOverlayIDFromScriptValue(overlayIDsToDiscard); - return findRayIntersectionInternal(ray, precisionPicking, - overlaysToInclude, overlaysToDiscard, visibleOnly, collidableOnly); + return findRayIntersection(ray, precisionPicking, + overlaysToInclude, overlaysToDiscard, visibleOnly, collidableOnly); } -RayToOverlayIntersectionResult Overlays::findRayIntersectionInternal(const PickRay& ray, bool precisionPicking, - const QVector& overlaysToInclude, - const QVector& overlaysToDiscard, - bool visibleOnly, bool collidableOnly) { +RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly, bool collidableOnly) { float bestDistance = std::numeric_limits::max(); bool bestIsFront = false; @@ -702,7 +761,7 @@ bool Overlays::isAddedOverlay(OverlayID id) { } QMutexLocker locker(&_mutex); - return _overlaysHUD.contains(id) || _overlaysWorld.contains(id); + return _overlaysHUD.contains(id) || _overlays3DHUD.contains(id) || _overlaysWorld.contains(id); } void Overlays::sendMousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { @@ -849,21 +908,21 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionForMouseEvent(PickRa // first priority is tablet screen overlaysToInclude << qApp->getTabletScreenID(); - rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + rayPickResult = findRayIntersection(ray, true, overlaysToInclude, overlaysToDiscard); if (rayPickResult.intersects) { return rayPickResult; } // then tablet home button overlaysToInclude.clear(); overlaysToInclude << qApp->getTabletHomeButtonID(); - rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + rayPickResult = findRayIntersection(ray, true, overlaysToInclude, overlaysToDiscard); if (rayPickResult.intersects) { return rayPickResult; } // then tablet frame overlaysToInclude.clear(); overlaysToInclude << OverlayID(qApp->getTabletFrameID()); - rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + rayPickResult = findRayIntersection(ray, true, overlaysToInclude, overlaysToDiscard); if (rayPickResult.intersects) { return rayPickResult; } diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index a915acb06a..9db6375e84 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -90,6 +90,7 @@ public: void init(); void update(float deltatime); void renderHUD(RenderArgs* renderArgs); + void render3DHUDOverlays(RenderArgs* renderArgs); void disable(); void enable(); @@ -102,6 +103,8 @@ public: OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); } OverlayID addOverlay(const Overlay::Pointer& overlay); + void setOverlayDrawHUDLayer(const OverlayID& id, const bool drawHUDLayer); + bool mousePressEvent(QMouseEvent* event); bool mouseDoublePressEvent(QMouseEvent* event); bool mouseReleaseEvent(QMouseEvent* event); @@ -213,6 +216,12 @@ public slots: bool visibleOnly = false, bool collidableOnly = false); + // Same as above but with QVectors + RayToOverlayIntersectionResult findRayIntersection(const PickRay& ray, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly = false, bool collidableOnly = false); + /**jsdoc * Return a list of 3d overlays with bounding boxes that touch the given sphere * @@ -325,7 +334,11 @@ private: mutable QMutex _mutex { QMutex::Recursive }; QMap _overlaysHUD; + QMap _overlays3DHUD; QMap _overlaysWorld; + + render::ShapePlumberPointer _shapePlumber; + #if OVERLAY_PANELS QMap _panels; #endif @@ -343,10 +356,6 @@ private: OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID }; OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; - Q_INVOKABLE RayToOverlayIntersectionResult findRayIntersectionInternal(const PickRay& ray, bool precisionPicking, - const QVector& overlaysToInclude, - const QVector& overlaysToDiscard, - bool visibleOnly = false, bool collidableOnly = false); RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray); }; diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index a6fcacc769..7126f7bde4 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -65,7 +65,7 @@ const render::ShapeKey Shape3DOverlay::getShapeKey() { if (getAlpha() != 1.0f) { builder.withTranslucent(); } - if (!getIsSolid()) { + if (!getIsSolid() || shouldDrawHUDLayer()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 5bbf41eb94..ee3f9b9784 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -62,7 +62,7 @@ const render::ShapeKey Sphere3DOverlay::getShapeKey() { if (getAlpha() != 1.0f) { builder.withTranslucent(); } - if (!getIsSolid()) { + if (!getIsSolid() || shouldDrawHUDLayer()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 29b8aee08b..4dc8d3378c 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -349,10 +349,9 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c auto relativePosition = vec3(relativePosition4) / relativePosition4.w; auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction; - float uiRadius = _hmdUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale - + const float UI_RADIUS = 1.0f; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale float instersectionDistance; - if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){ + if (raySphereIntersect(relativeDirection, relativePosition, UI_RADIUS, &instersectionDistance)){ result = position + glm::normalize(direction) * instersectionDistance; return true; } diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 5be2d68cf9..e6a32dcfb9 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -111,8 +111,6 @@ public: void setDisplayPlugin(const DisplayPluginPointer& displayPlugin) { _currentDisplayPlugin = displayPlugin; } void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; } - float getHmdUiRadius() const { return _hmdUIRadius; } - signals: void allowMouseCaptureChanged(); void alphaChanged(); @@ -142,7 +140,6 @@ private: float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO }; float _alpha { 1.0f }; - float _hmdUIRadius { 1.0f }; int _previousBorderWidth { -1 }; int _previousBorderHeight { -1 }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index e1259fc5fc..4e8940fe59 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -626,6 +626,12 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeScene(); } + // Clear the depth framebuffer after drawing the scene so that the HUD elements can depth test against each other + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(_compositeFramebuffer); + batch.clearDepthFramebuffer((float) UINT32_MAX); + }); #ifdef HIFI_ENABLE_NSIGHT_DEBUG if (false) // do not compositeoverlay if running nsight debug @@ -635,16 +641,33 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeOverlay(); } - auto compositorHelper = DependencyManager::get(); - if (compositorHelper->getReticleVisible()) { - PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount()) - compositePointer(); + // Only render HUD layer 3D overlays in HMD mode + if (isHmd()) { + PROFILE_RANGE_EX(render_detail, "compositeHUDOverlays", 0xff0077ff, (uint64_t)presentCount()) + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(_compositeFramebuffer); + }); + _gpuContext->executeBatch(_currentFrame->postCompositeBatch); } { PROFILE_RANGE_EX(render_detail, "compositeExtra", 0xff0077ff, (uint64_t)presentCount()) compositeExtra(); } + + // Clear the depth buffer again and draw the pointer last so it's on top of everything + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(_compositeFramebuffer); + batch.clearDepthFramebuffer((float) UINT32_MAX); + }); + + auto compositorHelper = DependencyManager::get(); + if (compositorHelper->getReticleVisible()) { + PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount()) + compositePointer(); + } } void OpenGLDisplayPlugin::internalPresent() { @@ -851,7 +874,8 @@ OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { void OpenGLDisplayPlugin::updateCompositeFramebuffer() { auto renderSize = getRecommendedRenderSize(); if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) { - _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y)); + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); + _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, depthFormat, renderSize.x, renderSize.y)); } } diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp index fd45398236..1e0e7e6c1f 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp @@ -35,16 +35,7 @@ bool DebugHmdDisplayPlugin::beginFrameRender(uint32_t frameIndex) { //_currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; withNonPresentThreadLock([&] { - _uiModelTransform = DependencyManager::get()->getModelTransform(); _frameInfos[frameIndex] = _currentRenderFrameInfo; - - _handPoses[0] = glm::translate(mat4(), vec3(0.3f * cosf(secTimestampNow() * 3.0f), -0.3f * sinf(secTimestampNow() * 5.0f), 0.0f)); - _handLasers[0].color = vec4(1, 0, 0, 1); - _handLasers[0].mode = HandLaserMode::Overlay; - - _handPoses[1] = glm::translate(mat4(), vec3(0.3f * sinf(secTimestampNow() * 3.0f), -0.3f * cosf(secTimestampNow() * 5.0f), 0.0f)); - _handLasers[1].color = vec4(0, 1, 1, 1); - _handLasers[1].mode = HandLaserMode::Overlay; }); return Parent::beginFrameRender(frameIndex); } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index b183850e7f..aef5c73fa3 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -32,9 +32,6 @@ #include "../Logging.h" #include "../CompositorHelper.h" -#include <../render-utils/shaders/render-utils/glowLine_vert.h> -#include <../render-utils/shaders/render-utils/glowLine_frag.h> - static const QString MONO_PREVIEW = "Mono Preview"; static const QString DISABLE_PREVIEW = "Disable Preview"; @@ -45,17 +42,10 @@ static const bool DEFAULT_MONO_VIEW = true; static const bool DEFAULT_DISABLE_PREVIEW = false; #endif static const glm::mat4 IDENTITY_MATRIX; -static const size_t NUMBER_OF_HANDS = 2; //#define LIVE_SHADER_RELOAD 1 extern glm::vec3 getPoint(float yaw, float pitch); -struct HandLaserData { - vec4 p1; - vec4 p2; - vec4 color; -}; - static QString readFile(const QString& filename) { QFile file(filename); file.open(QFile::Text | QFile::ReadOnly); @@ -118,31 +108,9 @@ void HmdDisplayPlugin::internalDeactivate() { Parent::internalDeactivate(); } -static const int32_t LINE_DATA_SLOT = 1; - void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); _overlayRenderer.build(); - - { - auto state = std::make_shared(); - auto VS = gpu::Shader::createVertex(std::string(glowLine_vert)); - auto PS = gpu::Shader::createPixel(std::string(glowLine_frag)); - auto program = gpu::Shader::createProgram(VS, PS); - state->setCullMode(gpu::State::CULL_NONE); - state->setDepthTest(true, false, gpu::LESS_EQUAL); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - - gpu::Shader::BindingSet bindings; - bindings.insert({ "lineData", LINE_DATA_SLOT });; - gpu::Shader::makeProgram(*program, bindings); - _glowLinePipeline = gpu::Pipeline::create(program, state); - _handLaserUniforms = std::array{ { std::make_shared(), std::make_shared() } }; - _extraLaserUniforms = std::make_shared(); - }; - } void HmdDisplayPlugin::uncustomizeContext() { @@ -157,10 +125,6 @@ void HmdDisplayPlugin::uncustomizeContext() { }); _overlayRenderer = OverlayRenderer(); _previewTexture.reset(); - _handLaserUniforms[0].reset(); - _handLaserUniforms[1].reset(); - _extraLaserUniforms.reset(); - _glowLinePipeline.reset(); Parent::uncustomizeContext(); } @@ -383,132 +347,13 @@ void HmdDisplayPlugin::updateFrameData() { getGLBackend()->setCameraCorrection(correction); } - withPresentThreadLock([&] { - _presentHandLasers = _handLasers; - _presentHandPoses = _handPoses; - _presentUiModelTransform = _uiModelTransform; - - _presentExtraLaser = _extraLaser; - _presentExtraLaserStart = _extraLaserStart; - }); - auto compositorHelper = DependencyManager::get(); glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); - static const float OUT_OF_BOUNDS = -1; - std::array handGlowPoints { { vec2(OUT_OF_BOUNDS), vec2(OUT_OF_BOUNDS) } }; - vec2 extraGlowPoint(OUT_OF_BOUNDS); - - float uiRadius = compositorHelper->getHmdUiRadius(); - - // compute the glow point interesections - for (size_t i = 0; i < NUMBER_OF_HANDS; ++i) { - if (_presentHandPoses[i] == IDENTITY_MATRIX) { - continue; - } - const auto& handLaser = _presentHandLasers[i]; - if (!handLaser.valid()) { - continue; - } - - const vec3& laserDirection = handLaser.direction; - mat4 model = _presentHandPoses[i]; - vec3 castStart = vec3(model[3]); - vec3 castDirection = glm::quat_cast(model) * laserDirection; - - // this offset needs to match GRAB_POINT_SPHERE_OFFSET in scripts/system/libraries/controllers.js:19 - static const vec3 GRAB_POINT_SPHERE_OFFSET(0.04f, 0.13f, 0.039f); // x = upward, y = forward, z = lateral - - // swizzle grab point so that (x = upward, y = lateral, z = forward) - vec3 grabPointOffset = glm::vec3(GRAB_POINT_SPHERE_OFFSET.x, GRAB_POINT_SPHERE_OFFSET.z, -GRAB_POINT_SPHERE_OFFSET.y); - if (i == 0) { - grabPointOffset.x *= -1.0f; // this changes between left and right hands - } - castStart += glm::quat_cast(model) * grabPointOffset; - - // Find the intersection of the laser with he UI and use it to scale the model matrix - float distance; - if (!glm::intersectRaySphere(castStart, castDirection, - _presentUiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { - continue; - } - - _presentHandLaserPoints[i].first = castStart; - _presentHandLaserPoints[i].second = _presentHandLaserPoints[i].first + (castDirection * distance); - - vec3 intersectionPosition = castStart + (castDirection * distance) - _presentUiModelTransform.getTranslation(); - intersectionPosition = glm::inverse(_presentUiModelTransform.getRotation()) * intersectionPosition; - - // Take the interesection normal and convert it to a texture coordinate - vec2 yawPitch; - { - vec2 xdir = glm::normalize(vec2(intersectionPosition.x, -intersectionPosition.z)); - yawPitch.x = glm::atan(xdir.x, xdir.y); - yawPitch.y = (acosf(intersectionPosition.y) * -1.0f) + (float)M_PI_2; - } - vec2 halfFov = CompositorHelper::VIRTUAL_UI_TARGET_FOV / 2.0f; - - // Are we out of range - if (glm::any(glm::greaterThan(glm::abs(yawPitch), halfFov))) { - continue; - } - - yawPitch /= CompositorHelper::VIRTUAL_UI_TARGET_FOV; - yawPitch += 0.5f; - handGlowPoints[i] = yawPitch; - } - - // compute the glow point interesections - if (_presentExtraLaser.valid()) { - const vec3& laserDirection = _presentExtraLaser.direction; - vec3 castStart = _presentExtraLaserStart; - vec3 castDirection = laserDirection; - - // Find the intersection of the laser with he UI and use it to scale the model matrix - float distance; - if (glm::intersectRaySphere(castStart, castDirection, - _presentUiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { - - - _presentExtraLaserPoints.first = castStart; - _presentExtraLaserPoints.second = _presentExtraLaserPoints.first + (castDirection * distance); - - vec3 intersectionPosition = castStart + (castDirection * distance) - _presentUiModelTransform.getTranslation(); - intersectionPosition = glm::inverse(_presentUiModelTransform.getRotation()) * intersectionPosition; - - // Take the interesection normal and convert it to a texture coordinate - vec2 yawPitch; - { - vec2 xdir = glm::normalize(vec2(intersectionPosition.x, -intersectionPosition.z)); - yawPitch.x = glm::atan(xdir.x, xdir.y); - yawPitch.y = (acosf(intersectionPosition.y) * -1.0f) + (float)M_PI_2; - } - vec2 halfFov = CompositorHelper::VIRTUAL_UI_TARGET_FOV / 2.0f; - - // Are we out of range - if (!glm::any(glm::greaterThan(glm::abs(yawPitch), halfFov))) { - yawPitch /= CompositorHelper::VIRTUAL_UI_TARGET_FOV; - yawPitch += 0.5f; - extraGlowPoint = yawPitch; - } - } - } - for_each_eye([&](Eye eye) { auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat; _overlayRenderer.mvps[eye] = _eyeProjections[eye] * modelView; }); - - // Setup the uniforms - { - auto& uniforms = _overlayRenderer.uniforms; - uniforms.alpha = _compositeOverlayAlpha; - uniforms.glowPoints = vec4(handGlowPoints[0], handGlowPoints[1]); - uniforms.glowColors[0] = _presentHandLasers[0].color; - uniforms.glowColors[1] = _presentHandLasers[1].color; - uniforms.extraGlowPoint = extraGlowPoint; - uniforms.extraGlowColor = _presentExtraLaser.color; - } } void HmdDisplayPlugin::OverlayRenderer::build() { @@ -573,8 +418,8 @@ void HmdDisplayPlugin::OverlayRenderer::build() { } void HmdDisplayPlugin::OverlayRenderer::updatePipeline() { - static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui_glow.vert"; - static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui_glow.frag"; + static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui.vert"; + static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui.frag"; #if LIVE_SHADER_RELOAD static qint64 vsBuiltAge = 0; @@ -598,7 +443,7 @@ void HmdDisplayPlugin::OverlayRenderer::updatePipeline() { this->uniformsLocation = program->getUniformBuffers().findLocation("overlayBuffer"); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false)); + state->setDepthTest(gpu::State::DepthTest(true, true, gpu::LESS_EQUAL)); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); @@ -665,84 +510,6 @@ void HmdDisplayPlugin::compositeOverlay() { _overlayRenderer.render(*this); } -bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) { - HandLaserInfo info; - info.mode = mode; - info.color = color; - info.direction = direction; - withNonPresentThreadLock([&] { - if (hands & Hand::LeftHand) { - _handLasers[0] = info; - } - if (hands & Hand::RightHand) { - _handLasers[1] = info; - } - }); - // FIXME defer to a child class plugin to determine if hand lasers are actually - // available based on the presence or absence of hand controllers - return true; -} - -bool HmdDisplayPlugin::setExtraLaser(HandLaserMode mode, const vec4& color, const glm::vec3& sensorSpaceStart, const vec3& sensorSpaceDirection) { - HandLaserInfo info; - info.mode = mode; - info.color = color; - info.direction = sensorSpaceDirection; - withNonPresentThreadLock([&] { - _extraLaser = info; - _extraLaserStart = sensorSpaceStart; - }); - - // FIXME defer to a child class plugin to determine if hand lasers are actually - // available based on the presence or absence of hand controllers - return true; -} - - -void HmdDisplayPlugin::compositeExtra() { - // If neither hand laser is activated, exit - if (!_presentHandLasers[0].valid() && !_presentHandLasers[1].valid() && !_presentExtraLaser.valid()) { - return; - } - - if (_presentHandPoses[0] == IDENTITY_MATRIX && _presentHandPoses[1] == IDENTITY_MATRIX && !_presentExtraLaser.valid()) { - return; - } - - render([&](gpu::Batch& batch) { - batch.setFramebuffer(_compositeFramebuffer); - batch.setModelTransform(Transform()); - batch.setViewportTransform(ivec4(uvec2(0), _renderTargetSize)); - batch.setViewTransform(_currentPresentFrameInfo.presentPose, false); - // Compile the shaders - batch.setPipeline(_glowLinePipeline); - - - bilateral::for_each_side([&](bilateral::Side side){ - auto index = bilateral::index(side); - if (_presentHandPoses[index] == IDENTITY_MATRIX) { - return; - } - const auto& laser = _presentHandLasers[index]; - if (laser.valid()) { - const auto& points = _presentHandLaserPoints[index]; - _handLaserUniforms[index]->resize(sizeof(HandLaserData)); - _handLaserUniforms[index]->setSubData(0, HandLaserData { vec4(points.first, 1.0f), vec4(points.second, 1.0f), _handLasers[index].color }); - batch.setUniformBuffer(LINE_DATA_SLOT, _handLaserUniforms[index]); - batch.draw(gpu::TRIANGLE_STRIP, 4, 0); - } - }); - - if (_presentExtraLaser.valid()) { - const auto& points = _presentExtraLaserPoints; - _extraLaserUniforms->resize(sizeof(HandLaserData)); - _extraLaserUniforms->setSubData(0, HandLaserData { vec4(points.first, 1.0f), vec4(points.second, 1.0f), _presentExtraLaser.color }); - batch.setUniformBuffer(LINE_DATA_SLOT, _extraLaserUniforms); - batch.draw(gpu::TRIANGLE_STRIP, 4, 0); - } - }); -} - HmdDisplayPlugin::~HmdDisplayPlugin() { } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 055328ee21..0827d04922 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -37,9 +37,6 @@ public: virtual glm::mat4 getHeadPose() const override; - bool setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) override; - bool setExtraLaser(HandLaserMode mode, const vec4& color, const glm::vec3& sensorSpaceStart, const vec3& sensorSpaceDirection) override; - bool wantVsync() const override { return false; } @@ -63,33 +60,6 @@ protected: void customizeContext() override; void uncustomizeContext() override; void updateFrameData() override; - void compositeExtra() override; - - struct HandLaserInfo { - HandLaserMode mode { HandLaserMode::None }; - vec4 color { 1.0f }; - vec3 direction { 0, 0, -1 }; - - // Is this hand laser info suitable for drawing? - bool valid() const { - return (mode != HandLaserMode::None && color.a > 0.0f && direction != vec3()); - } - }; - - Transform _uiModelTransform; - std::array _handLasers; - std::array _handPoses; - - Transform _presentUiModelTransform; - std::array _presentHandLasers; - std::array _presentHandPoses; - std::array, 2> _presentHandLaserPoints; - - HandLaserInfo _extraLaser; - HandLaserInfo _presentExtraLaser; - vec3 _extraLaserStart; - vec3 _presentExtraLaserStart; - std::pair _presentExtraLaserPoints; std::array _eyeOffsets; std::array _eyeProjections; @@ -120,9 +90,6 @@ private: bool _disablePreviewItemAdded { false }; bool _monoPreview { true }; bool _clearPreviewFlag { false }; - std::array _handLaserUniforms; - gpu::BufferPointer _extraLaserUniforms; - gpu::PipelinePointer _glowLinePipeline; gpu::TexturePointer _previewTexture; glm::vec2 _lastWindowSize; @@ -140,14 +107,7 @@ private: struct Uniforms { mat4 mvp; - vec4 glowPoints { -1 }; - vec4 glowColors[2]; - vec2 resolution { CompositorHelper::VIRTUAL_SCREEN_SIZE }; - float radius { 0.005f }; float alpha { 1.0f }; - - vec4 extraGlowColor; - vec2 extraGlowPoint { -1 }; } uniforms; struct Vertex { diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index e21c9581e1..1eb9ef8462 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -679,11 +679,17 @@ QVector EntityScriptingInterface::findEntitiesByType(const QString entity RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { - PROFILE_RANGE(script_entities, __FUNCTION__); - QVector entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); QVector entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard); - return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entitiesToInclude, entitiesToDiscard, visibleOnly, collidableOnly); + + return findRayIntersection(ray, precisionPicking, entitiesToInclude, entitiesToDiscard, visibleOnly, collidableOnly); +} + +RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { + PROFILE_RANGE(script_entities, __FUNCTION__); + + return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly); } // FIXME - we should remove this API and encourage all users to use findRayIntersection() instead. We've changed diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 575528fa78..fc5053bdd6 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -229,6 +229,11 @@ public slots: const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue(), bool visibleOnly = false, bool collidableOnly = false); + /// Same as above but with QVectors + Q_INVOKABLE RayToEntityIntersectionResult findRayIntersection(const PickRay& ray, bool precisionPicking, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly, bool collidableOnly); + /// If the scripting context has visible entities, this will determine a ray intersection, and will block in /// order to return an accurate result Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue()); diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index 3c6fed9393..bfebe85753 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -32,6 +32,8 @@ namespace gpu { Mat4 pose; /// The collection of batches which make up the frame Batches batches; + /// Single batch containing overlays to be drawn in the composite framebuffer + Batch postCompositeBatch; /// The main thread updates to buffers that are applicable for this frame. BufferUpdates bufferUpdates; /// The destination framebuffer in which the results will be placed diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 9e18ee534d..c63db81405 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -109,24 +109,6 @@ public: RightHand = 0x02, }; - enum class HandLaserMode { - None, // Render no hand lasers - Overlay, // Render hand lasers only if they intersect with the UI layer, and stop at the UI layer - }; - - virtual bool setHandLaser( - uint32_t hands, // Bits from the Hand enum - HandLaserMode mode, // Mode in which to render - const vec4& color = vec4(1), // The color of the rendered laser - const vec3& direction = vec3(0, 0, -1) // The direction in which to render the hand lasers - ) { - return false; - } - - virtual bool setExtraLaser(HandLaserMode mode, const vec4& color, const glm::vec3& sensorSpaceStart, const vec3& sensorSpaceDirection) { - return false; - } - virtual bool suppressKeyboard() { return false; } virtual void unsuppressKeyboard() {}; virtual bool isKeyboardVisible() { return false; } diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 20c999019b..94fd1627d6 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -44,7 +44,7 @@ using namespace render; -extern void initOverlay3DPipelines(render::ShapePlumber& plumber); +extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); extern void initDeferredPipelines(render::ShapePlumber& plumber); void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index 6c3a58b7e5..18146ca651 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -69,7 +69,7 @@ using namespace render; using namespace std::placeholders; -void initOverlay3DPipelines(ShapePlumber& plumber); +void initOverlay3DPipelines(ShapePlumber& plumber, bool depthTest = false); void initDeferredPipelines(ShapePlumber& plumber); void initForwardPipelines(ShapePlumber& plumber); @@ -79,7 +79,7 @@ void addPlumberPipeline(ShapePlumber& plumber, void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args); void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args); -void initOverlay3DPipelines(ShapePlumber& plumber) { +void initOverlay3DPipelines(ShapePlumber& plumber, bool depthTest) { auto vertex = gpu::Shader::createVertex(std::string(overlay3D_vert)); auto vertexModel = gpu::Shader::createVertex(std::string(model_vert)); auto pixel = gpu::Shader::createPixel(std::string(overlay3D_frag)); @@ -106,7 +106,11 @@ void initOverlay3DPipelines(ShapePlumber& plumber) { bool isOpaque = (i & 4); auto state = std::make_shared(); - state->setDepthTest(false); + if (depthTest) { + state->setDepthTest(true, true, gpu::LESS_EQUAL); + } else { + state->setDepthTest(false); + } state->setCullMode(isCulled ? gpu::State::CULL_BACK : gpu::State::CULL_NONE); if (isBiased) { state->setDepthBias(1.0f); diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index b30637c83f..78b54d26f1 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -34,6 +34,7 @@ int vec2MetaTypeId = qRegisterMetaType(); int quatMetaTypeId = qRegisterMetaType(); int xColorMetaTypeId = qRegisterMetaType(); int pickRayMetaTypeId = qRegisterMetaType(); +int rayPickResultMetaTypeId = qRegisterMetaType(); int collisionMetaTypeId = qRegisterMetaType(); int qMapURLStringMetaTypeId = qRegisterMetaType>(); int socketErrorMetaTypeId = qRegisterMetaType(); @@ -56,6 +57,7 @@ void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, qColorToScriptValue, qColorFromScriptValue); qScriptRegisterMetaType(engine, qURLToScriptValue, qURLFromScriptValue); qScriptRegisterMetaType(engine, pickRayToScriptValue, pickRayFromScriptValue); + qScriptRegisterMetaType(engine, rayPickResultToScriptValue, rayPickResultFromScriptValue); qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue); qScriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue); qScriptRegisterMetaType(engine, qSizeFToScriptValue, qSizeFFromScriptValue); @@ -751,6 +753,23 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) { } } +QScriptValue rayPickResultToScriptValue(QScriptEngine* engine, const RayPickResult& rayPickResult) { + QScriptValue obj = engine->newObject(); + obj.setProperty("type", rayPickResult.type); + QScriptValue objectID = quuidToScriptValue(engine, rayPickResult.objectID); + obj.setProperty("objectID", objectID); + obj.setProperty("distance", rayPickResult.distance); + QScriptValue intersection = vec3toScriptValue(engine, rayPickResult.intersection); + obj.setProperty("intersection", intersection); + QScriptValue surfaceNormal = vec3toScriptValue(engine, rayPickResult.surfaceNormal); + obj.setProperty("surfaceNormal", surfaceNormal); + return obj; +} + +void rayPickResultFromScriptValue(const QScriptValue& object, RayPickResult& rayPickResult) { + // TODO: cannot currently accept RayPickResults from JS +} + QScriptValue collisionToScriptValue(QScriptEngine* engine, const Collision& collision) { QScriptValue obj = engine->newObject(); obj.setProperty("type", collision.type); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 123c769a96..f4fc9109ac 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -136,6 +136,29 @@ Q_DECLARE_METATYPE(PickRay) QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay); void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay); +enum IntersectionType { + NONE, + ENTITY, + OVERLAY, + AVATAR, + HUD +}; + +class RayPickResult { +public: + RayPickResult() {} + RayPickResult(const IntersectionType type, const QUuid& objectID, const float distance, const glm::vec3& intersection, const glm::vec3& surfaceNormal = glm::vec3(NAN)) : + type(type), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) {} + IntersectionType type { NONE }; + QUuid objectID { 0 }; + float distance { FLT_MAX }; + glm::vec3 intersection { NAN }; + glm::vec3 surfaceNormal { NAN }; +}; +Q_DECLARE_METATYPE(RayPickResult) +QScriptValue rayPickResultToScriptValue(QScriptEngine* engine, const RayPickResult& rayPickResult); +void rayPickResultFromScriptValue(const QScriptValue& object, RayPickResult& rayPickResult); + enum ContactEventType { CONTACT_EVENT_TYPE_START, CONTACT_EVENT_TYPE_CONTINUE, diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 0df504dfa2..69ad8a5710 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -51,8 +51,6 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { }); withNonPresentThreadLock([&] { - _uiModelTransform = DependencyManager::get()->getModelTransform(); - _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return Parent::beginFrameRender(frameIndex); diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index b31f55edeb..771ba8a3d8 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -594,9 +594,6 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } withNonPresentThreadLock([&] { - _uiModelTransform = DependencyManager::get()->getModelTransform(); - // Make controller poses available to the presentation thread - _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return Parent::beginFrameRender(frameIndex); diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index da8add5117..2844940d2b 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -197,53 +197,24 @@ Mouse.prototype.restoreRotateCursor = function() { var mouse = new Mouse(); - -// Beacon class stores info for drawing a line at object's target position -function Beacon() { - this.height = 0.10; - this.overlayID = Overlays.addOverlay("line3d", { - color: { - red: 200, - green: 200, - blue: 200 - }, - alpha: 1, - visible: false, - lineWidth: 2 - }); -} - -Beacon.prototype.enable = function() { - Overlays.editOverlay(this.overlayID, { - visible: true - }); +var beacon = { + type: "cube", + dimensions: { + x: 0.01, + y: 0, + z: 0.01 + }, + color: { + red: 200, + green: 200, + blue: 200 + }, + alpha: 1, + solid: true, + ignoreRayIntersection: true, + visible: true }; -Beacon.prototype.disable = function() { - Overlays.editOverlay(this.overlayID, { - visible: false - }); -}; - -Beacon.prototype.updatePosition = function(position) { - Overlays.editOverlay(this.overlayID, { - visible: true, - start: { - x: position.x, - y: position.y + this.height, - z: position.z - }, - end: { - x: position.x, - y: position.y - this.height, - z: position.z - } - }); -}; - -var beacon = new Beacon(); - - // TODO: play sounds again when we aren't leaking AudioInjector threads // var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav"); // var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav"); @@ -285,6 +256,21 @@ function Grabber() { this.liftKey = false; // SHIFT this.rotateKey = false; // CONTROL + + this.mouseRayOverlays = RayPick.createRayPick({ + joint: "Mouse", + filter: RayPick.PICK_OVERLAYS, + enabled: true + }); + RayPick.setIncludeOverlays(this.mouseRayOverlays, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); + var renderStates = [{name: "grabbed", end: beacon}]; + this.mouseRayEntities = LaserPointers.createLaserPointer({ + joint: "Mouse", + filter: RayPick.PICK_ENTITIES, + faceAvatar: true, + enabled: true, + renderStates: renderStates + }); } Grabber.prototype.computeNewGrabPlane = function() { @@ -333,40 +319,42 @@ Grabber.prototype.pressEvent = function(event) { return; } - var pickRay = Camera.computePickRay(event.x, event.y); - - var overlayResult = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); - if (overlayResult.intersects) { + var overlayResult = RayPick.getPrevRayPickResult(this.mouseRayOverlays); + if (overlayResult.type != RayPick.INTERSECTED_NONE) { return; } - var pickResults = Entities.findRayIntersection(pickRay, true); // accurate picking - if (!pickResults.intersects) { - // didn't click on anything + var pickResults = LaserPointers.getPrevRayPickResult(this.mouseRayEntities); + if (pickResults.type == RayPick.INTERSECTED_NONE) { + LaserPointers.setRenderState(this.mouseRayEntities, ""); return; } - var isDynamic = Entities.getEntityProperties(pickResults.entityID, "dynamic").dynamic; + var isDynamic = Entities.getEntityProperties(pickResults.objectID, "dynamic").dynamic; if (!isDynamic) { // only grab dynamic objects return; } - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, pickResults.entityID, DEFAULT_GRABBABLE_DATA); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, pickResults.objectID, DEFAULT_GRABBABLE_DATA); if (grabbableData.grabbable === false) { return; } + LaserPointers.setRenderState(this.mouseRayEntities, "grabbed"); + LaserPointers.setLockEndUUID(this.mouseRayEntities, pickResults.objectID, false); + mouse.startDrag(event); - var clickedEntity = pickResults.entityID; + var clickedEntity = pickResults.objectID; var entityProperties = Entities.getEntityProperties(clickedEntity); this.startPosition = entityProperties.position; this.lastRotation = entityProperties.rotation; var cameraPosition = Camera.getPosition(); var objectBoundingDiameter = Vec3.length(entityProperties.dimensions); - beacon.height = objectBoundingDiameter; + beacon.dimensions.y = objectBoundingDiameter; + LaserPointers.editRenderState(this.mouseRayEntities, "grabbed", {end: beacon}); this.maxDistance = objectBoundingDiameter / MAX_SOLID_ANGLE; if (Vec3.distance(this.startPosition, cameraPosition) > this.maxDistance) { // don't allow grabs of things far away @@ -385,6 +373,7 @@ Grabber.prototype.pressEvent = function(event) { }; // compute the grab point + var pickRay = Camera.computePickRay(event.x, event.y); var nearestPoint = Vec3.subtract(this.startPosition, cameraPosition); var distanceToGrab = Vec3.dot(nearestPoint, pickRay.direction); nearestPoint = Vec3.multiply(distanceToGrab, pickRay.direction); @@ -395,8 +384,6 @@ Grabber.prototype.pressEvent = function(event) { this.computeNewGrabPlane(); - beacon.updatePosition(this.startPosition); - if (!entityIsGrabbedByOther(this.entityID)) { this.moveEvent(event); } @@ -431,7 +418,7 @@ Grabber.prototype.releaseEvent = function(event) { } this.actionID = null; - beacon.disable(); + LaserPointers.setRenderState(this.mouseRayEntities, ""); var args = "mouse"; Entities.callEntityMethod(this.entityID, "releaseGrab", args); @@ -552,7 +539,6 @@ Grabber.prototype.moveEventProcess = function() { ttl: ACTION_TTL }; - beacon.updatePosition(this.targetPosition); } if (!this.actionID) { @@ -586,6 +572,11 @@ Grabber.prototype.keyPressEvent = function(event) { this.computeNewGrabPlane(); }; +Grabber.prototype.cleanup = function() { + LaserPointers.removeLaserPointer(this.mouseRayEntities); + RayPick.removeRayPick(this.mouseRayOverlays); +}; + var grabber = new Grabber(); function pressEvent(event) { @@ -608,10 +599,15 @@ function keyReleaseEvent(event) { grabber.keyReleaseEvent(event); } +function cleanup() { + grabber.cleanup(); +} + Controller.mousePressEvent.connect(pressEvent); Controller.mouseMoveEvent.connect(moveEvent); Controller.mouseReleaseEvent.connect(releaseEvent); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); +Script.scriptEnding.connect(cleanup); }()); // END LOCAL_SCOPE diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index bd8ab26506..44f79a5570 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -64,8 +64,6 @@ var HAPTIC_STYLUS_DURATION = 20.0; var HAPTIC_LASER_UI_STRENGTH = 1.0; var HAPTIC_LASER_UI_DURATION = 20.0; -var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move. - var PICK_WITH_HAND_RAY = true; var EQUIP_SPHERE_SCALE_FACTOR = 0.65; @@ -157,7 +155,6 @@ var INCHES_TO_METERS = 1.0 / 39.3701; // these control how long an abandoned pointer line or action will hang around var ACTION_TTL = 15; // seconds var ACTION_TTL_REFRESH = 5; -var PICKS_PER_SECOND_PER_HAND = 60; var MSECS_PER_SEC = 1000.0; var GRABBABLE_PROPERTIES = [ "position", @@ -458,15 +455,6 @@ function entityHasActions(entityID) { return Entities.getActionIDs(entityID).length > 0; } -function findRayIntersection(pickRay, precise, include, exclude) { - var entities = Entities.findRayIntersection(pickRay, precise, include, exclude, true); - var overlays = Overlays.findRayIntersection(pickRay, precise, [], [HMD.tabletID]); - if (!overlays.intersects || (entities.intersects && (entities.distance <= overlays.distance))) { - return entities; - } - return overlays; -} - function entityIsGrabbedByOther(entityID) { // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. var actionIDs = Entities.getActionIDs(entityID); @@ -1062,6 +1050,74 @@ function getControllerJointIndex(hand) { // global EquipHotspotBuddy instance var equipHotspotBuddy = new EquipHotspotBuddy(); +var halfPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID +} +var halfEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true +} +var fullPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID +} +var fullEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true +} +var holdPath = { + type: "line3d", + color: COLORS_GRAB_DISTANCE_HOLD, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID +} + +var renderStates = [{name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath}]; +var headRenderStates = [{name: "half", end: halfEnd}, + {name: "full", end: fullEnd}, + {name: "hold", path: holdPath}]; + +// how far from camera to search intersection? +var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; +var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + function MyController(hand) { this.hand = hand; this.autoUnequipCounter = 0; @@ -1110,7 +1166,6 @@ function MyController(hand) { this.grabbedThingID = null; // on this entity. this.grabbedOverlay = null; this.state = STATE_OFF; - this.pointer = null; // entity-id of line object this.triggerValue = 0; // rolling average of trigger value this.triggerClicked = false; @@ -1119,18 +1174,33 @@ function MyController(hand) { this.rawThumbValue = 0; // for visualizations - this.overlayLine = null; - this.searchSphere = null; - this.otherGrabbingLine = null; + this.halfEnd = halfEnd; + this.fullEnd = fullEnd; + this.laserPointer = LaserPointers.createLaserPointer({ + joint: (hand == RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + maxDistance: PICK_MAX_DISTANCE, + posOffset: getGrabPointSphereOffset(this.handToController()), + renderStates: renderStates, + faceAvatar: true, + defaultRenderStates: defaultRenderStates + }); + this.headLaserPointer = LaserPointers.createLaserPointer({ + joint: "Avatar", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + maxDistance: PICK_MAX_DISTANCE, + renderStates: headRenderStates, + faceAvatar: true, + defaultRenderStates: defaultRenderStates + }); + LaserPointers.setIgnoreOverlays(this.laserPointer, [HMD.tabletID]); + LaserPointers.setIgnoreOverlays(this.headLaserPointer, [HMD.tabletID]); this.otherGrabbingUUID = null; this.waitForTriggerRelease = false; - // how far from camera to search intersection? - var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; this.intersectionDistance = 0.0; - this.searchSphereDistance = DEFAULT_SEARCH_SPHERE_DISTANCE; this.ignoreIK = false; this.offsetPosition = Vec3.ZERO; @@ -1311,41 +1381,6 @@ function MyController(hand) { } }; - this.searchSphereOn = function(location, size, color) { - - var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); - var brightColor = colorPow(color, 0.06); - if (this.searchSphere === null) { - var sphereProperties = { - name: "searchSphere", - position: location, - rotation: rotation, - outerRadius: size * 1.2, - innerColor: brightColor, - outerColor: color, - innerAlpha: 0.9, - outerAlpha: 0.0, - solid: true, - ignoreRayIntersection: true, - drawInFront: true, // Even when burried inside of something, show it. - visible: true - }; - this.searchSphere = Overlays.addOverlay("circle3d", sphereProperties); - } else { - Overlays.editOverlay(this.searchSphere, { - position: location, - rotation: rotation, - innerColor: brightColor, - outerColor: color, - innerAlpha: 1.0, - outerAlpha: 0.0, - outerRadius: size * 1.2, - visible: true, - ignoreRayIntersection: true - }); - } - }; - this.showStylus = function() { if (this.stylus) { return; @@ -1381,99 +1416,40 @@ function MyController(hand) { this.stylus = null; }; - this.overlayLineOn = function(closePoint, farPoint, color, farParentID) { - if (this.overlayLine === null) { - var lineProperties = { - name: "line", - glow: 1.0, - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - ignoreRayIntersection: true, // always ignore this - drawInFront: true, // Even when burried inside of something, show it. - visible: true, - alpha: 1, - parentID: AVATAR_SELF_ID, - parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"), - endParentID: farParentID - }; - this.overlayLine = Overlays.addOverlay("line3d", lineProperties); - - } else { - if (farParentID && farParentID != NULL_UUID) { - Overlays.editOverlay(this.overlayLine, { - color: color, - endParentID: farParentID - }); - } else { - Overlays.editOverlay(this.overlayLine, { - length: Vec3.distance(farPoint, closePoint), - color: color, - endParentID: farParentID - }); - } - } - }; - - this.searchIndicatorOn = function(distantPickRay) { - var handPosition = distantPickRay.origin; + this.updateLaserPointer = function() { var SEARCH_SPHERE_SIZE = 0.011; - var SEARCH_SPHERE_FOLLOW_RATE = 0.50; - - if (this.intersectionDistance > 0) { - // If we hit something with our pick ray, move the search sphere toward that distance - this.searchSphereDistance = this.searchSphereDistance * SEARCH_SPHERE_FOLLOW_RATE + - this.intersectionDistance * (1.0 - SEARCH_SPHERE_FOLLOW_RATE); + var MIN_SPHERE_SIZE = 0.0005; + var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE); + var dim = {x: radius, y: radius, z: radius}; + var mode = "hold"; + if (this.state !== STATE_DISTANCE_HOLDING && this.state !== STATE_DISTANCE_ROTATING) { + mode = (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? "full" : "half"; } - var searchSphereLocation = Vec3.sum(distantPickRay.origin, - Vec3.multiply(distantPickRay.direction, this.searchSphereDistance)); - this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance, - (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? - COLORS_GRAB_SEARCHING_FULL_SQUEEZE : - COLORS_GRAB_SEARCHING_HALF_SQUEEZE); - if (PICK_WITH_HAND_RAY) { - this.overlayLineOn(handPosition, searchSphereLocation, - (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? - COLORS_GRAB_SEARCHING_FULL_SQUEEZE : - COLORS_GRAB_SEARCHING_HALF_SQUEEZE); + var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; + if (mode === "full") { + var fullEndToEdit = PICK_WITH_HAND_RAY ? this.fullEnd : fullEnd; + fullEndToEdit.dimensions = dim; + LaserPointers.editRenderState(laserPointerID, mode, {path: fullPath, end: fullEndToEdit}); + } else if (mode === "half") { + var halfEndToEdit = PICK_WITH_HAND_RAY ? this.halfEnd : halfEnd; + halfEndToEdit.dimensions = dim; + LaserPointers.editRenderState(laserPointerID, mode, {path: halfPath, end: halfEndToEdit}); } - }; - - // Turns off indicators used for searching. Overlay line and sphere. - this.searchIndicatorOff = function() { - this.searchSphereOff(); - if (PICK_WITH_HAND_RAY) { - this.overlayLineOff(); - } - }; - - this.otherGrabbingLineOn = function(avatarPosition, entityPosition, color) { - if (this.otherGrabbingLine === null) { - var lineProperties = { - lineWidth: 5, - start: avatarPosition, - end: entityPosition, - color: color, - glow: 1.0, - ignoreRayIntersection: true, - drawInFront: true, - visible: true, - alpha: 1 - }; - this.otherGrabbingLine = Overlays.addOverlay("line3d", lineProperties); + LaserPointers.enableLaserPointer(laserPointerID); + LaserPointers.setRenderState(laserPointerID, mode); + if (this.state === STATE_DISTANCE_HOLDING || this.state === STATE_DISTANCE_ROTATING) { + LaserPointers.setLockEndUUID(laserPointerID, this.grabbedThingID, this.grabbedIsOverlay); } else { - Overlays.editOverlay(this.otherGrabbingLine, { - start: avatarPosition, - end: entityPosition, - color: color - }); + LaserPointers.setLockEndUUID(laserPointerID, null, false); } }; + this.laserPointerOff = function() { + var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; + LaserPointers.disableLaserPointer(laserPointerID); + }; + this.evalLightWorldTransform = function(modelPos, modelRot) { var MODEL_LIGHT_POSITION = { @@ -1494,42 +1470,9 @@ function MyController(hand) { }; }; - this.lineOff = function() { - if (this.pointer !== null) { - Entities.deleteEntity(this.pointer); - } - this.pointer = null; - }; - - this.overlayLineOff = function() { - if (this.overlayLine !== null) { - Overlays.deleteOverlay(this.overlayLine); - } - this.overlayLine = null; - }; - - this.searchSphereOff = function() { - if (this.searchSphere !== null) { - Overlays.deleteOverlay(this.searchSphere); - this.searchSphere = null; - this.searchSphereDistance = DEFAULT_SEARCH_SPHERE_DISTANCE; - this.intersectionDistance = 0.0; - } - }; - - this.otherGrabbingLineOff = function() { - if (this.otherGrabbingLine !== null) { - Overlays.deleteOverlay(this.otherGrabbingLine); - } - this.otherGrabbingLine = null; - }; - this.turnOffVisualizations = function() { - this.overlayLineOff(); this.grabPointSphereOff(); - this.lineOff(); - this.searchSphereOff(); - this.otherGrabbingLineOff(); + this.laserPointerOff(); restore2DMode(); }; @@ -1824,12 +1767,12 @@ function MyController(hand) { var rayPickInfo = this.calcRayPickInfo(this.hand); if (rayPickInfo.isValid) { this.intersectionDistance = (rayPickInfo.entityID || rayPickInfo.overlayID) ? rayPickInfo.distance : 0; - this.searchIndicatorOn(rayPickInfo.searchRay); + this.updateLaserPointer(); } else { - this.searchIndicatorOff(); + this.laserPointerOff(); } } else { - this.searchIndicatorOff(); + this.laserPointerOff(); } }; @@ -1870,29 +1813,26 @@ function MyController(hand) { // Performs ray pick test from the hand controller into the world // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND - // @param {object} if set, use this as as the pick ray, expects origin, direction, and length fields. // @returns {object} returns object with two keys entityID and distance // - this.calcRayPickInfo = function(hand, pickRayOverride) { + this.calcRayPickInfo = function(hand) { + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldHandPosition = controllerLocation.position; + var worldHandRotation = controllerLocation.orientation; var pickRay; var valid = true - if (pickRayOverride) { - pickRay = pickRayOverride; - } else { - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var worldHandPosition = controllerLocation.position; - var worldHandRotation = controllerLocation.orientation; - valid = !(worldHandPosition === undefined); - pickRay = { - origin: PICK_WITH_HAND_RAY ? worldHandPosition : Camera.position, - direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Vec3.mix(Quat.getUp(worldHandRotation), - Quat.getFront(Camera.orientation), - HAND_HEAD_MIX_RATIO), - length: PICK_MAX_DISTANCE - }; - } + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldHandPosition = controllerLocation.position; + var worldHandRotation = controllerLocation.orientation; + valid = !(worldHandPosition === undefined); + + pickRay = { + origin: PICK_WITH_HAND_RAY ? worldHandPosition : MyAvatar.getHeadPosition(), + direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Quat.getFront(Camera.orientation), + length: PICK_MAX_DISTANCE + }; var result = { entityID: null, @@ -1902,28 +1842,17 @@ function MyController(hand) { isValid: valid }; - var now = Date.now(); - if (now - this.lastPickTime < MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - return result; - } - this.lastPickTime = now; + var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; + var intersection = LaserPointers.getPrevRayPickResult(laserPointerID); - var intersection; - if (USE_BLACKLIST === true && blacklist.length !== 0) { - intersection = findRayIntersection(pickRay, true, [], blacklist, true); - } else { - intersection = findRayIntersection(pickRay, true, [], [], true); - } - - if (intersection.intersects) { + if (intersection.type != RayPick.INTERSECTED_NONE) { return { - entityID: intersection.entityID, - overlayID: intersection.overlayID, + entityID: intersection.type == RayPick.INTERSECTED_ENTITY ? intersection.objectID : null, + overlayID: intersection.type == RayPick.INTERSECTED_OVERLAY ? intersection.objectID : null, searchRay: pickRay, - distance: Vec3.distance(pickRay.origin, intersection.intersection), + distance: intersection.distance, intersection: intersection.intersection, - normal: intersection.surfaceNormal, - properties: intersection.properties + normal: intersection.surfaceNormal }; } else { return result; @@ -2195,7 +2124,6 @@ function MyController(hand) { var rayPickInfo = this.calcRayPickInfo(this.hand); if (rayPickInfo.entityID || rayPickInfo.overlayID) { this.intersectionDistance = rayPickInfo.distance; - this.searchSphereDistance = this.intersectionDistance; } }; @@ -2387,7 +2315,7 @@ function MyController(hand) { } if (isInEditMode()) { - this.searchIndicatorOn(rayPickInfo.searchRay); + this.updateLaserPointer(); if (this.triggerSmoothedGrab()) { if (!this.editTriggered){ if (rayPickInfo.entityID) { @@ -2420,7 +2348,7 @@ function MyController(hand) { } else { // potentialFarTriggerEntity = entity; } - this.otherGrabbingLineOff(); + this.laserPointerOff(); } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { this.grabbedThingID = entity; @@ -2435,26 +2363,18 @@ function MyController(hand) { } else { // potentialFarGrabEntity = entity; } - this.otherGrabbingLineOff(); + this.laserPointerOff(); } else if (this.otherGrabbingUUID !== null) { if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { - var avatar = AvatarList.getAvatar(this.otherGrabbingUUID); - var IN_FRONT_OF_AVATAR = { x: 0, y: 0.2, z: 0.4 }; // Up from hips and in front of avatar. - var startPosition = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.rotation, IN_FRONT_OF_AVATAR)); - var rayHitProps = entityPropertiesCache.getProps(rayPickInfo.entityID); - var finishPisition = Vec3.sum(rayHitProps.position, // Entity's centroid. - Vec3.multiplyQbyV(rayHitProps.rotation, - Vec3.multiplyVbyV(rayHitProps.dimensions, - Vec3.subtract(DEFAULT_REGISTRATION_POINT, rayHitProps.registrationPoint)))); - this.otherGrabbingLineOn(startPosition, finishPisition, COLORS_GRAB_DISTANCE_HOLD); + this.updateLaserPointer(); } else { - this.otherGrabbingLineOff(); + this.laserPointerOff(); } } else { - this.otherGrabbingLineOff(); + this.laserPointerOff(); } } else { - this.otherGrabbingLineOff(); + this.laserPointerOff(); } this.updateEquipHaptics(potentialEquipHotspot, handPosition); @@ -2466,7 +2386,7 @@ function MyController(hand) { } if (farGrabEnabled && farSearching) { - this.searchIndicatorOn(rayPickInfo.searchRay); + this.updateLaserPointer(); } Reticle.setVisible(false); }; @@ -2799,7 +2719,7 @@ function MyController(hand) { y: 0.0, z: objDistance }); - var change = Vec3.multiply(Vec3.subtract(before, after), HAND_HEAD_MIX_RATIO); + var change = Vec3.subtract(before, after) * (PICK_WITH_HAND_RAY ? 0.0 : 1.0); this.currentCameraOrientation = Camera.orientation; this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); } @@ -2808,11 +2728,7 @@ function MyController(hand) { this.maybeScale(grabbedProperties); // visualizations - var rayPickInfo = this.calcRayPickInfo(this.hand); - this.overlayLineOn(rayPickInfo.searchRay.origin, - Vec3.subtract(grabbedProperties.position, this.offsetPosition), - COLORS_GRAB_DISTANCE_HOLD, - this.grabbedThingID); + this.updateLaserPointer(); var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); @@ -2919,9 +2835,7 @@ function MyController(hand) { this.getOtherHandController().offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, this.getOtherHandController().offsetPosition); - var rayPickInfo = this.calcRayPickInfo(this.hand); - this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), - COLORS_GRAB_DISTANCE_HOLD, this.grabbedThingID); + this.updateLaserPointer(); this.previousWorldControllerRotation = worldControllerRotation; }; @@ -3005,10 +2919,7 @@ function MyController(hand) { this.nearGrabbingEnter = function() { this.grabPointSphereOff(); - this.lineOff(); - this.overlayLineOff(); - this.searchSphereOff(); - this.otherGrabbingLineOff(); + this.laserPointerOff(); this.dropGestureReset(); this.clearEquipHaptics(); @@ -3512,28 +3423,18 @@ function MyController(hand) { return; } - var pickRay = { - origin: getControllerWorldLocation(this.handToController(), false).position, - direction: Quat.getUp(getControllerWorldLocation(this.handToController(), false).orientation) - }; - - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - var intersection = findRayIntersection(pickRay, true, [], [], true); - if (intersection.accurate || intersection.overlayID) { - this.lastPickTime = now; - if (intersection.entityID != this.grabbedThingID) { - this.callEntityMethodOnGrabbed("stopFarTrigger"); - this.grabbedThingID = null; - this.setState(STATE_OFF, "laser moved off of entity"); - return; - } - if (intersection.intersects) { - this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); - } - if (farGrabEnabled) { - this.searchIndicatorOn(pickRay); - } + var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; + var intersection = LaserPointers.getPrevRayPickResult(laserPointerID); + if (intersection.type != RayPick.INTERSECTED_NONE) { + if (intersection.objectID != this.grabbedThingID) { + this.callEntityMethodOnGrabbed("stopFarTrigger"); + this.grabbedThingID = null; + this.setState(STATE_OFF, "laser moved off of entity"); + return; + } + this.intersectionDistance = intersection.distance; + if (farGrabEnabled) { + this.updateLaserPointer(); } } @@ -3541,8 +3442,6 @@ function MyController(hand) { }; this.offEnter = function() { - // Reuse the existing search distance if lasers were active since - // they will be shown in OFF state while in edit mode. var existingSearchDistance = this.searchSphereDistance; this.release(); @@ -3664,7 +3563,7 @@ function MyController(hand) { this.intersectionDistance = intersectInfo.distance; if (this.state == STATE_ENTITY_LASER_TOUCHING) { - this.searchIndicatorOn(intersectInfo.searchRay); + this.updateLaserPointer(); } Reticle.setVisible(false); } else { @@ -3792,7 +3691,7 @@ function MyController(hand) { this.intersectionDistance = intersectInfo.distance; if (this.state == STATE_OVERLAY_LASER_TOUCHING) { - this.searchIndicatorOn(intersectInfo.searchRay); + this.updateLaserPointer(); } Reticle.setVisible(false); } else { @@ -3935,7 +3834,8 @@ function MyController(hand) { this.release(); this.grabPointSphereOff(); this.hideStylus(); - this.overlayLineOff(); + LaserPointers.removeLaserPointer(this.laserPointer); + LaserPointers.removeLaserPointer(this.headLaserPointer); }; this.thisHandIsParent = function(props) { @@ -3986,8 +3886,7 @@ function MyController(hand) { children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerCRJointIndex)); children.forEach(function(childID) { - if (childID !== _this.stylus && - childID !== _this.overlayLine) { + if (childID !== _this.stylus) { // we appear to be holding something and this script isn't in a state that would be holding something. // unhook it. if we previously took note of this entity's parent, put it back where it was. This // works around some problems that happen when more than one hand or avatar is passing something around. @@ -4101,6 +4000,15 @@ Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.subscribe('Hifi-Object-Manipulation'); Messages.subscribe('Hifi-Hand-Drop'); +var setBlacklist = function() { + if (USE_BLACKLIST) { + LaserPointers.setIgnoreEntities(leftController.laserPointer, blacklist); + LaserPointers.setIgnoreEntities(leftController.headLaserPointer, blacklist); + LaserPointers.setIgnoreEntities(rightController.laserPointer, blacklist); + LaserPointers.setIgnoreEntities(rightController.headLaserPointer, blacklist); + } +} + var handleHandMessages = function(channel, message, sender) { var data; if (sender === MyAvatar.sessionUUID) { @@ -4175,10 +4083,12 @@ var handleHandMessages = function(channel, message, sender) { if (action === 'add' && index === -1) { blacklist.push(id); + setBlacklist(); } if (action === 'remove') { if (index > -1) { blacklist.splice(index, 1); + setBlacklist(); } } diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index eb94428100..538fe0b1e4 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -150,15 +150,92 @@ var setReticlePosition = function (point2d) { Reticle.setPosition(point2d); }; -// Generalizations of utilities that work with system and overlay elements. -function findRayIntersection(pickRay) { - // Check 3D overlays and entities. Argument is an object with origin and direction. - var result = Overlays.findRayIntersection(pickRay); - if (!result.intersects) { - result = Entities.findRayIntersection(pickRay, true); - } - return result; +// VISUAL AID ----------- +// Same properties as handControllerGrab search sphere +var LASER_ALPHA = 0.5; +var LASER_SEARCH_COLOR = {red: 10, green: 10, blue: 255}; +var LASER_TRIGGER_COLOR = {red: 250, green: 10, blue: 10}; +var END_DIAMETER = 0.05; +var systemLaserOn = false; + +var triggerPath = { + type: "line3d", + color: LASER_TRIGGER_COLOR, + ignoreRayIntersection: true, + visible: true, + alpha: LASER_ALPHA, + solid: true, + glow: 1.0, + drawHUDLayer: true } +var triggerEnd = { + type: "sphere", + dimensions: {x: END_DIAMETER, y: END_DIAMETER, z: END_DIAMETER}, + color: LASER_TRIGGER_COLOR, + ignoreRayIntersection: true, + visible: true, + alpha: LASER_ALPHA, + solid: true, + drawHUDLayer: true +} + +var searchPath = { + type: "line3d", + color: LASER_SEARCH_COLOR, + ignoreRayIntersection: true, + visible: true, + alpha: LASER_ALPHA, + solid: true, + glow: 1.0, + drawHUDLayer: true +} +var searchEnd = { + type: "sphere", + dimensions: {x: END_DIAMETER, y: END_DIAMETER, z: END_DIAMETER}, + color: LASER_SEARCH_COLOR, + ignoreRayIntersection: true, + visible: true, + alpha: LASER_ALPHA, + solid: true, + drawHUDLayer: true +} + +var hudRayStates = [{name: "trigger", path: triggerPath, end: triggerEnd}, + {name: "search", path: searchPath, end: searchEnd}]; +// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378 +var GRAB_POINT_SPHERE_OFFSET_RIGHT = { x: 0.04, y: 0.13, z: 0.039 }; +var GRAB_POINT_SPHERE_OFFSET_LEFT = { x: -0.04, y: 0.13, z: 0.039 }; +var hudRayRight = LaserPointers.createLaserPointer({ + joint: "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", + filter: RayPick.PICK_HUD, + posOffset: GRAB_POINT_SPHERE_OFFSET_RIGHT, + renderStates: hudRayStates, + enabled: true +}); +var hudRayLeft = LaserPointers.createLaserPointer({ + joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_HUD, + posOffset: GRAB_POINT_SPHERE_OFFSET_LEFT, + renderStates: hudRayStates, + enabled: true +}); + +// NOTE: keep this offset in sync with scripts/system/librarires/controllers.js:57 +var VERTICAL_HEAD_LASER_OFFSET = 0.1; +var hudRayHead = LaserPointers.createLaserPointer({ + joint: "Avatar", + filter: RayPick.PICK_HUD, + posOffset: {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0}, + renderStates: hudRayStates, + enabled: true +}); + +var mouseRayPick = RayPick.createRayPick({ + joint: "Mouse", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + enabled: true +}); + function isPointingAtOverlay(optionalHudPosition2d) { return Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(optionalHudPosition2d || Reticle.position); } @@ -166,10 +243,21 @@ function isPointingAtOverlay(optionalHudPosition2d) { // Generalized HUD utilities, with or without HMD: // This "var" is for documentation. Do not change the value! var PLANAR_PERPENDICULAR_HUD_DISTANCE = 1; -function calculateRayUICollisionPoint(position, direction) { +function calculateRayUICollisionPoint(position, direction, isHands) { // Answer the 3D intersection of the HUD by the given ray, or falsey if no intersection. if (HMD.active) { - return HMD.calculateRayUICollisionPoint(position, direction); + var laserPointer; + if (isHands) { + laserPointer = activeHand == Controller.Standard.RightHand ? hudRayRight : hudRayLeft; + } else { + laserPointer = hudRayHead; + } + var result = LaserPointers.getPrevRayPickResult(laserPointer); + if (result.type != RayPick.INTERSECTED_NONE) { + return result.intersection; + } else { + return null; + } } // interect HUD plane, 1m in front of camera, using formula: // scale = hudNormal dot (hudPoint - position) / hudNormal dot direction @@ -215,7 +303,7 @@ function activeHudPoint2dGamePad() { var headPosition = MyAvatar.getHeadPosition(); var headDirection = Quat.getUp(Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }))); - var hudPoint3d = calculateRayUICollisionPoint(headPosition, headDirection); + var hudPoint3d = calculateRayUICollisionPoint(headPosition, headDirection, false); if (!hudPoint3d) { if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here @@ -241,7 +329,7 @@ function activeHudPoint2d(activeHand) { // if controller is valid, update reticl var controllerPosition = controllerPose.position; var controllerDirection = Quat.getUp(controllerPose.rotation); - var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); + var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection, true); if (!hudPoint3d) { if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong. @@ -355,7 +443,7 @@ function onMouseMove() { if (isPointingAtOverlay()) { Reticle.depth = hudReticleDistance(); } else { - var result = findRayIntersection(Camera.computePickRay(Reticle.position.x, Reticle.position.y)); + var result = RayPick.getPrevRayPickResult(mouseRayPick); Reticle.depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH; } } @@ -473,14 +561,6 @@ clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard. clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand)); clickMapping.enable(); -// VISUAL AID ----------- -// Same properties as handControllerGrab search sphere -var LASER_ALPHA = 0.5; -var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA}; -var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA}; -var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; -var systemLaserOn = false; - var HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable"; var isPointerEnabled = true; @@ -488,23 +568,34 @@ function clearSystemLaser() { if (!systemLaserOn) { return; } - HMD.disableHandLasers(BOTH_HUD_LASERS); - HMD.disableExtraLaser(); + HMD.deactivateHMDHandMouse(); + LaserPointers.setRenderState(hudRayRight, ""); + LaserPointers.setRenderState(hudRayLeft, ""); + LaserPointers.setRenderState(hudRayHead, ""); systemLaserOn = false; weMovedReticle = true; } function setColoredLaser() { // answer trigger state if lasers supported, else falsey. - var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW; + var mode = (activeTrigger.state === 'full') ? 'trigger' : 'search'; - if (!HMD.isHandControllerAvailable()) { - // NOTE: keep this offset in sync with scripts/system/librarires/controllers.js:57 - var VERTICAL_HEAD_LASER_OFFSET = 0.1; - var position = Vec3.sum(HMD.position, Vec3.multiplyQbyV(HMD.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0})); - var orientation = Quat.multiply(HMD.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 })); - return HMD.setExtraLaser(position, true, color, Quat.getUp(orientation)); + if (!systemLaserOn) { + HMD.activateHMDHandMouse(); } - return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state; + var pose = Controller.getPoseValue(activeHand); + if (!pose.valid) { + LaserPointers.setRenderState(hudRayRight, ""); + LaserPointers.setRenderState(hudRayLeft, ""); + LaserPointers.setRenderState(hudRayHead, mode); + return true; + } + + var right = activeHand == Controller.Standard.RightHand; + LaserPointers.setRenderState(hudRayRight, right ? mode : ""); + LaserPointers.setRenderState(hudRayLeft, right ? "" : mode); + LaserPointers.setRenderState(hudRayHead, ""); + + return activeTrigger.state; } // MAIN OPERATIONS ----------- @@ -551,11 +642,13 @@ function update() { if (HMD.active) { Reticle.depth = hudReticleDistance(); - if (!HMD.isHandControllerAvailable()) { - var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW; - var position = MyAvatar.getHeadPosition(); - var direction = Quat.getUp(Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }))); - HMD.setExtraLaser(position, true, color, direction); + var pose = Controller.getPoseValue(activeHand); + if (!pose.valid) { + var mode = (activeTrigger.state === 'full') ? 'trigger' : 'search'; + if (!systemLaserOn) { + HMD.activateHMDHandMouse(); + } + LaserPointers.setRenderState(hudRayHead, mode); } } @@ -604,6 +697,10 @@ Script.scriptEnding.connect(function () { Script.clearInterval(settingsChecker); Script.update.disconnect(update); OffscreenFlags.navigationFocusDisabled = false; + LaserPointers.removeLaserPointer(hudRayRight); + LaserPointers.removeLaserPointer(hudRayLeft); + LaserPointers.removeLaserPointer(hudRayHead); + HMD.deactivateHMDHandMouse(); }); }()); // END LOCAL_SCOPE diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index 15ba314a1d..17ca2f91c5 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -37,12 +37,6 @@ var COLORS_TELEPORT_CAN_TELEPORT = { blue: 255 }; -var COLORS_TELEPORT_CANNOT_TELEPORT = { - red: 0, - green: 121, - blue: 141 -}; - var COLORS_TELEPORT_CANCEL = { red: 255, green: 184, @@ -61,6 +55,59 @@ var handInfo = { } }; +var cancelPath = { + type: "line3d", + color: COLORS_TELEPORT_CANCEL, + ignoreRayIntersection: true, + alpha: 1, + solid: true, + drawInFront: true, + glow: 1.0 +}; +var teleportPath = { + type: "line3d", + color: COLORS_TELEPORT_CAN_TELEPORT, + ignoreRayIntersection: true, + alpha: 1, + solid: true, + drawInFront: true, + glow: 1.0 +}; +var seatPath = { + type: "line3d", + color: COLORS_TELEPORT_SEAT, + ignoreRayIntersection: true, + alpha: 1, + solid: true, + drawInFront: true, + glow: 1.0 +}; +var cancelEnd = { + type: "model", + url: TOO_CLOSE_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + ignoreRayIntersection: true +}; +var teleportEnd = { + type: "model", + url: TARGET_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + ignoreRayIntersection: true +}; +var seatEnd = { + type: "model", + url: SEAT_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + ignoreRayIntersection: true +} + +var teleportRenderStates = [{name: "cancel", path: cancelPath, end: cancelEnd}, + {name: "teleport", path: teleportPath, end: teleportEnd}, + {name: "seat", path: seatPath, end: seatEnd}]; + +var DEFAULT_DISTANCE = 50; +var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}]; + function ThumbPad(hand) { this.hand = hand; var _thisPad = this; @@ -108,33 +155,59 @@ function Teleporter() { this.state = TELEPORTER_STATES.IDLE; this.currentTarget = TARGET.INVALID; - this.overlayLines = { - left: null, - right: null, - }; + this.teleportRayLeftVisible = LaserPointers.createLaserPointer({ + joint: "LeftHand", + filter: RayPick.PICK_ENTITIES, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates, + defaultRenderStates: teleportDefaultRenderStates + }); + this.teleportRayLeftInvisible = LaserPointers.createLaserPointer({ + joint: "LeftHand", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates + }); + this.teleportRayRightVisible = LaserPointers.createLaserPointer({ + joint: "RightHand", + filter: RayPick.PICK_ENTITIES, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates, + defaultRenderStates: teleportDefaultRenderStates + }); + this.teleportRayRightInvisible = LaserPointers.createLaserPointer({ + joint: "RightHand", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates + }); + + this.teleportRayHeadVisible = LaserPointers.createLaserPointer({ + joint: "Avatar", + filter: RayPick.PICK_ENTITIES, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates, + defaultRenderStates: teleportDefaultRenderStates + }); + this.teleportRayHeadInvisible = LaserPointers.createLaserPointer({ + joint: "Avatar", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates + }); + this.updateConnected = null; this.activeHand = null; this.teleporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random(); this.teleportMappingInternal = Controller.newMapping(this.teleporterMappingInternalName); - // Setup overlays - this.cancelOverlay = Overlays.addOverlay("model", { - url: TOO_CLOSE_MODEL_URL, - dimensions: TARGET_MODEL_DIMENSIONS, - visible: false - }); - this.targetOverlay = Overlays.addOverlay("model", { - url: TARGET_MODEL_URL, - dimensions: TARGET_MODEL_DIMENSIONS, - visible: false - }); - this.seatOverlay = Overlays.addOverlay("model", { - url: SEAT_MODEL_URL, - dimensions: TARGET_MODEL_DIMENSIONS, - visible: false - }); - this.enableMappings = function() { Controller.enableMapping(this.teleporterMappingInternalName); }; @@ -146,16 +219,13 @@ function Teleporter() { this.cleanup = function() { this.disableMappings(); - Overlays.deleteOverlay(this.targetOverlay); - this.targetOverlay = null; + LaserPointers.removeLaserPointer(this.teleportRayLeftVisible); + LaserPointers.removeLaserPointer(this.teleportRayLeftInvisible); + LaserPointers.removeLaserPointer(this.teleportRayRightVisible); + LaserPointers.removeLaserPointer(this.teleportRayRightInvisible); + LaserPointers.removeLaserPointer(this.teleportRayHeadVisible); + LaserPointers.removeLaserPointer(this.teleportRayHeadInvisible); - Overlays.deleteOverlay(this.cancelOverlay); - this.cancelOverlay = null; - - Overlays.deleteOverlay(this.seatOverlay); - this.seatOverlay = null; - - this.deleteOverlayBeams(); if (this.updateConnected === true) { Script.update.disconnect(this, this.update); } @@ -194,43 +264,47 @@ function Teleporter() { } this.disableMappings(); - this.deleteOverlayBeams(); - this.hideTargetOverlay(); - this.hideCancelOverlay(); + LaserPointers.disableLaserPointer(this.teleportRayLeftVisible); + LaserPointers.disableLaserPointer(this.teleportRayLeftInvisible); + LaserPointers.disableLaserPointer(this.teleportRayRightVisible); + LaserPointers.disableLaserPointer(this.teleportRayRightInvisible); + LaserPointers.disableLaserPointer(this.teleportRayHeadVisible); + LaserPointers.disableLaserPointer(this.teleportRayHeadInvisible); this.updateConnected = null; this.state = TELEPORTER_STATES.IDLE; inTeleportMode = false; }; - this.deleteOverlayBeams = function() { - for (var key in this.overlayLines) { - if (this.overlayLines[key] !== null) { - Overlays.deleteOverlay(this.overlayLines[key]); - this.overlayLines[key] = null; - } - } - }; - this.update = function() { if (_this.state === TELEPORTER_STATES.IDLE) { return; } - // Get current hand pose information so that we can get the direction of the teleport beam + // Get current hand pose information to see if the pose is valid var pose = Controller.getPoseValue(handInfo[_this.activeHand].controllerInput); - var handPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition(); - var handRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) : - Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { - x: 1, - y: 0, - z: 0 - })); - - var pickRay = { - origin: handPosition, - direction: Quat.getUp(handRotation), - }; + var mode = pose.valid ? _this.activeHand : 'head'; + if (!pose.valid) { + if (mode === 'right') { + LaserPointers.disableLaserPointer(_this.teleportRayRightVisible); + LaserPointers.disableLaserPointer(_this.teleportRayRightInvisible); + } else { + LaserPointers.disableLaserPointer(_this.teleportRayLeftVisible); + LaserPointers.disableLaserPointer(_this.teleportRayLeftInvisible); + } + LaserPointers.enableLaserPointer(_this.teleportRayHeadVisible); + LaserPointers.enableLaserPointer(_this.teleportRayHeadInvisible); + } else { + if (mode === 'right') { + LaserPointers.enableLaserPointer(_this.teleportRayRightVisible); + LaserPointers.enableLaserPointer(_this.teleportRayRightInvisible); + } else { + LaserPointers.enableLaserPointer(_this.teleportRayLeftVisible); + LaserPointers.enableLaserPointer(_this.teleportRayLeftInvisible); + } + LaserPointers.disableLaserPointer(_this.teleportRayHeadVisible); + LaserPointers.disableLaserPointer(_this.teleportRayHeadInvisible); + } // We do up to 2 ray picks to find a teleport location. // There are 2 types of teleport locations we are interested in: @@ -241,48 +315,40 @@ function Teleporter() { // We might hit an invisible entity that is not a seat, so we need to do a second pass. // * In the second pass we pick against visible entities only. // - var intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity].concat(ignoredEntities), false, true); + var result; + if (mode === 'right') { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayRightInvisible); + } else if (mode === 'left') { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayLeftInvisible); + } else { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayHeadInvisible); + } - var teleportLocationType = getTeleportTargetType(intersection); + var teleportLocationType = getTeleportTargetType(result); if (teleportLocationType === TARGET.INVISIBLE) { - intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity].concat(ignoredEntities), true, true); - teleportLocationType = getTeleportTargetType(intersection); + if (mode === 'right') { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayRightVisible); + } else if (mode === 'left') { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayLeftVisible); + } else { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayHeadVisible); + } + teleportLocationType = getTeleportTargetType(result); } if (teleportLocationType === TARGET.NONE) { - this.hideTargetOverlay(); - this.hideCancelOverlay(); - this.hideSeatOverlay(); - - var farPosition = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, 50)); - this.updateLineOverlay(_this.activeHand, pickRay.origin, farPosition, COLORS_TELEPORT_CANNOT_TELEPORT); + // Use the cancel default state + this.setTeleportState(mode, "cancel", ""); } else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) { - this.hideTargetOverlay(); - this.hideSeatOverlay(); - - this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL); - this.updateDestinationOverlay(this.cancelOverlay, intersection); + this.setTeleportState(mode, "", "cancel"); } else if (teleportLocationType === TARGET.SURFACE) { if (this.state === TELEPORTER_STATES.COOL_IN) { - this.hideTargetOverlay(); - this.hideSeatOverlay(); - - this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL); - this.updateDestinationOverlay(this.cancelOverlay, intersection); + this.setTeleportState(mode, "cancel", ""); } else { - this.hideCancelOverlay(); - this.hideSeatOverlay(); - - this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, - COLORS_TELEPORT_CAN_TELEPORT); - this.updateDestinationOverlay(this.targetOverlay, intersection); + this.setTeleportState(mode, "teleport", ""); } } else if (teleportLocationType === TARGET.SEAT) { - this.hideCancelOverlay(); - this.hideTargetOverlay(); - - this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_SEAT); - this.updateDestinationOverlay(this.seatOverlay, intersection); + this.setTeleportState(mode, "", "seat"); } @@ -290,79 +356,33 @@ function Teleporter() { // remember the state before we exit teleport mode and set it back to IDLE var previousState = this.state; this.exitTeleportMode(); - this.hideCancelOverlay(); - this.hideTargetOverlay(); - this.hideSeatOverlay(); if (teleportLocationType === TARGET.NONE || teleportLocationType === TARGET.INVALID || previousState === TELEPORTER_STATES.COOL_IN) { // Do nothing } else if (teleportLocationType === TARGET.SEAT) { - Entities.callEntityMethod(intersection.entityID, 'sit'); + Entities.callEntityMethod(result.objectID, 'sit'); } else if (teleportLocationType === TARGET.SURFACE) { var offset = getAvatarFootOffset(); - intersection.intersection.y += offset; - MyAvatar.goToLocation(intersection.intersection, false, {x: 0, y: 0, z: 0, w: 1}, false); + result.intersection.y += offset; + MyAvatar.goToLocation(result.intersection, false, {x: 0, y: 0, z: 0, w: 1}, false); HMD.centerUI(); MyAvatar.centerBody(); } } }; - this.updateLineOverlay = function(hand, closePoint, farPoint, color) { - if (this.overlayLines[hand] === null) { - var lineProperties = { - start: closePoint, - end: farPoint, - color: color, - ignoreRayIntersection: true, - visible: true, - alpha: 1, - solid: true, - drawInFront: true, - glow: 1.0 - }; - - this.overlayLines[hand] = Overlays.addOverlay("line3d", lineProperties); - + this.setTeleportState = function(mode, visibleState, invisibleState) { + if (mode === 'right') { + LaserPointers.setRenderState(_this.teleportRayRightVisible, visibleState); + LaserPointers.setRenderState(_this.teleportRayRightInvisible, invisibleState); + } else if (mode === 'left') { + LaserPointers.setRenderState(_this.teleportRayLeftVisible, visibleState); + LaserPointers.setRenderState(_this.teleportRayLeftInvisible, invisibleState); } else { - Overlays.editOverlay(this.overlayLines[hand], { - start: closePoint, - end: farPoint, - color: color - }); + LaserPointers.setRenderState(_this.teleportRayHeadVisible, visibleState); + LaserPointers.setRenderState(_this.teleportRayHeadInvisible, invisibleState); } }; - - this.hideCancelOverlay = function() { - Overlays.editOverlay(this.cancelOverlay, { visible: false }); - }; - - this.hideTargetOverlay = function() { - Overlays.editOverlay(this.targetOverlay, { visible: false }); - }; - - this.hideSeatOverlay = function() { - Overlays.editOverlay(this.seatOverlay, { visible: false }); - }; - - this.updateDestinationOverlay = function(overlayID, intersection) { - var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP); - var euler = Quat.safeEulerAngles(rotation); - var position = { - x: intersection.intersection.x, - y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2, - z: intersection.intersection.z - }; - - var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0); - - Overlays.editOverlay(overlayID, { - visible: true, - position: position, - rotation: towardUs - }); - - }; } // related to repositioning the avatar after you teleport @@ -424,12 +444,12 @@ function parseJSON(json) { // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then // you can't teleport there. var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; -function getTeleportTargetType(intersection) { - if (!intersection.intersects) { +function getTeleportTargetType(result) { + if (result.type == RayPick.INTERSECTED_NONE) { return TARGET.NONE; } - var props = Entities.getEntityProperties(intersection.entityID, ['userData', 'visible']); + var props = Entities.getEntityProperties(result.objectID, ['userData', 'visible']); var data = parseJSON(props.userData); if (data !== undefined && data.seat !== undefined) { var avatarUuid = Uuid.fromString(data.seat.user); @@ -444,13 +464,13 @@ function getTeleportTargetType(intersection) { return TARGET.INVISIBLE; } - var surfaceNormal = intersection.surfaceNormal; + var surfaceNormal = result.surfaceNormal; var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z); var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI); if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) || angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) || - Vec3.distance(MyAvatar.position, intersection.intersection) <= TELEPORT_CANCEL_RANGE) { + Vec3.distance(MyAvatar.position, result.intersection) <= TELEPORT_CANCEL_RANGE) { return TARGET.INVALID; } else { return TARGET.SURFACE; @@ -509,6 +529,15 @@ function cleanup() { } Script.scriptEnding.connect(cleanup); +var setIgnoreEntities = function() { + LaserPointers.setIgnoreEntities(teleporter.teleportRayRightVisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayRightInvisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayLeftVisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayLeftInvisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayHeadVisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayHeadInvisible, ignoredEntities); +} + var isDisabled = false; var handleTeleportMessages = function(channel, message, sender) { if (sender === MyAvatar.sessionUUID) { @@ -527,10 +556,12 @@ var handleTeleportMessages = function(channel, message, sender) { } } else if (channel === 'Hifi-Teleport-Ignore-Add' && !Uuid.isNull(message) && ignoredEntities.indexOf(message) === -1) { ignoredEntities.push(message); + setIgnoreEntities(); } else if (channel === 'Hifi-Teleport-Ignore-Remove' && !Uuid.isNull(message)) { var removeIndex = ignoredEntities.indexOf(message); if (removeIndex > -1) { ignoredEntities.splice(removeIndex, 1); + setIgnoreEntities(); } } }